vendor: 2,339 bytes, lines 1-73
1/* @preserve 2 * Leaflet 1.7.1+master.0a61e90, a JS library for interactive maps. http://leafletjs.com 3 * (c) 2010-2021 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.L = {})); 10})(this, (function (exports) { 'use strict'; 11 12 var version = "1.7.1+master.0a61e906"; 13 14 /* 15 * @namespace Util 16 * 17 * Various utility functions, used by Leaflet internally. 18 */ 19 20 // @function extend(dest: Object, src?: Object): Object 21 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 22 function extend(dest) { 23 var i, j, len, src; 24 25 for (j = 1, len = arguments.length; j < len; j++) { 26 src = arguments[j]; 27 for (i in src) { 28 dest[i] = src[i]; 29 } 30 } 31 return dest; 32 } 33 34 // @function create(proto: Object, properties?: Object): Object 35 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 36 var create$2 = Object.create || (function () { 37 function F() {} 38 return function (proto) { 39 F.prototype = proto; 40 return new F(); 41 }; 42 })(); 43 44 // @function bind(fn: Function, â¦): Function 45 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). 46 // Has a `L.bind()` shortcut. 47 function bind(fn, obj) { 48 var slice = Array.prototype.slice; 49 50 if (fn.bind) { 51 return fn.bind.apply(fn, slice.call(arguments, 1)); 52 } 53 54 var args = slice.call(arguments, 2); 55 56 return function () { 57 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 58 }; 59 } 60 61 // @property lastId: Number 62 // Last unique ID used by [`stamp()`](#util-stamp) 63 var lastId = 0; 64 65 // @function stamp(obj: Object): Number 66 // Returns the unique ID of an object, assigning it one if it doesn't have it. 67 function stamp(obj) { 68 if (!('_leaflet_id' in obj)) { 69 obj['_leaflet_id'] = ++lastId; 70 } 71 return obj._leaflet_id; 72 } 73
vendor: 11,150 bytes, lines 74-404
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 http://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 // call the constructor 299 if (this.initialize) { 300 this.initialize.apply(this, arguments); 301 } 302 303 // call all constructor hooks 304 this.callInitHooks(); 305 }; 306 307 var parentProto = NewClass.__super__ = this.prototype; 308 309 var proto = create$2(parentProto); 310 proto.constructor = NewClass; 311 312 NewClass.prototype = proto; 313 314 // inherit parent's statics 315 for (var i in this) { 316 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') { 317 NewClass[i] = this[i]; 318 } 319 } 320 321 // mix static properties into the class 322 if (props.statics) { 323 extend(NewClass, props.statics); 324 } 325 326 // mix includes into the prototype 327 if (props.includes) { 328 checkDeprecatedMixinEvents(props.includes); 329 extend.apply(null, [proto].concat(props.includes)); 330 } 331 332 // mix given properties into the prototype 333 extend(proto, props); 334 delete proto.statics; 335 delete proto.includes; 336 337 // merge options 338 if (proto.options) { 339 proto.options = parentProto.options ? create$2(parentProto.options) : {}; 340 extend(proto.options, props.options); 341 } 342 343 proto._initHooks = []; 344 345 // add method for calling all hooks 346 proto.callInitHooks = function () { 347 348 if (this._initHooksCalled) { return; } 349 350 if (parentProto.callInitHooks) { 351 parentProto.callInitHooks.call(this); 352 } 353 354 this._initHooksCalled = true; 355 356 for (var i = 0, len = proto._initHooks.length; i < len; i++) { 357 proto._initHooks[i].call(this); 358 } 359 }; 360 361 return NewClass; 362 }; 363 364 365 // @function include(properties: Object): this 366 // [Includes a mixin](#class-includes) into the current class. 367 Class.include = function (props) { 368 var parentOptions = this.prototype.options; 369 extend(this.prototype, props); 370 if (props.options) { 371 this.prototype.options = parentOptions; 372 this.mergeOptions(props.options); 373 } 374 return this; 375 }; 376 377 // @function mergeOptions(options: Object): this 378 // [Merges `options`](#class-options) into the defaults of the class. 379 Class.mergeOptions = function (options) { 380 extend(this.prototype.options, options); 381 return this; 382 }; 383 384 // @function addInitHook(fn: Function): this 385 // Adds a [constructor hook](#class-constructor-hooks) to the class. 386 Class.addInitHook = function (fn) { // (Function) || (String, args...) 387 var args = Array.prototype.slice.call(arguments, 1); 388 389 var init = typeof fn === 'function' ? fn : function () { 390 this[fn].apply(this, args); 391 }; 392 393 this.prototype._initHooks = this.prototype._initHooks || []; 394 this.prototype._initHooks.push(init); 395 return this; 396 }; 397 398 function checkDeprecatedMixinEvents(includes) { 399 if (typeof L === 'undefined' || !L || !L.Mixin) { return; } 400 401 includes = isArray(includes) ? includes : [includes]; 402 403 for (var i = 0; i < includes.length; i++) { 404 if (includes[i] === L.Mixin.Events) {
405 console.warn('Deprecated include of L.Mixin.Events: ' + 406 'this property will be removed in future releases, ' + 407 'please inherit from L.Evented instead.', new Error().stack); 408 } 409 } 410 } 411 412 /* 413 * @class Evented 414 * @aka L.Evented 415 * @inherits Class 416 * 417 * 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). 418 * 419 * @example 420 * 421 * ```js 422 * map.on('click', function(e) { 423 * alert(e.latlng); 424 * } ); 425 * ``` 426 * 427 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 428 * 429 * ```js 430 * function onClick(e) { ... } 431 * 432 * map.on('click', onClick); 433 * map.off('click', onClick); 434 * ``` 435 */ 436 437 var Events = { 438 /* @method on(type: String, fn: Function, context?: Object): this 439 * 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'`). 440 * 441 * @alternative 442 * @method on(eventMap: Object): this 443 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 444 */ 445 on: function (types, fn, context) { 446 447 // types can be a map of types/handlers 448 if (typeof types === 'object') { 449 for (var type in types) { 450 // we don't process space-separated events here for performance; 451 // it's a hot path since Layer uses the on(obj) syntax 452 this._on(type, types[type], fn); 453 } 454 455 } else { 456 // types can be a string of space-separated words 457 types = splitWords(types); 458 459 for (var i = 0, len = types.length; i < len; i++) { 460 this._on(types[i], fn, context); 461 } 462 } 463 464 return this; 465 }, 466 467 /* @method off(type: String, fn?: Function, context?: Object): this 468 * 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. 469 * 470 * @alternative 471 * @method off(eventMap: Object): this 472 * Removes a set of type/listener pairs. 473 * 474 * @alternative 475 * @method off: this 476 * Removes all listeners to all events on the object. This includes implicitly attached events. 477 */ 478 off: function (types, fn, context) { 479 480 if (!arguments.length) { 481 // clear all listeners if called without arguments 482 delete this._events; 483 484 } else if (typeof types === 'object') { 485 for (var type in types) { 486 this._off(type, types[type], fn); 487 } 488 489 } else { 490 types = splitWords(types); 491 492 var removeAll = arguments.length === 1; 493 for (var i = 0, len = types.length; i < len; i++) { 494 if (removeAll) { 495 this._off(types[i]); 496 } else { 497 this._off(types[i], fn, context); 498 } 499 } 500 } 501 502 return this; 503 }, 504 505 // attach listener (without syntactic sugar now) 506 _on: function (type, fn, context) { 507 if (typeof fn !== 'function') { 508 console.warn('wrong listener type: ' + typeof fn); 509 return; 510 } 511 this._events = this._events || {}; 512 513 /* get/init listeners for type */ 514 var typeListeners = this._events[type]; 515 if (!typeListeners) { 516 typeListeners = []; 517 this._events[type] = typeListeners; 518 } 519 520 if (context === this) { 521 // Less memory footprint. 522 context = undefined; 523 } 524 var newListener = {fn: fn, ctx: context}, 525 listeners = typeListeners; 526 527 // check if fn already there 528 for (var i = 0, len = listeners.length; i < len; i++) { 529 if (listeners[i].fn === fn && listeners[i].ctx === context) { 530 return; 531 } 532 } 533 534 listeners.push(newListener); 535 }, 536 537 _off: function (type, fn, context) { 538 var listeners, 539 i, 540 len; 541 542 if (!this._events) { return; } 543 544 listeners = this._events[type]; 545 546 if (!listeners) { 547 return; 548 } 549 550 if (arguments.length === 1) { // remove all 551 if (this._firingCount) { 552 // Set all removed listeners to noop 553 // so they are not called if remove happens in fire 554 for (i = 0, len = listeners.length; i < len; i++) { 555 listeners[i].fn = falseFn; 556 } 557 } 558 // clear all listeners for a type if function isn't specified 559 delete this._events[type]; 560 return; 561 } 562 563 if (context === this) { 564 context = undefined; 565 } 566 567 if (typeof fn !== 'function') { 568 console.warn('wrong listener type: ' + typeof fn); 569 return; 570 } 571 // find fn and remove it 572 for (i = 0, len = listeners.length; i < len; i++) { 573 var l = listeners[i]; 574 if (l.ctx !== context) { continue; } 575 if (l.fn === fn) { 576 if (this._firingCount) { 577 // set the removed listener to noop so that's not called if remove happens in fire 578 l.fn = falseFn; 579 580 /* copy array in case events are being fired */ 581 this._events[type] = listeners = listeners.slice(); 582 } 583 listeners.splice(i, 1); 584 585 return; 586 } 587 } 588 console.warn('listener not found'); 589 }, 590 591 // @method fire(type: String, data?: Object, propagate?: Boolean): this 592 // Fires an event of the specified type. You can optionally provide a data 593 // object â the first argument of the listener function will contain its 594 // properties. The event can optionally be propagated to event parents. 595 fire: function (type, data, propagate) {
vendor: 9,254 bytes, lines 596-918
596 if (!this.listens(type, propagate)) { return this; } 597 598 var event = extend({}, data, { 599 type: type, 600 target: this, 601 sourceTarget: data && data.sourceTarget || this 602 }); 603 604 if (this._events) { 605 var listeners = this._events[type]; 606 607 if (listeners) { 608 this._firingCount = (this._firingCount + 1) || 1; 609 for (var i = 0, len = listeners.length; i < len; i++) { 610 var l = listeners[i]; 611 l.fn.call(l.ctx || this, event); 612 } 613 614 this._firingCount--; 615 } 616 } 617 618 if (propagate) { 619 // propagate the event to parents (set with addEventParent) 620 this._propagateEvent(event); 621 } 622 623 return this; 624 }, 625 626 // @method listens(type: String, propagate?: Boolean): Boolean 627 // Returns `true` if a particular event type has any listeners attached to it. 628 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it. 629 listens: function (type, propagate) { 630 if (typeof type !== 'string') { 631 console.warn('"string" type argument expected'); 632 } 633 var listeners = this._events && this._events[type]; 634 if (listeners && listeners.length) { return true; } 635 636 if (propagate) { 637 // also check parents for listeners if event propagates 638 for (var id in this._eventParents) { 639 if (this._eventParents[id].listens(type, propagate)) { return true; } 640 } 641 } 642 return false; 643 }, 644 645 // @method once(â¦): this 646 // Behaves as [`on(â¦)`](#evented-on), except the listener will only get fired once and then removed. 647 once: function (types, fn, context) { 648 649 if (typeof types === 'object') { 650 for (var type in types) { 651 this.once(type, types[type], fn); 652 } 653 return this; 654 } 655 656 var handler = bind(function () { 657 this 658 .off(types, fn, context) 659 .off(types, handler, context); 660 }, this); 661 662 // add a listener that's executed once and removed after that 663 return this 664 .on(types, fn, context) 665 .on(types, handler, context); 666 }, 667 668 // @method addEventParent(obj: Evented): this 669 // Adds an event parent - an `Evented` that will receive propagated events 670 addEventParent: function (obj) { 671 this._eventParents = this._eventParents || {}; 672 this._eventParents[stamp(obj)] = obj; 673 return this; 674 }, 675 676 // @method removeEventParent(obj: Evented): this 677 // Removes an event parent, so it will stop receiving propagated events 678 removeEventParent: function (obj) { 679 if (this._eventParents) { 680 delete this._eventParents[stamp(obj)]; 681 } 682 return this; 683 }, 684 685 _propagateEvent: function (e) { 686 for (var id in this._eventParents) { 687 this._eventParents[id].fire(e.type, extend({ 688 layer: e.target, 689 propagatedFrom: e.target 690 }, e), true); 691 } 692 } 693 }; 694 695 // aliases; we should ditch those eventually 696 697 // @method addEventListener(â¦): this 698 // Alias to [`on(â¦)`](#evented-on) 699 Events.addEventListener = Events.on; 700 701 // @method removeEventListener(â¦): this 702 // Alias to [`off(â¦)`](#evented-off) 703 704 // @method clearAllEventListeners(â¦): this 705 // Alias to [`off()`](#evented-off) 706 Events.removeEventListener = Events.clearAllEventListeners = Events.off; 707 708 // @method addOneTimeEventListener(â¦): this 709 // Alias to [`once(â¦)`](#evented-once) 710 Events.addOneTimeEventListener = Events.once; 711 712 // @method fireEvent(â¦): this 713 // Alias to [`fire(â¦)`](#evented-fire) 714 Events.fireEvent = Events.fire; 715 716 // @method hasEventListeners(â¦): Boolean 717 // Alias to [`listens(â¦)`](#evented-listens) 718 Events.hasEventListeners = Events.listens; 719 720 var Evented = Class.extend(Events); 721 722 /* 723 * @class Point 724 * @aka L.Point 725 * 726 * Represents a point with `x` and `y` coordinates in pixels. 727 * 728 * @example 729 * 730 * ```js 731 * var point = L.point(200, 300); 732 * ``` 733 * 734 * 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: 735 * 736 * ```js 737 * map.panBy([200, 300]); 738 * map.panBy(L.point(200, 300)); 739 * ``` 740 * 741 * Note that `Point` does not inherit from Leaflet's `Class` object, 742 * which means new classes can't inherit from it, and new methods 743 * can't be added to it with the `include` function. 744 */ 745 746 function Point(x, y, round) { 747 // @property x: Number; The `x` coordinate of the point 748 this.x = (round ? Math.round(x) : x); 749 // @property y: Number; The `y` coordinate of the point 750 this.y = (round ? Math.round(y) : y); 751 } 752 753 var trunc = Math.trunc || function (v) { 754 return v > 0 ? Math.floor(v) : Math.ceil(v); 755 }; 756 757 Point.prototype = { 758 759 // @method clone(): Point 760 // Returns a copy of the current point. 761 clone: function () { 762 return new Point(this.x, this.y); 763 }, 764 765 // @method add(otherPoint: Point): Point 766 // Returns the result of addition of the current and the given points. 767 add: function (point) { 768 // non-destructive, returns a new point 769 return this.clone()._add(toPoint(point)); 770 }, 771 772 _add: function (point) { 773 // destructive, used directly for performance in situations where it's safe to modify existing point 774 this.x += point.x; 775 this.y += point.y; 776 return this; 777 }, 778 779 // @method subtract(otherPoint: Point): Point 780 // Returns the result of subtraction of the given point from the current. 781 subtract: function (point) { 782 return this.clone()._subtract(toPoint(point)); 783 }, 784 785 _subtract: function (point) { 786 this.x -= point.x; 787 this.y -= point.y; 788 return this; 789 }, 790 791 // @method divideBy(num: Number): Point 792 // Returns the result of division of the current point by the given number. 793 divideBy: function (num) { 794 return this.clone()._divideBy(num); 795 }, 796 797 _divideBy: function (num) { 798 this.x /= num; 799 this.y /= num; 800 return this; 801 }, 802 803 // @method multiplyBy(num: Number): Point 804 // Returns the result of multiplication of the current point by the given number. 805 multiplyBy: function (num) { 806 return this.clone()._multiplyBy(num); 807 }, 808 809 _multiplyBy: function (num) { 810 this.x *= num; 811 this.y *= num; 812 return this; 813 }, 814 815 // @method scaleBy(scale: Point): Point 816 // Multiply each coordinate of the current point by each coordinate of 817 // `scale`. In linear algebra terms, multiply the point by the 818 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 819 // defined by `scale`. 820 scaleBy: function (point) { 821 return new Point(this.x * point.x, this.y * point.y); 822 }, 823 824 // @method unscaleBy(scale: Point): Point 825 // Inverse of `scaleBy`. Divide each coordinate of the current point by 826 // each coordinate of `scale`. 827 unscaleBy: function (point) { 828 return new Point(this.x / point.x, this.y / point.y); 829 }, 830 831 // @method round(): Point 832 // Returns a copy of the current point with rounded coordinates. 833 round: function () { 834 return this.clone()._round(); 835 }, 836 837 _round: function () { 838 this.x = Math.round(this.x); 839 this.y = Math.round(this.y); 840 return this; 841 }, 842 843 // @method floor(): Point 844 // Returns a copy of the current point with floored coordinates (rounded down). 845 floor: function () { 846 return this.clone()._floor(); 847 }, 848 849 _floor: function () { 850 this.x = Math.floor(this.x); 851 this.y = Math.floor(this.y); 852 return this; 853 }, 854 855 // @method ceil(): Point 856 // Returns a copy of the current point with ceiled coordinates (rounded up). 857 ceil: function () { 858 return this.clone()._ceil(); 859 }, 860 861 _ceil: function () { 862 this.x = Math.ceil(this.x); 863 this.y = Math.ceil(this.y); 864 return this; 865 }, 866 867 // @method trunc(): Point 868 // Returns a copy of the current point with truncated coordinates (rounded towards zero). 869 trunc: function () { 870 return this.clone()._trunc(); 871 }, 872 873 _trunc: function () { 874 this.x = trunc(this.x); 875 this.y = trunc(this.y); 876 return this; 877 }, 878 879 // @method distanceTo(otherPoint: Point): Number 880 // Returns the cartesian distance between the current and the given points. 881 distanceTo: function (point) { 882 point = toPoint(point); 883 884 var x = point.x - this.x, 885 y = point.y - this.y; 886 887 return Math.sqrt(x * x + y * y); 888 }, 889 890 // @method equals(otherPoint: Point): Boolean 891 // Returns `true` if the given point has the same coordinates. 892 equals: function (point) { 893 point = toPoint(point); 894 895 return point.x === this.x && 896 point.y === this.y; 897 }, 898 899 // @method contains(otherPoint: Point): Boolean 900 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 901 contains: function (point) { 902 point = toPoint(point); 903 904 return Math.abs(point.x) <= Math.abs(this.x) && 905 Math.abs(point.y) <= Math.abs(this.y); 906 }, 907 908 // @method toString(): String 909 // Returns a string representation of the point for debugging purposes. 910 toString: function () { 911 return 'Point(' + 912 formatNum(this.x) + ', ' + 913 formatNum(this.y) + ')'; 914 } 915 }; 916 917 // @factory L.point(x: Number, y: Number, round?: Boolean) 918 // Creates a Point object with the given `x` and `y` coordinates. If optional `roun
918d` is set to true, rounds the `x` and `y` values. 919 920 // @alternative 921 // @factory L.point(coords: Number[]) 922 // Expects an array of the form `[x, y]` instead. 923 924 // @alternative 925 // @factory L.point(coords: Object) 926 // Expects a plain object of the form `{x: Number, y: Number}` instead. 927 function toPoint(x, y, round) { 928 if (x instanceof Point) { 929 return x; 930 } 931 if (isArray(x)) { 932 return new Point(x[0], x[1]); 933 } 934 if (x === undefined || x === null) { 935 return x; 936 } 937 if (typeof x === 'object' && 'x' in x && 'y' in x) { 938 return new Point(x.x, x.y); 939 } 940 return new Point(x, y, round); 941 } 942 943 /* 944 * @class Bounds 945 * @aka L.Bounds 946 * 947 * Represents a rectangular area in pixel coordinates. 948 * 949 * @example 950 * 951 * ```js 952 * var p1 = L.point(10, 10), 953 * p2 = L.point(40, 60), 954 * bounds = L.bounds(p1, p2); 955 * ``` 956 * 957 * 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: 958 * 959 * ```js 960 * otherBounds.intersects([[10, 10], [40, 60]]); 961 * ``` 962 * 963 * Note that `Bounds` does not inherit from Leaflet's `Class` object, 964 * which means new classes can't inherit from it, and new methods 965 * can't be added to it with the `include` function. 966 */ 967 968 function Bounds(a, b) { 969 if (!a) { return; } 970 971 var points = b ? [a, b] : a; 972 973 for (var i = 0, len = points.length; i < len; i++) { 974 this.extend(points[i]); 975 } 976 } 977 978 Bounds.prototype = { 979 // @method extend(point: Point): this 980 // Extends the bounds to contain the given point. 981 extend: function (point) { // (Point) 982 point = toPoint(point); 983 984 // @property min: Point 985 // The top left corner of the rectangle. 986 // @property max: Point 987 // The bottom right corner of the rectangle. 988 if (!this.min && !this.max) { 989 this.min = point.clone(); 990 this.max = point.clone(); 991 } else { 992 this.min.x = Math.min(point.x, this.min.x); 993 this.max.x = Math.max(point.x, this.max.x); 994 this.min.y = Math.min(point.y, this.min.y); 995 this.max.y = Math.max(point.y, this.max.y); 996 } 997 return this; 998 }, 999 1000 // @method getCenter(round?: Boolean): Point 1001 // Returns the center point of the bounds. 1002 getCenter: function (round) { 1003 return new Point( 1004 (this.min.x + this.max.x) / 2, 1005 (this.min.y + this.max.y) / 2, round); 1006 }, 1007 1008 // @method getBottomLeft(): Point 1009 // Returns the bottom-left point of the bounds. 1010 getBottomLeft: function () { 1011 return new Point(this.min.x, this.max.y); 1012 }, 1013 1014 // @method getTopRight(): Point 1015 // Returns the top-right point of the bounds. 1016 getTopRight: function () { // -> Point 1017 return new Point(this.max.x, this.min.y); 1018 }, 1019 1020 // @method getTopLeft(): Point 1021 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 1022 getTopLeft: function () { 1023 return this.min; // left, top 1024 }, 1025 1026 // @method getBottomRight(): Point 1027 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 1028 getBottomRight: function () { 1029 return this.max; // right, bottom 1030 }, 1031 1032 // @method getSize(): Point 1033 // Returns the size of the given bounds 1034 getSize: function () { 1035 return this.max.subtract(this.min); 1036 }, 1037 1038 // @method contains(otherBounds: Bounds): Boolean 1039 // Returns `true` if the rectangle contains the given one. 1040 // @alternative 1041 // @method contains(point: Point): Boolean 1042 // Returns `true` if the rectangle contains the given point. 1043 contains: function (obj) { 1044 var min, max; 1045 1046 if (typeof obj[0] === 'number' || obj instanceof Point) { 1047 obj = toPoint(obj); 1048 } else { 1049 obj = toBounds(obj); 1050 } 1051 1052 if (obj instanceof Bounds) { 1053 min = obj.min; 1054 max = obj.max; 1055 } else { 1056 min = max = obj; 1057 } 1058 1059 return (min.x >= this.min.x) && 1060 (max.x <= this.max.x) && 1061 (min.y >= this.min.y) && 1062 (max.y <= this.max.y); 1063 }, 1064 1065 // @method intersects(otherBounds: Bounds): Boolean 1066 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1067 // intersect if they have at least one point in common. 1068 intersects: function (bounds) { // (Bounds) -> Boolean 1069 bounds = toBounds(bounds); 1070 1071 var min = this.min, 1072 max = this.max, 1073 min2 = bounds.min,
vendor: 13,614 bytes, lines 1074-1503
1074 max2 = bounds.max, 1075 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1076 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1077 1078 return xIntersects && yIntersects; 1079 }, 1080 1081 // @method overlaps(otherBounds: Bounds): Boolean 1082 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1083 // overlap if their intersection is an area. 1084 overlaps: function (bounds) { // (Bounds) -> Boolean 1085 bounds = toBounds(bounds); 1086 1087 var min = this.min, 1088 max = this.max, 1089 min2 = bounds.min, 1090 max2 = bounds.max, 1091 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1092 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1093 1094 return xOverlaps && yOverlaps; 1095 }, 1096 1097 isValid: function () { 1098 return !!(this.min && this.max); 1099 } 1100 }; 1101 1102 1103 // @factory L.bounds(corner1: Point, corner2: Point) 1104 // Creates a Bounds object from two corners coordinate pairs. 1105 // @alternative 1106 // @factory L.bounds(points: Point[]) 1107 // Creates a Bounds object from the given array of points. 1108 function toBounds(a, b) { 1109 if (!a || a instanceof Bounds) { 1110 return a; 1111 } 1112 return new Bounds(a, b); 1113 } 1114 1115 /* 1116 * @class LatLngBounds 1117 * @aka L.LatLngBounds 1118 * 1119 * Represents a rectangular geographical area on a map. 1120 * 1121 * @example 1122 * 1123 * ```js 1124 * var corner1 = L.latLng(40.712, -74.227), 1125 * corner2 = L.latLng(40.774, -74.125), 1126 * bounds = L.latLngBounds(corner1, corner2); 1127 * ``` 1128 * 1129 * 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: 1130 * 1131 * ```js 1132 * map.fitBounds([ 1133 * [40.712, -74.227], 1134 * [40.774, -74.125] 1135 * ]); 1136 * ``` 1137 * 1138 * 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. 1139 * 1140 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object, 1141 * which means new classes can't inherit from it, and new methods 1142 * can't be added to it with the `include` function. 1143 */ 1144 1145 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1146 if (!corner1) { return; } 1147 1148 var latlngs = corner2 ? [corner1, corner2] : corner1; 1149 1150 for (var i = 0, len = latlngs.length; i < len; i++) { 1151 this.extend(latlngs[i]); 1152 } 1153 } 1154 1155 LatLngBounds.prototype = { 1156 1157 // @method extend(latlng: LatLng): this 1158 // Extend the bounds to contain the given point 1159 1160 // @alternative 1161 // @method extend(otherBounds: LatLngBounds): this 1162 // Extend the bounds to contain the given bounds 1163 extend: function (obj) { 1164 var sw = this._southWest, 1165 ne = this._northEast, 1166 sw2, ne2; 1167 1168 if (obj instanceof LatLng) { 1169 sw2 = obj; 1170 ne2 = obj; 1171 1172 } else if (obj instanceof LatLngBounds) { 1173 sw2 = obj._southWest; 1174 ne2 = obj._northEast; 1175 1176 if (!sw2 || !ne2) { return this; } 1177 1178 } else { 1179 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 1180 } 1181 1182 if (!sw && !ne) { 1183 this._southWest = new LatLng(sw2.lat, sw2.lng); 1184 this._northEast = new LatLng(ne2.lat, ne2.lng); 1185 } else { 1186 sw.lat = Math.min(sw2.lat, sw.lat); 1187 sw.lng = Math.min(sw2.lng, sw.lng); 1188 ne.lat = Math.max(ne2.lat, ne.lat); 1189 ne.lng = Math.max(ne2.lng, ne.lng); 1190 } 1191 1192 return this; 1193 }, 1194 1195 // @method pad(bufferRatio: Number): LatLngBounds 1196 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1197 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1198 // Negative values will retract the bounds. 1199 pad: function (bufferRatio) { 1200 var sw = this._southWest, 1201 ne = this._northEast, 1202 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1203 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1204 1205 return new LatLngBounds( 1206 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1207 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1208 }, 1209 1210 // @method getCenter(): LatLng 1211 // Returns the center point of the bounds. 1212 getCenter: function () { 1213 return new LatLng( 1214 (this._southWest.lat + this._northEast.lat) / 2, 1215 (this._southWest.lng + this._northEast.lng) / 2); 1216 }, 1217 1218 // @method getSouthWest(): LatLng 1219 // Returns the south-west point of the bounds. 1220 getSouthWest: function () { 1221 return this._southWest; 1222 }, 1223 1224 // @method getNorthEast(): LatLng 1225 // Returns the north-east point of the bounds. 1226 getNorthEast: function () { 1227 return this._northEast; 1228 }, 1229 1230 // @method getNorthWest(): LatLng 1231 // Returns the north-west point of the bounds. 1232 getNorthWest: function () { 1233 return new LatLng(this.getNorth(), this.getWest()); 1234 }, 1235 1236 // @method getSouthEast(): LatLng 1237 // Returns the south-east point of the bounds. 1238 getSouthEast: function () { 1239 return new LatLng(this.getSouth(), this.getEast()); 1240 }, 1241 1242 // @method getWest(): Number 1243 // Returns the west longitude of the bounds 1244 getWest: function () { 1245 return this._southWest.lng; 1246 }, 1247 1248 // @method getSouth(): Number 1249 // Returns the south latitude of the bounds 1250 getSouth: function () { 1251 return this._southWest.lat; 1252 }, 1253 1254 // @method getEast(): Number 1255 // Returns the east longitude of the bounds 1256 getEast: function () { 1257 return this._northEast.lng; 1258 }, 1259 1260 // @method getNorth(): Number 1261 // Returns the north latitude of the bounds 1262 getNorth: function () { 1263 return this._northEast.lat; 1264 }, 1265 1266 // @method contains(otherBounds: LatLngBounds): Boolean 1267 // Returns `true` if the rectangle contains the given one. 1268 1269 // @alternative 1270 // @method contains (latlng: LatLng): Boolean 1271 // Returns `true` if the rectangle contains the given point. 1272 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1273 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { 1274 obj = toLatLng(obj); 1275 } else { 1276 obj = toLatLngBounds(obj); 1277 } 1278 1279 var sw = this._southWest, 1280 ne = this._northEast, 1281 sw2, ne2; 1282 1283 if (obj instanceof LatLngBounds) { 1284 sw2 = obj.getSouthWest(); 1285 ne2 = obj.getNorthEast(); 1286 } else { 1287 sw2 = ne2 = obj; 1288 } 1289 1290 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1291 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1292 }, 1293 1294 // @method intersects(otherBounds: LatLngBounds): Boolean 1295 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1296 intersects: function (bounds) { 1297 bounds = toLatLngBounds(bounds); 1298 1299 var sw = this._southWest, 1300 ne = this._northEast, 1301 sw2 = bounds.getSouthWest(), 1302 ne2 = bounds.getNorthEast(), 1303 1304 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1305 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1306 1307 return latIntersects && lngIntersects; 1308 }, 1309 1310 // @method overlaps(otherBounds: LatLngBounds): Boolean 1311 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1312 overlaps: function (bounds) { 1313 bounds = toLatLngBounds(bounds); 1314 1315 var sw = this._southWest, 1316 ne = this._northEast, 1317 sw2 = bounds.getSouthWest(), 1318 ne2 = bounds.getNorthEast(), 1319 1320 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1321 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1322 1323 return latOverlaps && lngOverlaps; 1324 }, 1325 1326 // @method toBBoxString(): String 1327 // 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. 1328 toBBoxString: function () { 1329 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1330 }, 1331 1332 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 1333 // 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. 1334 equals: function (bounds, maxMargin) { 1335 if (!bounds) { return false; } 1336 1337 bounds = toLatLngBounds(bounds); 1338 1339 return this._southWest.equals(bounds.getSouthWest(), maxMargin) && 1340 this._northEast.equals(bounds.getNorthEast(), maxMargin); 1341 }, 1342 1343 // @method isValid(): Boolean 1344 // Returns `true` if the bounds are properly initialized. 1345 isValid: function () { 1346 return !!(this._southWest && this._northEast); 1347 } 1348 }; 1349 1350 // TODO International date line? 1351 1352 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1353 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1354 1355 // @alternative 1356 // @factory L.latLngBounds(latlngs: LatLng[]) 1357 // 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). 1358 function toLatLngBounds(a, b) { 1359 if (a instanceof LatLngBounds) { 1360 return a; 1361 } 1362 return new LatLngBounds(a, b); 1363 } 1364 1365 /* @class LatLng 1366 * @aka L.LatLng 1367 * 1368 * Represents a geographical point with a certain latitude and longitude. 1369 * 1370 * @example 1371 * 1372 * ``` 1373 * var latlng = L.latLng(50.5, 30.5); 1374 * ``` 1375 * 1376 * 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: 1377 * 1378 * ``` 1379 * map.panTo([50, 30]); 1380 * map.panTo({lon: 30, lat: 50}); 1381 * map.panTo({lat: 50, lng: 30}); 1382 * map.panTo(L.latLng(50, 30)); 1383 * ``` 1384 * 1385 * Note that `LatLng` does not inherit from Leaflet's `Class` object, 1386 * which means new classes can't inherit from it, and new methods 1387 * can't be added to it with the `include` function. 1388 */ 1389 1390 function LatLng(lat, lng, alt) { 1391 if (isNaN(lat) || isNaN(lng)) { 1392 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1393 } 1394 1395 // @property lat: Number 1396 // Latitude in degrees 1397 this.lat = +lat; 1398 1399 // @property lng: Number 1400 // Longitude in degrees 1401 this.lng = +lng; 1402 1403 // @property alt: Number 1404 // Altitude in meters (optional) 1405 if (alt !== undefined) { 1406 this.alt = +alt; 1407 } 1408 } 1409 1410 LatLng.prototype = { 1411 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1412 // 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. 1413 equals: function (obj, maxMargin) { 1414 if (!obj) { return false; } 1415 1416 obj = toLatLng(obj); 1417 1418 var margin = Math.max( 1419 Math.abs(this.lat - obj.lat), 1420 Math.abs(this.lng - obj.lng)); 1421 1422 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1423 }, 1424 1425 // @method toString(): String 1426 // Returns a string representation of the point (for debugging purposes). 1427 toString: function (precision) { 1428 return 'LatLng(' + 1429 formatNum(this.lat, precision) + ', ' + 1430 formatNum(this.lng, precision) + ')'; 1431 }, 1432 1433 // @method distanceTo(otherLatLng: LatLng): Number 1434 // 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). 1435 distanceTo: function (other) { 1436 return Earth.distance(this, toLatLng(other)); 1437 }, 1438 1439 // @method wrap(): LatLng 1440 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1441 wrap: function () { 1442 return Earth.wrapLatLng(this); 1443 }, 1444 1445 // @method toBounds(sizeInMeters: Number): LatLngBounds 1446 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1447 toBounds: function (sizeInMeters) { 1448 var latAccuracy = 180 * sizeInMeters / 40075017, 1449 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1450 1451 return toLatLngBounds( 1452 [this.lat - latAccuracy, this.lng - lngAccuracy], 1453 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1454 }, 1455 1456 clone: function () { 1457 return new LatLng(this.lat, this.lng, this.alt); 1458 } 1459 }; 1460 1461 1462 1463 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1464 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1465 1466 // @alternative 1467 // @factory L.latLng(coords: Array): LatLng 1468 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1469 1470 // @alternative 1471 // @factory L.latLng(coords: Object): LatLng 1472 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1473 1474 function toLatLng(a, b, c) { 1475 if (a instanceof LatLng) { 1476 return a; 1477 } 1478 if (isArray(a) && typeof a[0] !== 'object') { 1479 if (a.length === 3) { 1480 return new LatLng(a[0], a[1], a[2]); 1481 } 1482 if (a.length === 2) { 1483 return new LatLng(a[0], a[1]); 1484 } 1485 return null; 1486 } 1487 if (a === undefined || a === null) { 1488 return a; 1489 } 1490 if (typeof a === 'object' && 'lat' in a) { 1491 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1492 } 1493 if (b === undefined) { 1494 return null; 1495 } 1496 return new LatLng(a, b, c); 1497 } 1498 1499 /* 1500 * @namespace CRS 1501 * @crs L.CRS.Base 1502 * Object that defines coordinate reference systems for projecting 1503 * geographical points into pixel (screen) coordinates and back (and to
1504 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 1505 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). 1506 * 1507 * Leaflet defines the most usual CRSs by default. If you want to use a 1508 * CRS not defined by default, take a look at the 1509 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 1510 * 1511 * Note that the CRS instances do not inherit from Leaflet's `Class` object, 1512 * and can't be instantiated. Also, new classes can't inherit from them, 1513 * and methods can't be added to them with the `include` function. 1514 */ 1515 1516 var CRS = { 1517 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1518 // Projects geographical coordinates into pixel coordinates for a given zoom. 1519 latLngToPoint: function (latlng, zoom) { 1520 var projectedPoint = this.projection.project(latlng), 1521 scale = this.scale(zoom); 1522 1523 return this.transformation._transform(projectedPoint, scale); 1524 }, 1525 1526 // @method pointToLatLng(point: Point, zoom: Number): LatLng 1527 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1528 // zoom into geographical coordinates. 1529 pointToLatLng: function (point, zoom) { 1530 var scale = this.scale(zoom), 1531 untransformedPoint = this.transformation.untransform(point, scale); 1532 1533 return this.projection.unproject(untransformedPoint); 1534 }, 1535 1536 // @method project(latlng: LatLng): Point 1537 // Projects geographical coordinates into coordinates in units accepted for 1538 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1539 project: function (latlng) { 1540 return this.projection.project(latlng); 1541 }, 1542 1543 // @method unproject(point: Point): LatLng 1544 // Given a projected coordinate returns the corresponding LatLng. 1545 // The inverse of `project`. 1546 unproject: function (point) { 1547 return this.projection.unproject(point); 1548 }, 1549 1550 // @method scale(zoom: Number): Number 1551 // Returns the scale used when transforming projected coordinates into 1552 // pixel coordinates for a particular zoom. For example, it returns 1553 // `256 * 2^zoom` for Mercator-based CRS. 1554 scale: function (zoom) { 1555 return 256 * Math.pow(2, zoom); 1556 }, 1557 1558 // @method zoom(scale: Number): Number 1559 // Inverse of `scale()`, returns the zoom level corresponding to a scale 1560 // factor of `scale`. 1561 zoom: function (scale) { 1562 return Math.log(scale / 256) / Math.LN2; 1563 }, 1564 1565 // @method getProjectedBounds(zoom: Number): Bounds 1566 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1567 getProjectedBounds: function (zoom) { 1568 if (this.infinite) { return null; } 1569 1570 var b = this.projection.bounds, 1571 s = this.scale(zoom), 1572 min = this.transformation.transform(b.min, s), 1573 max = this.transformation.transform(b.max, s); 1574 1575 return new Bounds(min, max); 1576 }, 1577 1578 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1579 // Returns the distance between two geographical coordinates. 1580 1581 // @property code: String 1582 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1583 // 1584 // @property wrapLng: Number[] 1585 // An array of two numbers defining whether the longitude (horizontal) coordinate 1586 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1587 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1588 // 1589 // @property wrapLat: Number[] 1590 // Like `wrapLng`, but for the latitude (vertical) axis. 1591 1592 // wrapLng: [min, max], 1593 // wrapLat: [min, max], 1594 1595 // @property infinite: Boolean 1596 // If true, the coordinate space will be unbounded (infinite in both axes) 1597 infinite: false, 1598 1599 // @method wrapLatLng(latlng: LatLng): LatLng 1600 // Returns a `LatLng` where lat and lng has been wrapped according to the 1601 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1602 wrapLatLng: function (latlng) { 1603 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 1604 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 1605 alt = latlng.alt; 1606
vendor: 13,927 bytes, lines 1607-1997
1607 return new LatLng(lat, lng, alt); 1608 }, 1609 1610 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1611 // Returns a `LatLngBounds` with the same size as the given one, ensuring 1612 // that its center is within the CRS's bounds. 1613 // Only accepts actual `L.LatLngBounds` instances, not arrays. 1614 wrapLatLngBounds: function (bounds) { 1615 var center = bounds.getCenter(), 1616 newCenter = this.wrapLatLng(center), 1617 latShift = center.lat - newCenter.lat, 1618 lngShift = center.lng - newCenter.lng; 1619 1620 if (latShift === 0 && lngShift === 0) { 1621 return bounds; 1622 } 1623 1624 var sw = bounds.getSouthWest(), 1625 ne = bounds.getNorthEast(), 1626 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), 1627 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1628 1629 return new LatLngBounds(newSw, newNe); 1630 } 1631 }; 1632 1633 /* 1634 * @namespace CRS 1635 * @crs L.CRS.Earth 1636 * 1637 * Serves as the base for CRS that are global such that they cover the earth. 1638 * Can only be used as the base for other CRS and cannot be used directly, 1639 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 1640 * meters. 1641 */ 1642 1643 var Earth = extend({}, CRS, { 1644 wrapLng: [-180, 180], 1645 1646 // Mean Earth Radius, as recommended for use by 1647 // the International Union of Geodesy and Geophysics, 1648 // see http://rosettacode.org/wiki/Haversine_formula 1649 R: 6371000, 1650 1651 // distance between two geographical points using spherical law of cosines approximation 1652 distance: function (latlng1, latlng2) { 1653 var rad = Math.PI / 180, 1654 lat1 = latlng1.lat * rad, 1655 lat2 = latlng2.lat * rad, 1656 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), 1657 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), 1658 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, 1659 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1660 return this.R * c; 1661 } 1662 }); 1663 1664 /* 1665 * @namespace Projection 1666 * @projection L.Projection.SphericalMercator 1667 * 1668 * Spherical Mercator projection â the most common projection for online maps, 1669 * used by almost all free and commercial tile providers. Assumes that Earth is 1670 * a sphere. Used by the `EPSG:3857` CRS. 1671 */ 1672 1673 var earthRadius = 6378137; 1674 1675 var SphericalMercator = { 1676 1677 R: earthRadius, 1678 MAX_LATITUDE: 85.0511287798, 1679 1680 project: function (latlng) { 1681 var d = Math.PI / 180, 1682 max = this.MAX_LATITUDE, 1683 lat = Math.max(Math.min(max, latlng.lat), -max), 1684 sin = Math.sin(lat * d); 1685 1686 return new Point( 1687 this.R * latlng.lng * d, 1688 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 1689 }, 1690 1691 unproject: function (point) { 1692 var d = 180 / Math.PI; 1693 1694 return new LatLng( 1695 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 1696 point.x * d / this.R); 1697 }, 1698 1699 bounds: (function () { 1700 var d = earthRadius * Math.PI; 1701 return new Bounds([-d, -d], [d, d]); 1702 })() 1703 }; 1704 1705 /* 1706 * @class Transformation 1707 * @aka L.Transformation 1708 * 1709 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1710 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1711 * the reverse. Used by Leaflet in its projections code. 1712 * 1713 * @example 1714 * 1715 * ```js 1716 * var transformation = L.transformation(2, 5, -1, 10), 1717 * p = L.point(1, 2), 1718 * p2 = transformation.transform(p), // L.point(7, 8) 1719 * p3 = transformation.untransform(p2); // L.point(1, 2) 1720 * ``` 1721 */ 1722 1723 1724 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1725 // Creates a `Transformation` object with the given coefficients. 1726 function Transformation(a, b, c, d) { 1727 if (isArray(a)) { 1728 // use array properties 1729 this._a = a[0]; 1730 this._b = a[1]; 1731 this._c = a[2]; 1732 this._d = a[3]; 1733 return; 1734 } 1735 this._a = a; 1736 this._b = b; 1737 this._c = c; 1738 this._d = d; 1739 } 1740 1741 Transformation.prototype = { 1742 // @method transform(point: Point, scale?: Number): Point 1743 // Returns a transformed point, optionally multiplied by the given scale. 1744 // Only accepts actual `L.Point` instances, not arrays. 1745 transform: function (point, scale) { // (Point, Number) -> Point 1746 return this._transform(point.clone(), scale); 1747 }, 1748 1749 // destructive transform (faster) 1750 _transform: function (point, scale) { 1751 scale = scale || 1; 1752 point.x = scale * (this._a * point.x + this._b); 1753 point.y = scale * (this._c * point.y + this._d); 1754 return point; 1755 }, 1756 1757 // @method untransform(point: Point, scale?: Number): Point 1758 // Returns the reverse transformation of the given point, optionally divided 1759 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1760 untransform: function (point, scale) { 1761 scale = scale || 1; 1762 return new Point( 1763 (point.x / scale - this._b) / this._a, 1764 (point.y / scale - this._d) / this._c); 1765 } 1766 }; 1767 1768 // factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1769 1770 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1771 // Instantiates a Transformation object with the given coefficients. 1772 1773 // @alternative 1774 // @factory L.transformation(coefficients: Array): Transformation 1775 // Expects an coefficients array of the form 1776 // `[a: Number, b: Number, c: Number, d: Number]`. 1777 1778 function toTransformation(a, b, c, d) { 1779 return new Transformation(a, b, c, d); 1780 } 1781 1782 /* 1783 * @namespace CRS 1784 * @crs L.CRS.EPSG3857 1785 * 1786 * The most common CRS for online maps, used by almost all free and commercial 1787 * tile providers. Uses Spherical Mercator projection. Set in by default in 1788 * Map's `crs` option. 1789 */ 1790 1791 var EPSG3857 = extend({}, Earth, { 1792 code: 'EPSG:3857', 1793 projection: SphericalMercator, 1794 1795 transformation: (function () { 1796 var scale = 0.5 / (Math.PI * SphericalMercator.R); 1797 return toTransformation(scale, 0.5, -scale, 0.5); 1798 }()) 1799 }); 1800 1801 var EPSG900913 = extend({}, EPSG3857, { 1802 code: 'EPSG:900913' 1803 }); 1804 1805 // @namespace SVG; @section 1806 // There are several static functions which can be called without instantiating L.SVG: 1807 1808 // @function create(name: String): SVGElement 1809 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 1810 // corresponding to the class name passed. For example, using 'line' will return 1811 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 1812 function svgCreate(name) { 1813 return document.createElementNS('http://www.w3.org/2000/svg', name); 1814 } 1815 1816 // @function pointsToPath(rings: Point[], closed: Boolean): String 1817 // Generates a SVG path string for multiple rings, with each ring turning 1818 // into "M..L..L.." instructions 1819 function pointsToPath(rings, closed) { 1820 var str = '', 1821 i, j, len, len2, points, p; 1822 1823 for (i = 0, len = rings.length; i < len; i++) { 1824 points = rings[i]; 1825 1826 for (j = 0, len2 = points.length; j < len2; j++) { 1827 p = points[j]; 1828 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 1829 } 1830 1831 // closes the ring for polygons; "x" is VML syntax 1832 str += closed ? (svg$1 ? 'z' : 'x') : ''; 1833 } 1834 1835 // SVG complains about empty path strings 1836 return str || 'M0 0'; 1837 } 1838 1839 /* 1840 * @namespace Browser 1841 * @aka L.Browser 1842 * 1843 * A namespace with static properties for browser/feature detection used by Leaflet internally. 1844 * 1845 * @example 1846 * 1847 * ```js 1848 * if (L.Browser.ielt9) { 1849 * alert('Upgrade your browser, dude!'); 1850 * } 1851 * ``` 1852 */ 1853 1854 var style = document.documentElement.style; 1855 1856 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 1857 var ie = 'ActiveXObject' in window; 1858 1859 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 1860 var ielt9 = ie && !document.addEventListener; 1861 1862 // @property edge: Boolean; `true` for the Edge web browser. 1863 var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 1864 1865 // @property webkit: Boolean; 1866 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 1867 var webkit = userAgentContains('webkit'); 1868 1869 // @property android: Boolean 1870 // **Deprecated.** `true` for any browser running on an Android platform. 1871 var android = userAgentContains('android'); 1872 1873 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3. 1874 var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 1875 1876 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 1877 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 1878 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome) 1879 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 1880 1881 // @property opera: Boolean; `true` for the Opera browser 1882 var opera = !!window.opera; 1883 1884 // @property chrome: Boolean; `true` for the Chrome browser. 1885 var chrome = !edge && userAgentContains('chrome'); 1886 1887 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 1888 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 1889 1890 // @property safari: Boolean; `true` for the Safari browser. 1891 var safari = !chrome && userAgentContains('safari'); 1892 1893 var phantom = userAgentContains('phantom'); 1894 1895 // @property opera12: Boolean 1896 // `true` for the Opera browser supporting CSS transforms (version 12 or later). 1897 var opera12 = 'OTransition' in style; 1898 1899 // @property win: Boolean; `true` when the browser is running in a Windows platform 1900 var win = navigator.platform.indexOf('Win') === 0; 1901 1902 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. 1903 var ie3d = ie && ('transition' in style); 1904 1905 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 1906 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 1907 1908 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 1909 var gecko3d = 'MozPerspective' in style; 1910 1911 // @property any3d: Boolean 1912 // `true` for all browsers supporting CSS transforms. 1913 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1914 1915 // @property mobile: Boolean; `true` for all browsers running in a mobile device. 1916 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 1917 1918 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 1919 var mobileWebkit = mobile && webkit; 1920 1921 // @property mobileWebkit3d: Boolean 1922 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 1923 var mobileWebkit3d = mobile && webkit3d; 1924 1925 // @property msPointer: Boolean 1926 // `true` for browsers implementing the Microsoft touch events model (notably IE10). 1927 var msPointer = !window.PointerEvent && window.MSPointerEvent; 1928 1929 // @property pointer: Boolean 1930 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 1931 var pointer = !!(window.PointerEvent || msPointer); 1932 1933 // @property touchNative: Boolean 1934 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 1935 // **This does not necessarily mean** that the browser is running in a computer with 1936 // a touchscreen, it only means that the browser is capable of understanding 1937 // touch events. 1938 var touchNative = 'ontouchstart' in window || !!window.TouchEvent; 1939 1940 // @property touch: Boolean 1941 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events. 1942 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners. 1943 var touch = !window.L_NO_TOUCH && (touchNative || pointer); 1944 1945 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 1946 var mobileOpera = mobile && opera; 1947 1948 // @property mobileGecko: Boolean 1949 // `true` for gecko-based browsers running in a mobile device. 1950 var mobileGecko = mobile && gecko; 1951 1952 // @property retina: Boolean 1953 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 1954 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 1955 1956 // @property passiveEvents: Boolean 1957 // `true` for browsers that support passive events. 1958 var passiveEvents = (function () { 1959 var supportsPassiveOption = false; 1960 try { 1961 var opts = Object.defineProperty({}, 'passive', { 1962 get: function () { // eslint-disable-line getter-return 1963 supportsPassiveOption = true; 1964 } 1965 }); 1966 window.addEventListener('testPassiveEventSupport', falseFn, opts); 1967 window.removeEventListener('testPassiveEventSupport', falseFn, opts); 1968 } catch (e) { 1969 // Errors can safely be ignored since this is only a browser support test. 1970 } 1971 return supportsPassiveOption; 1972 }()); 1973 1974 // @property canvas: Boolean 1975 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 1976 var canvas$1 = (function () { 1977 return !!document.createElement('canvas').getContext; 1978 }()); 1979 1980 // @property svg: Boolean 1981 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 1982 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect); 1983 1984 // @property vml: Boolean 1985 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 1986 var vml = !svg$1 && (function () { 1987 try { 1988 var div = document.createElement('div'); 1989 div.innerHTML = '<v:shape adj="1"/>'; 1990 1991 var shape = div.firstChild; 1992 shape.style.behavior = 'url(#default#VML)'; 1993 1994 return shape && (typeof shape.adj === 'object'); 1995 1996 } catch (e) { 1997 return false;
1998 } 1999 }()); 2000 2001 2002 function userAgentContains(str) { 2003 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 2004 } 2005 2006 var Browser = { 2007 __proto__: null, 2008 ie: ie, 2009 ielt9: ielt9, 2010 edge: edge, 2011 webkit: webkit, 2012 android: android, 2013 android23: android23, 2014 androidStock: androidStock, 2015 opera: opera, 2016 chrome: chrome, 2017 gecko: gecko, 2018 safari: safari, 2019 phantom: phantom, 2020 opera12: opera12, 2021 win: win, 2022 ie3d: ie3d, 2023 webkit3d: webkit3d, 2024 gecko3d: gecko3d, 2025 any3d: any3d, 2026 mobile: mobile, 2027 mobileWebkit: mobileWebkit, 2028 mobileWebkit3d: mobileWebkit3d, 2029 msPointer: msPointer, 2030 pointer: pointer, 2031 touchNative: touchNative, 2032 touch: touch, 2033 mobileOpera: mobileOpera, 2034 mobileGecko: mobileGecko, 2035 retina: retina, 2036 passiveEvents: passiveEvents, 2037 canvas: canvas$1, 2038 svg: svg$1, 2039 vml: vml 2040 }; 2041 2042 /* 2043 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 2044 */ 2045 2046 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown'; 2047 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'; 2048 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'; 2049 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'; 2050 var pEvent = { 2051 touchstart : POINTER_DOWN, 2052 touchmove : POINTER_MOVE, 2053 touchend : POINTER_UP, 2054 touchcancel : POINTER_CANCEL 2055 }; 2056 var handle = { 2057 touchstart : _onPointerStart, 2058 touchmove : _handlePointer, 2059 touchend : _handlePointer, 2060 touchcancel : _handlePointer 2061 }; 2062 var _pointers = {}; 2063 var _pointerDocListener = false;
vendor: 154,267 bytes, lines 2064-7013
2064 2065 // Provides a touch events wrapper for (ms)pointer events. 2066 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2067 2068 function addPointerListener(obj, type, handler) { 2069 if (type === 'touchstart') { 2070 _addPointerDocListener(); 2071 } 2072 if (!handle[type]) { 2073 console.warn('wrong event specified:', type); 2074 return L.Util.falseFn; 2075 } 2076 handler = handle[type].bind(this, handler); 2077 obj.addEventListener(pEvent[type], handler, false); 2078 return handler; 2079 } 2080 2081 function removePointerListener(obj, type, handler) { 2082 if (!pEvent[type]) { 2083 console.warn('wrong event specified:', type); 2084 return; 2085 } 2086 obj.removeEventListener(pEvent[type], handler, false); 2087 } 2088 2089 function _globalPointerDown(e) { 2090 _pointers[e.pointerId] = e; 2091 } 2092 2093 function _globalPointerMove(e) { 2094 if (_pointers[e.pointerId]) { 2095 _pointers[e.pointerId] = e; 2096 } 2097 } 2098 2099 function _globalPointerUp(e) { 2100 delete _pointers[e.pointerId]; 2101 } 2102 2103 function _addPointerDocListener() { 2104 // need to keep track of what pointers and how many are active to provide e.touches emulation 2105 if (!_pointerDocListener) { 2106 // we listen document as any drags that end by moving the touch off the screen get fired there 2107 document.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2108 document.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2109 document.addEventListener(POINTER_UP, _globalPointerUp, true); 2110 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2111 2112 _pointerDocListener = true; 2113 } 2114 } 2115 2116 function _handlePointer(handler, e) { 2117 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; } 2118 2119 e.touches = []; 2120 for (var i in _pointers) { 2121 e.touches.push(_pointers[i]); 2122 } 2123 e.changedTouches = [e]; 2124 2125 handler(e); 2126 } 2127 2128 function _onPointerStart(handler, e) { 2129 // IE10 specific: MsTouch needs preventDefault. See #2000 2130 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) { 2131 preventDefault(e); 2132 } 2133 _handlePointer(handler, e); 2134 } 2135 2136 /* 2137 * Extends the event handling code with double tap support for mobile browsers. 2138 * 2139 * Note: currently most browsers fire native dblclick, with only a few exceptions 2140 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386) 2141 */ 2142 2143 function makeDblclick(event) { 2144 // in modern browsers `type` cannot be just overridden: 2145 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only 2146 var newEvent = {}, 2147 prop, i; 2148 for (i in event) { 2149 prop = event[i]; 2150 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop; 2151 } 2152 event = newEvent; 2153 newEvent.type = 'dblclick'; 2154 newEvent.detail = 2; 2155 newEvent.isTrusted = false; 2156 newEvent._simulated = true; // for debug purposes 2157 return newEvent; 2158 } 2159 2160 var delay = 200; 2161 function addDoubleTapListener(obj, handler) { 2162 // Most browsers handle double tap natively 2163 obj.addEventListener('dblclick', handler); 2164 2165 // On some platforms the browser doesn't fire native dblclicks for touch events. 2166 // It seems that in all such cases `detail` property of `click` event is always `1`. 2167 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed. 2168 var last = 0, 2169 detail; 2170 function simDblclick(e) { 2171 if (e.detail !== 1) { 2172 detail = e.detail; // keep in sync to avoid false dblclick in some cases 2173 return; 2174 } 2175 2176 if (e.pointerType === 'mouse' || 2177 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) { 2178 2179 return; 2180 } 2181 2182 var now = Date.now(); 2183 if (now - last <= delay) { 2184 detail++; 2185 if (detail === 2) { 2186 handler(makeDblclick(e)); 2187 } 2188 } else { 2189 detail = 1; 2190 } 2191 last = now; 2192 } 2193 2194 obj.addEventListener('click', simDblclick); 2195 2196 return { 2197 dblclick: handler, 2198 simDblclick: simDblclick 2199 }; 2200 } 2201 2202 function removeDoubleTapListener(obj, handlers) { 2203 obj.removeEventListener('dblclick', handlers.dblclick); 2204 obj.removeEventListener('click', handlers.simDblclick); 2205 } 2206 2207 /* 2208 * @namespace DomUtil 2209 * 2210 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2211 * tree, used by Leaflet internally. 2212 * 2213 * Most functions expecting or returning a `HTMLElement` also work for 2214 * SVG elements. The only difference is that classes refer to CSS classes 2215 * in HTML and SVG classes in SVG. 2216 */ 2217 2218 2219 // @property TRANSFORM: String 2220 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2221 var TRANSFORM = testProp( 2222 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2223 2224 // webkitTransition comes first because some browser versions that drop vendor prefix don't do 2225 // the same for the transitionend event, in particular the Android 4.1 stock browser 2226 2227 // @property TRANSITION: String 2228 // Vendor-prefixed transition style name. 2229 var TRANSITION = testProp( 2230 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2231 2232 // @property TRANSITION_END: String 2233 // Vendor-prefixed transitionend event name. 2234 var TRANSITION_END = 2235 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2236 2237 2238 // @function get(id: String|HTMLElement): HTMLElement 2239 // Returns an element given its DOM id, or returns the element itself 2240 // if it was passed directly. 2241 function get(id) { 2242 return typeof id === 'string' ? document.getElementById(id) : id; 2243 } 2244 2245 // @function getStyle(el: HTMLElement, styleAttrib: String): String 2246 // Returns the value for a certain style attribute on an element, 2247 // including computed values or values set through CSS. 2248 function getStyle(el, style) { 2249 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2250 2251 if ((!value || value === 'auto') && document.defaultView) { 2252 var css = document.defaultView.getComputedStyle(el, null); 2253 value = css ? css[style] : null; 2254 } 2255 return value === 'auto' ? null : value; 2256 } 2257 2258 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2259 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2260 function create$1(tagName, className, container) { 2261 var el = document.createElement(tagName); 2262 el.className = className || ''; 2263 2264 if (container) { 2265 container.appendChild(el); 2266 } 2267 return el; 2268 } 2269 2270 // @function remove(el: HTMLElement) 2271 // Removes `el` from its parent element 2272 function remove(el) { 2273 var parent = el.parentNode; 2274 if (parent) { 2275 parent.removeChild(el); 2276 } 2277 } 2278 2279 // @function empty(el: HTMLElement) 2280 // Removes all of `el`'s children elements from `el` 2281 function empty(el) { 2282 while (el.firstChild) { 2283 el.removeChild(el.firstChild); 2284 } 2285 } 2286 2287 // @function toFront(el: HTMLElement) 2288 // Makes `el` the last child of its parent, so it renders in front of the other children. 2289 function toFront(el) { 2290 var parent = el.parentNode; 2291 if (parent && parent.lastChild !== el) { 2292 parent.appendChild(el); 2293 } 2294 } 2295 2296 // @function toBack(el: HTMLElement) 2297 // Makes `el` the first child of its parent, so it renders behind the other children. 2298 function toBack(el) { 2299 var parent = el.parentNode; 2300 if (parent && parent.firstChild !== el) { 2301 parent.insertBefore(el, parent.firstChild); 2302 } 2303 } 2304 2305 // @function hasClass(el: HTMLElement, name: String): Boolean 2306 // Returns `true` if the element's class attribute contains `name`. 2307 function hasClass(el, name) { 2308 if (el.classList !== undefined) { 2309 return el.classList.contains(name); 2310 } 2311 var className = getClass(el); 2312 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2313 } 2314 2315 // @function addClass(el: HTMLElement, name: String) 2316 // Adds `name` to the element's class attribute. 2317 function addClass(el, name) { 2318 if (el.classList !== undefined) { 2319 var classes = splitWords(name); 2320 for (var i = 0, len = classes.length; i < len; i++) { 2321 el.classList.add(classes[i]); 2322 } 2323 } else if (!hasClass(el, name)) { 2324 var className = getClass(el); 2325 setClass(el, (className ? className + ' ' : '') + name); 2326 } 2327 } 2328 2329 // @function removeClass(el: HTMLElement, name: String) 2330 // Removes `name` from the element's class attribute. 2331 function removeClass(el, name) { 2332 if (el.classList !== undefined) { 2333 el.classList.remove(name); 2334 } else { 2335 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2336 } 2337 } 2338 2339 // @function setClass(el: HTMLElement, name: String) 2340 // Sets the element's class. 2341 function setClass(el, name) { 2342 if (el.className.baseVal === undefined) { 2343 el.className = name; 2344 } else { 2345 // in case of SVG element 2346 el.className.baseVal = name; 2347 } 2348 } 2349 2350 // @function getClass(el: HTMLElement): String 2351 // Returns the element's class. 2352 function getClass(el) { 2353 // Check if the element is an SVGElementInstance and use the correspondingElement instead 2354 // (Required for linked SVG elements in IE11.) 2355 if (el.correspondingElement) { 2356 el = el.correspondingElement; 2357 } 2358 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2359 } 2360 2361 // @function setOpacity(el: HTMLElement, opacity: Number) 2362 // Set the opacity of an element (including old IE support). 2363 // `opacity` must be a number from `0` to `1`. 2364 function setOpacity(el, value) { 2365 if ('opacity' in el.style) { 2366 el.style.opacity = value; 2367 } else if ('filter' in el.style) { 2368 _setOpacityIE(el, value); 2369 } 2370 } 2371 2372 function _setOpacityIE(el, value) { 2373 var filter = false, 2374 filterName = 'DXImageTransform.Microsoft.Alpha'; 2375 2376 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2377 try { 2378 filter = el.filters.item(filterName); 2379 } catch (e) { 2380 // don't set opacity to 1 if we haven't already set an opacity, 2381 // it isn't needed and breaks transparent pngs. 2382 if (value === 1) { return; } 2383 } 2384 2385 value = Math.round(value * 100); 2386 2387 if (filter) { 2388 filter.Enabled = (value !== 100); 2389 filter.Opacity = value; 2390 } else { 2391 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2392 } 2393 } 2394 2395 // @function testProp(props: String[]): String|false 2396 // Goes through the array of style names and returns the first name 2397 // that is a valid style name for an element. If no such name is found, 2398 // it returns false. Useful for vendor-prefixed styles like `transform`. 2399 function testProp(props) { 2400 var style = document.documentElement.style; 2401 2402 for (var i = 0; i < props.length; i++) { 2403 if (props[i] in style) { 2404 return props[i]; 2405 } 2406 } 2407 return false; 2408 } 2409 2410 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2411 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2412 // and optionally scaled by `scale`. Does not have an effect if the 2413 // browser doesn't support 3D CSS transforms. 2414 function setTransform(el, offset, scale) { 2415 var pos = offset || new Point(0, 0); 2416 2417 el.style[TRANSFORM] = 2418 (ie3d ? 2419 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2420 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2421 (scale ? ' scale(' + scale + ')' : ''); 2422 } 2423 2424 // @function setPosition(el: HTMLElement, position: Point) 2425 // Sets the position of `el` to coordinates specified by `position`, 2426 // using CSS translate or top/left positioning depending on the browser 2427 // (used by Leaflet internally to position its layers). 2428 function setPosition(el, point) { 2429 2430 /*eslint-disable */ 2431 el._leaflet_pos = point; 2432 /* eslint-enable */ 2433 2434 if (any3d) { 2435 setTransform(el, point); 2436 } else { 2437 el.style.left = point.x + 'px'; 2438 el.style.top = point.y + 'px'; 2439 } 2440 } 2441 2442 // @function getPosition(el: HTMLElement): Point 2443 // Returns the coordinates of an element previously positioned with setPosition. 2444 function getPosition(el) { 2445 // this method is only used for elements previously positioned using setPosition, 2446 // so it's safe to cache the position for performance 2447 2448 return el._leaflet_pos || new Point(0, 0); 2449 } 2450 2451 // @function disableTextSelection() 2452 // Prevents the user from generating `selectstart` DOM events, usually generated 2453 // when the user drags the mouse through a page with text. Used internally 2454 // by Leaflet to override the behaviour of any click-and-drag interaction on 2455 // the map. Affects drag interactions on the whole document. 2456 2457 // @function enableTextSelection() 2458 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2459 var disableTextSelection; 2460 var enableTextSelection; 2461 var _userSelect; 2462 if ('onselectstart' in document) { 2463 disableTextSelection = function () { 2464 on(window, 'selectstart', preventDefault); 2465 }; 2466 enableTextSelection = function () { 2467 off(window, 'selectstart', preventDefault); 2468 }; 2469 } else { 2470 var userSelectProperty = testProp( 2471 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2472 2473 disableTextSelection = function () { 2474 if (userSelectProperty) { 2475 var style = document.documentElement.style; 2476 _userSelect = style[userSelectProperty]; 2477 style[userSelectProperty] = 'none'; 2478 } 2479 }; 2480 enableTextSelection = function () { 2481 if (userSelectProperty) { 2482 document.documentElement.style[userSelectProperty] = _userSelect; 2483 _userSelect = undefined; 2484 } 2485 }; 2486 } 2487 2488 // @function disableImageDrag() 2489 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2490 // for `dragstart` DOM events, usually generated when the user drags an image. 2491 function disableImageDrag() { 2492 on(window, 'dragstart', preventDefault); 2493 } 2494 2495 // @function enableImageDrag() 2496 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2497 function enableImageDrag() { 2498 off(window, 'dragstart', preventDefault); 2499 } 2500 2501 var _outlineElement, _outlineStyle; 2502 // @function preventOutline(el: HTMLElement) 2503 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2504 // of the element `el` invisible. Used internally by Leaflet to prevent 2505 // focusable elements from displaying an outline when the user performs a 2506 // drag interaction on them. 2507 function preventOutline(element) { 2508 while (element.tabIndex === -1) { 2509 element = element.parentNode; 2510 } 2511 if (!element.style) { return; } 2512 restoreOutline(); 2513 _outlineElement = element; 2514 _outlineStyle = element.style.outline; 2515 element.style.outline = 'none'; 2516 on(window, 'keydown', restoreOutline); 2517 } 2518 2519 // @function restoreOutline() 2520 // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2521 function restoreOutline() { 2522 if (!_outlineElement) { return; } 2523 _outlineElement.style.outline = _outlineStyle; 2524 _outlineElement = undefined; 2525 _outlineStyle = undefined; 2526 off(window, 'keydown', restoreOutline); 2527 } 2528 2529 // @function getSizedParentNode(el: HTMLElement): HTMLElement 2530 // Finds the closest parent node which size (width and height) is not null. 2531 function getSizedParentNode(element) { 2532 do { 2533 element = element.parentNode; 2534 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2535 return element; 2536 } 2537 2538 // @function getScale(el: HTMLElement): Object 2539 // Computes the CSS scale currently applied on the element. 2540 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2541 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2542 function getScale(element) { 2543 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2544 2545 return { 2546 x: rect.width / element.offsetWidth || 1, 2547 y: rect.height / element.offsetHeight || 1, 2548 boundingClientRect: rect 2549 }; 2550 } 2551 2552 var DomUtil = { 2553 __proto__: null, 2554 TRANSFORM: TRANSFORM, 2555 TRANSITION: TRANSITION, 2556 TRANSITION_END: TRANSITION_END, 2557 get: get, 2558 getStyle: getStyle, 2559 create: create$1, 2560 remove: remove, 2561 empty: empty, 2562 toFront: toFront, 2563 toBack: toBack, 2564 hasClass: hasClass, 2565 addClass: addClass, 2566 removeClass: removeClass, 2567 setClass: setClass, 2568 getClass: getClass, 2569 setOpacity: setOpacity, 2570 testProp: testProp, 2571 setTransform: setTransform, 2572 setPosition: setPosition, 2573 getPosition: getPosition, 2574 get disableTextSelection () { return disableTextSelection; }, 2575 get enableTextSelection () { return enableTextSelection; }, 2576 disableImageDrag: disableImageDrag, 2577 enableImageDrag: enableImageDrag, 2578 preventOutline: preventOutline, 2579 restoreOutline: restoreOutline, 2580 getSizedParentNode: getSizedParentNode, 2581 getScale: getScale 2582 }; 2583 2584 /* 2585 * @namespace DomEvent 2586 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2587 */ 2588 2589 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2590 2591 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2592 // Adds a listener function (`fn`) to a particular DOM event type of the 2593 // element `el`. You can optionally specify the context of the listener 2594 // (object the `this` keyword will point to). You can also pass several 2595 // space-separated types (e.g. `'click dblclick'`). 2596 2597 // @alternative 2598 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2599 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2600 function on(obj, types, fn, context) { 2601 2602 if (types && typeof types === 'object') { 2603 for (var type in types) { 2604 addOne(obj, type, types[type], fn); 2605 } 2606 } else { 2607 types = splitWords(types); 2608 2609 for (var i = 0, len = types.length; i < len; i++) { 2610 addOne(obj, types[i], fn, context); 2611 } 2612 } 2613 2614 return this; 2615 } 2616 2617 var eventsKey = '_leaflet_events'; 2618 2619 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2620 // Removes a previously added listener function. 2621 // Note that if you passed a custom context to on, you must pass the same 2622 // context to `off` in order to remove the listener. 2623 2624 // @alternative 2625 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2626 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2627 2628 // @alternative 2629 // @function off(el: HTMLElement, types: String): this 2630 // Removes all previously added listeners of given types. 2631 2632 // @alternative 2633 // @function off(el: HTMLElement): this 2634 // Removes all previously added listeners from given HTMLElement 2635 function off(obj, types, fn, context) { 2636 2637 if (arguments.length === 1) { 2638 batchRemove(obj); 2639 delete obj[eventsKey]; 2640 2641 } else if (types && typeof types === 'object') { 2642 for (var type in types) { 2643 removeOne(obj, type, types[type], fn); 2644 } 2645 2646 } else { 2647 types = splitWords(types); 2648 2649 if (arguments.length === 2) { 2650 batchRemove(obj, function (type) { 2651 return indexOf(types, type) !== -1; 2652 }); 2653 } else { 2654 for (var i = 0, len = types.length; i < len; i++) { 2655 removeOne(obj, types[i], fn, context); 2656 } 2657 } 2658 } 2659 2660 return this; 2661 } 2662 2663 function batchRemove(obj, filterFn) { 2664 for (var id in obj[eventsKey]) { 2665 var type = id.split(/\d/)[0]; 2666 if (!filterFn || filterFn(type)) { 2667 removeOne(obj, type, null, null, id); 2668 } 2669 } 2670 } 2671 2672 var mouseSubst = { 2673 mouseenter: 'mouseover', 2674 mouseleave: 'mouseout', 2675 wheel: !('onwheel' in window) && 'mousewheel' 2676 }; 2677 2678 function addOne(obj, type, fn, context) { 2679 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2680 2681 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 2682 2683 var handler = function (e) { 2684 return fn.call(context || obj, e || window.event); 2685 }; 2686 2687 var originalHandler = handler; 2688 2689 if (!touchNative && pointer && type.indexOf('touch') === 0) { 2690 // Needs DomEvent.Pointer.js 2691 handler = addPointerListener(obj, type, handler); 2692 2693 } else if (touch && (type === 'dblclick')) { 2694 handler = addDoubleTapListener(obj, handler); 2695 2696 } else if ('addEventListener' in obj) { 2697 2698 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') { 2699 obj.addEventListener(mouseSubst[type] || type, handler, passiveEvents ? {passive: false} : false); 2700 2701 } else if (type === 'mouseenter' || type === 'mouseleave') { 2702 handler = function (e) { 2703 e = e || window.event; 2704 if (isExternalTarget(obj, e)) { 2705 originalHandler(e); 2706 } 2707 }; 2708 obj.addEventListener(mouseSubst[type], handler, false); 2709 2710 } else { 2711 obj.addEventListener(type, originalHandler, false); 2712 } 2713 2714 } else { 2715 obj.attachEvent('on' + type, handler); 2716 } 2717 2718 obj[eventsKey] = obj[eventsKey] || {}; 2719 obj[eventsKey][id] = handler; 2720 } 2721 2722 function removeOne(obj, type, fn, context, id) { 2723 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2724 var handler = obj[eventsKey] && obj[eventsKey][id]; 2725 2726 if (!handler) { return this; } 2727 2728 if (!touchNative && pointer && type.indexOf('touch') === 0) { 2729 removePointerListener(obj, type, handler); 2730 2731 } else if (touch && (type === 'dblclick')) { 2732 removeDoubleTapListener(obj, handler); 2733 2734 } else if ('removeEventListener' in obj) { 2735 2736 obj.removeEventListener(mouseSubst[type] || type, handler, false); 2737 2738 } else { 2739 obj.detachEvent('on' + type, handler); 2740 } 2741 2742 obj[eventsKey][id] = null; 2743 } 2744 2745 // @function stopPropagation(ev: DOMEvent): this 2746 // Stop the given event from propagation to parent elements. Used inside the listener functions: 2747 // ```js 2748 // L.DomEvent.on(div, 'click', function (ev) { 2749 // L.DomEvent.stopPropagation(ev); 2750 // }); 2751 // ``` 2752 function stopPropagation(e) { 2753 2754 if (e.stopPropagation) { 2755 e.stopPropagation(); 2756 } else if (e.originalEvent) { // In case of Leaflet event. 2757 e.originalEvent._stopped = true; 2758 } else { 2759 e.cancelBubble = true; 2760 } 2761 2762 return this; 2763 } 2764 2765 // @function disableScrollPropagation(el: HTMLElement): this 2766 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants). 2767 function disableScrollPropagation(el) { 2768 addOne(el, 'wheel', stopPropagation); 2769 return this; 2770 } 2771 2772 // @function disableClickPropagation(el: HTMLElement): this 2773 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`, 2774 // `'mousedown'` and `'touchstart'` events (plus browser variants). 2775 function disableClickPropagation(el) { 2776 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation); 2777 el['_leaflet_disable_click'] = true; 2778 return this; 2779 } 2780 2781 // @function preventDefault(ev: DOMEvent): this 2782 // Prevents the default action of the DOM Event `ev` from happening (such as 2783 // following a link in the href of the a element, or doing a POST request 2784 // with page reload when a `<form>` is submitted). 2785 // Use it inside listener functions. 2786 function preventDefault(e) { 2787 if (e.preventDefault) { 2788 e.preventDefault(); 2789 } else { 2790 e.returnValue = false; 2791 } 2792 return this; 2793 } 2794 2795 // @function stop(ev: DOMEvent): this 2796 // Does `stopPropagation` and `preventDefault` at the same time. 2797 function stop(e) { 2798 preventDefault(e); 2799 stopPropagation(e); 2800 return this; 2801 } 2802 2803 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 2804 // Gets normalized mouse position from a DOM event relative to the 2805 // `container` (border excluded) or to the whole page if not specified. 2806 function getMousePosition(e, container) { 2807 if (!container) { 2808 return new Point(e.clientX, e.clientY); 2809 } 2810 2811 var scale = getScale(container), 2812 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 2813 2814 return new Point( 2815 // offset.left/top values are in page scale (like clientX/Y), 2816 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 2817 (e.clientX - offset.left) / scale.x - container.clientLeft, 2818 (e.clientY - offset.top) / scale.y - container.clientTop 2819 ); 2820 } 2821 2822 // Chrome on Win scrolls double the pixels as in other platforms (see #4538), 2823 // and Firefox scrolls device pixels, not CSS pixels 2824 var wheelPxFactor = 2825 (win && chrome) ? 2 * window.devicePixelRatio : 2826 gecko ? window.devicePixelRatio : 1; 2827 2828 // @function getWheelDelta(ev: DOMEvent): Number 2829 // Gets normalized wheel delta from a wheel DOM event, in vertical 2830 // pixels scrolled (negative if scrolling down). 2831 // Events from pointing devices without precise scrolling are mapped to 2832 // a best guess of 60 pixels. 2833 function getWheelDelta(e) { 2834 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 2835 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 2836 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 2837 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 2838 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 2839 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 2840 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 2841 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 2842 0; 2843 } 2844 2845 // check if element really left/entered the event target (for mouseenter/mouseleave) 2846 function isExternalTarget(el, e) { 2847 2848 var related = e.relatedTarget; 2849 2850 if (!related) { return true; } 2851 2852 try { 2853 while (related && (related !== el)) { 2854 related = related.parentNode; 2855 } 2856 } catch (err) { 2857 return false; 2858 } 2859 return (related !== el); 2860 } 2861 2862 var DomEvent = { 2863 __proto__: null, 2864 on: on, 2865 off: off, 2866 stopPropagation: stopPropagation, 2867 disableScrollPropagation: disableScrollPropagation, 2868 disableClickPropagation: disableClickPropagation, 2869 preventDefault: preventDefault, 2870 stop: stop, 2871 getMousePosition: getMousePosition, 2872 getWheelDelta: getWheelDelta, 2873 isExternalTarget: isExternalTarget, 2874 addListener: on, 2875 removeListener: off 2876 }; 2877 2878 /* 2879 * @class PosAnimation 2880 * @aka L.PosAnimation 2881 * @inherits Evented 2882 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 2883 * 2884 * @example 2885 * ```js 2886 * var fx = new L.PosAnimation(); 2887 * fx.run(el, [300, 500], 0.5); 2888 * ``` 2889 * 2890 * @constructor L.PosAnimation() 2891 * Creates a `PosAnimation` object. 2892 * 2893 */ 2894 2895 var PosAnimation = Evented.extend({ 2896 2897 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 2898 // Run an animation of a given element to a new position, optionally setting 2899 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 2900 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), 2901 // `0.5` by default). 2902 run: function (el, newPos, duration, easeLinearity) { 2903 this.stop(); 2904 2905 this._el = el; 2906 this._inProgress = true; 2907 this._duration = duration || 0.25; 2908 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 2909 2910 this._startPos = getPosition(el); 2911 this._offset = newPos.subtract(this._startPos); 2912 this._startTime = +new Date(); 2913 2914 // @event start: Event 2915 // Fired when the animation starts 2916 this.fire('start'); 2917 2918 this._animate(); 2919 }, 2920 2921 // @method stop() 2922 // Stops the animation (if currently running). 2923 stop: function () { 2924 if (!this._inProgress) { return; } 2925 2926 this._step(true); 2927 this._complete(); 2928 }, 2929 2930 _animate: function () { 2931 // animation loop 2932 this._animId = requestAnimFrame(this._animate, this); 2933 this._step(); 2934 }, 2935 2936 _step: function (round) { 2937 var elapsed = (+new Date()) - this._startTime, 2938 duration = this._duration * 1000; 2939 2940 if (elapsed < duration) { 2941 this._runFrame(this._easeOut(elapsed / duration), round); 2942 } else { 2943 this._runFrame(1); 2944 this._complete(); 2945 } 2946 }, 2947 2948 _runFrame: function (progress, round) { 2949 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 2950 if (round) { 2951 pos._round(); 2952 } 2953 setPosition(this._el, pos); 2954 2955 // @event step: Event 2956 // Fired continuously during the animation. 2957 this.fire('step'); 2958 }, 2959 2960 _complete: function () { 2961 cancelAnimFrame(this._animId); 2962 2963 this._inProgress = false; 2964 // @event end: Event 2965 // Fired when the animation ends. 2966 this.fire('end'); 2967 }, 2968 2969 _easeOut: function (t) { 2970 return 1 - Math.pow(1 - t, this._easeOutPower); 2971 } 2972 }); 2973 2974 /* 2975 * @class Map 2976 * @aka L.Map 2977 * @inherits Evented 2978 * 2979 * The central class of the API â it is used to create a map on a page and manipulate it. 2980 * 2981 * @example 2982 * 2983 * ```js 2984 * // initialize the map on the "map" div with a given center and zoom 2985 * var map = L.map('map', { 2986 * center: [51.505, -0.09], 2987 * zoom: 13 2988 * }); 2989 * ``` 2990 * 2991 */ 2992 2993 var Map = Evented.extend({ 2994 2995 options: { 2996 // @section Map State Options 2997 // @option crs: CRS = L.CRS.EPSG3857 2998 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 2999 // sure what it means. 3000 crs: EPSG3857, 3001 3002 // @option center: LatLng = undefined 3003 // Initial geographic center of the map 3004 center: undefined, 3005 3006 // @option zoom: Number = undefined 3007 // Initial map zoom level 3008 zoom: undefined, 3009 3010 // @option minZoom: Number = * 3011 // Minimum zoom level of the map. 3012 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3013 // the lowest of their `minZoom` options will be used instead. 3014 minZoom: undefined, 3015 3016 // @option maxZoom: Number = * 3017 // Maximum zoom level of the map. 3018 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3019 // the highest of their `maxZoom` options will be used instead. 3020 maxZoom: undefined, 3021 3022 // @option layers: Layer[] = [] 3023 // Array of layers that will be added to the map initially 3024 layers: [], 3025 3026 // @option maxBounds: LatLngBounds = null 3027 // When this option is set, the map restricts the view to the given 3028 // geographical bounds, bouncing the user back if the user tries to pan 3029 // outside the view. To set the restriction dynamically, use 3030 // [`setMaxBounds`](#map-setmaxbounds) method. 3031 maxBounds: undefined, 3032 3033 // @option renderer: Renderer = * 3034 // The default method for drawing vector layers on the map. `L.SVG` 3035 // or `L.Canvas` by default depending on browser support. 3036 renderer: undefined, 3037 3038 3039 // @section Animation Options 3040 // @option zoomAnimation: Boolean = true 3041 // Whether the map zoom animation is enabled. By default it's enabled 3042 // in all browsers that support CSS3 Transitions except Android. 3043 zoomAnimation: true, 3044 3045 // @option zoomAnimationThreshold: Number = 4 3046 // Won't animate zoom if the zoom difference exceeds this value. 3047 zoomAnimationThreshold: 4, 3048 3049 // @option fadeAnimation: Boolean = true 3050 // Whether the tile fade animation is enabled. By default it's enabled 3051 // in all browsers that support CSS3 Transitions except Android. 3052 fadeAnimation: true, 3053 3054 // @option markerZoomAnimation: Boolean = true 3055 // Whether markers animate their zoom with the zoom animation, if disabled 3056 // they will disappear for the length of the animation. By default it's 3057 // enabled in all browsers that support CSS3 Transitions except Android. 3058 markerZoomAnimation: true, 3059 3060 // @option transform3DLimit: Number = 2^23 3061 // Defines the maximum size of a CSS translation transform. The default 3062 // value should not be changed unless a web browser positions layers in 3063 // the wrong place after doing a large `panBy`. 3064 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3065 3066 // @section Interaction Options 3067 // @option zoomSnap: Number = 1 3068 // Forces the map's zoom level to always be a multiple of this, particularly 3069 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3070 // By default, the zoom level snaps to the nearest integer; lower values 3071 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3072 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3073 zoomSnap: 1, 3074 3075 // @option zoomDelta: Number = 1 3076 // Controls how much the map's zoom level will change after a 3077 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3078 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3079 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3080 zoomDelta: 1, 3081 3082 // @option trackResize: Boolean = true 3083 // Whether the map automatically handles browser window resize to update itself. 3084 trackResize: true 3085 }, 3086 3087 initialize: function (id, options) { // (HTMLElement or String, Object) 3088 options = setOptions(this, options); 3089 3090 // Make sure to assign internal flags at the beginning, 3091 // to avoid inconsistent state in some edge cases. 3092 this._handlers = []; 3093 this._layers = {}; 3094 this._zoomBoundLayers = {}; 3095 this._sizeChanged = true; 3096 3097 this._initContainer(id); 3098 this._initLayout(); 3099 3100 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3101 this._onResize = bind(this._onResize, this); 3102 3103 this._initEvents(); 3104 3105 if (options.maxBounds) { 3106 this.setMaxBounds(options.maxBounds); 3107 } 3108 3109 if (options.zoom !== undefined) { 3110 this._zoom = this._limitZoom(options.zoom); 3111 } 3112 3113 if (options.center && options.zoom !== undefined) { 3114 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3115 } 3116 3117 this.callInitHooks(); 3118 3119 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3120 this._zoomAnimated = TRANSITION && any3d && !mobileOpera && 3121 this.options.zoomAnimation; 3122 3123 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3124 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3125 if (this._zoomAnimated) { 3126 this._createAnimProxy(); 3127 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3128 } 3129 3130 this._addLayers(this.options.layers); 3131 }, 3132 3133 3134 // @section Methods for modifying map state 3135 3136 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3137 // Sets the view of the map (geographical center and zoom) with the given 3138 // animation options. 3139 setView: function (center, zoom, options) { 3140 3141 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3142 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3143 options = options || {}; 3144 3145 this._stop(); 3146 3147 if (this._loaded && !options.reset && options !== true) { 3148 3149 if (options.animate !== undefined) { 3150 options.zoom = extend({animate: options.animate}, options.zoom); 3151 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3152 } 3153 3154 // try animating pan or zoom 3155 var moved = (this._zoom !== zoom) ? 3156 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3157 this._tryAnimatedPan(center, options.pan); 3158 3159 if (moved) { 3160 // prevent resize handler call, the view will refresh after animation anyway 3161 clearTimeout(this._sizeTimer); 3162 return this; 3163 } 3164 } 3165 3166 // animation didn't start, just reset the map view 3167 this._resetView(center, zoom); 3168 3169 return this; 3170 }, 3171 3172 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3173 // Sets the zoom of the map. 3174 setZoom: function (zoom, options) { 3175 if (!this._loaded) { 3176 this._zoom = zoom; 3177 return this; 3178 } 3179 return this.setView(this.getCenter(), zoom, {zoom: options}); 3180 }, 3181 3182 // @method zoomIn(delta?: Number, options?: Zoom options): this 3183 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3184 zoomIn: function (delta, options) { 3185 delta = delta || (any3d ? this.options.zoomDelta : 1); 3186 return this.setZoom(this._zoom + delta, options); 3187 }, 3188 3189 // @method zoomOut(delta?: Number, options?: Zoom options): this 3190 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3191 zoomOut: function (delta, options) { 3192 delta = delta || (any3d ? this.options.zoomDelta : 1); 3193 return this.setZoom(this._zoom - delta, options); 3194 }, 3195 3196 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3197 // Zooms the map while keeping a specified geographical point on the map 3198 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3199 // @alternative 3200 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3201 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3202 setZoomAround: function (latlng, zoom, options) { 3203 var scale = this.getZoomScale(zoom), 3204 viewHalf = this.getSize().divideBy(2), 3205 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3206 3207 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3208 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3209 3210 return this.setView(newCenter, zoom, {zoom: options}); 3211 }, 3212 3213 _getBoundsCenterZoom: function (bounds, options) { 3214 3215 options = options || {}; 3216 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3217 3218 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3219 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3220 3221 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3222 3223 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3224 3225 if (zoom === Infinity) { 3226 return { 3227 center: bounds.getCenter(), 3228 zoom: zoom 3229 }; 3230 } 3231 3232 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3233 3234 swPoint = this.project(bounds.getSouthWest(), zoom), 3235 nePoint = this.project(bounds.getNorthEast(), zoom), 3236 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3237 3238 return { 3239 center: center, 3240 zoom: zoom 3241 }; 3242 }, 3243 3244 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3245 // Sets a map view that contains the given geographical bounds with the 3246 // maximum zoom level possible. 3247 fitBounds: function (bounds, options) { 3248 3249 bounds = toLatLngBounds(bounds); 3250 3251 if (!bounds.isValid()) { 3252 throw new Error('Bounds are not valid.'); 3253 } 3254 3255 var target = this._getBoundsCenterZoom(bounds, options); 3256 return this.setView(target.center, target.zoom, options); 3257 }, 3258 3259 // @method fitWorld(options?: fitBounds options): this 3260 // Sets a map view that mostly contains the whole world with the maximum 3261 // zoom level possible. 3262 fitWorld: function (options) { 3263 return this.fitBounds([[-90, -180], [90, 180]], options); 3264 }, 3265 3266 // @method panTo(latlng: LatLng, options?: Pan options): this 3267 // Pans the map to a given center. 3268 panTo: function (center, options) { // (LatLng) 3269 return this.setView(center, this._zoom, {pan: options}); 3270 }, 3271 3272 // @method panBy(offset: Point, options?: Pan options): this 3273 // Pans the map by a given number of pixels (animated). 3274 panBy: function (offset, options) { 3275 offset = toPoint(offset).round(); 3276 options = options || {}; 3277 3278 if (!offset.x && !offset.y) { 3279 return this.fire('moveend'); 3280 } 3281 // If we pan too far, Chrome gets issues with tiles 3282 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3283 if (options.animate !== true && !this.getSize().contains(offset)) { 3284 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3285 return this; 3286 } 3287 3288 if (!this._panAnim) { 3289 this._panAnim = new PosAnimation(); 3290 3291 this._panAnim.on({ 3292 'step': this._onPanTransitionStep, 3293 'end': this._onPanTransitionEnd 3294 }, this); 3295 } 3296 3297 // don't fire movestart if animating inertia 3298 if (!options.noMoveStart) { 3299 this.fire('movestart'); 3300 } 3301 3302 // animate pan unless animate: false specified 3303 if (options.animate !== false) { 3304 addClass(this._mapPane, 'leaflet-pan-anim'); 3305 3306 var newPos = this._getMapPanePos().subtract(offset).round(); 3307 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3308 } else { 3309 this._rawPanBy(offset); 3310 this.fire('move').fire('moveend'); 3311 } 3312 3313 return this; 3314 }, 3315 3316 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3317 // Sets the view of the map (geographical center and zoom) performing a smooth 3318 // pan-zoom animation. 3319 flyTo: function (targetCenter, targetZoom, options) { 3320 3321 options = options || {}; 3322 if (options.animate === false || !any3d) { 3323 return this.setView(targetCenter, targetZoom, options); 3324 } 3325 3326 this._stop(); 3327 3328 var from = this.project(this.getCenter()), 3329 to = this.project(targetCenter), 3330 size = this.getSize(), 3331 startZoom = this._zoom; 3332 3333 targetCenter = toLatLng(targetCenter); 3334 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3335 3336 var w0 = Math.max(size.x, size.y), 3337 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3338 u1 = (to.distanceTo(from)) || 1, 3339 rho = 1.42, 3340 rho2 = rho * rho; 3341 3342 function r(i) { 3343 var s1 = i ? -1 : 1, 3344 s2 = i ? w1 : w0, 3345 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3346 b1 = 2 * s2 * rho2 * u1, 3347 b = t1 / b1, 3348 sq = Math.sqrt(b * b + 1) - b; 3349 3350 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3351 // thus triggering an infinite loop in flyTo 3352 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3353 3354 return log; 3355 } 3356 3357 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3358 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3359 function tanh(n) { return sinh(n) / cosh(n); } 3360 3361 var r0 = r(0); 3362 3363 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3364 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3365 3366 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3367 3368 var start = Date.now(), 3369 S = (r(1) - r0) / rho, 3370 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3371 3372 function frame() { 3373 var t = (Date.now() - start) / duration, 3374 s = easeOut(t) * S; 3375 3376 if (t <= 1) { 3377 this._flyToFrame = requestAnimFrame(frame, this); 3378 3379 this._move( 3380 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3381 this.getScaleZoom(w0 / w(s), startZoom), 3382 {flyTo: true}); 3383 3384 } else { 3385 this 3386 ._move(targetCenter, targetZoom) 3387 ._moveEnd(true); 3388 } 3389 } 3390 3391 this._moveStart(true, options.noMoveStart); 3392 3393 frame.call(this); 3394 return this; 3395 }, 3396 3397 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3398 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3399 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3400 flyToBounds: function (bounds, options) { 3401 var target = this._getBoundsCenterZoom(bounds, options); 3402 return this.flyTo(target.center, target.zoom, options); 3403 }, 3404 3405 // @method setMaxBounds(bounds: LatLngBounds): this 3406 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3407 setMaxBounds: function (bounds) { 3408 bounds = toLatLngBounds(bounds); 3409 3410 if (!bounds.isValid()) { 3411 this.options.maxBounds = null; 3412 return this.off('moveend', this._panInsideMaxBounds); 3413 } else if (this.options.maxBounds) { 3414 this.off('moveend', this._panInsideMaxBounds); 3415 } 3416 3417 this.options.maxBounds = bounds; 3418 3419 if (this._loaded) { 3420 this._panInsideMaxBounds(); 3421 } 3422 3423 return this.on('moveend', this._panInsideMaxBounds); 3424 }, 3425 3426 // @method setMinZoom(zoom: Number): this 3427 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3428 setMinZoom: function (zoom) { 3429 var oldZoom = this.options.minZoom; 3430 this.options.minZoom = zoom; 3431 3432 if (this._loaded && oldZoom !== zoom) { 3433 this.fire('zoomlevelschange'); 3434 3435 if (this.getZoom() < this.options.minZoom) { 3436 return this.setZoom(zoom); 3437 } 3438 } 3439 3440 return this; 3441 }, 3442 3443 // @method setMaxZoom(zoom: Number): this 3444 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3445 setMaxZoom: function (zoom) { 3446 var oldZoom = this.options.maxZoom; 3447 this.options.maxZoom = zoom; 3448 3449 if (this._loaded && oldZoom !== zoom) { 3450 this.fire('zoomlevelschange'); 3451 3452 if (this.getZoom() > this.options.maxZoom) { 3453 return this.setZoom(zoom); 3454 } 3455 } 3456 3457 return this; 3458 }, 3459 3460 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3461 // 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. 3462 panInsideBounds: function (bounds, options) { 3463 this._enforcingBounds = true; 3464 var center = this.getCenter(), 3465 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3466 3467 if (!center.equals(newCenter)) { 3468 this.panTo(newCenter, options); 3469 } 3470 3471 this._enforcingBounds = false; 3472 return this; 3473 }, 3474 3475 // @method panInside(latlng: LatLng, options?: padding options): this 3476 // Pans the map the minimum amount to make the `latlng` visible. Use 3477 // padding options to fit the display to more restricted bounds. 3478 // If `latlng` is already within the (optionally padded) display bounds, 3479 // the map will not be panned. 3480 panInside: function (latlng, options) { 3481 options = options || {}; 3482 3483 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3484 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3485 pixelCenter = this.project(this.getCenter()), 3486 pixelPoint = this.project(latlng), 3487 pixelBounds = this.getPixelBounds(), 3488 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]), 3489 paddedSize = paddedBounds.getSize(); 3490 3491 if (!paddedBounds.contains(pixelPoint)) { 3492 this._enforcingBounds = true; 3493 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter()); 3494 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize); 3495 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x; 3496 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y; 3497 this.panTo(this.unproject(pixelCenter), options); 3498 this._enforcingBounds = false; 3499 } 3500 return this; 3501 }, 3502 3503 // @method invalidateSize(options: Zoom/pan options): this 3504 // Checks if the map container size changed and updates the map if so â 3505 // call it after you've changed the map size dynamically, also animating 3506 // pan by default. If `options.pan` is `false`, panning will not occur. 3507 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3508 // that it doesn't happen often even if the method is called many 3509 // times in a row. 3510 3511 // @alternative 3512 // @method invalidateSize(animate: Boolean): this 3513 // Checks if the map container size changed and updates the map if so â 3514 // call it after you've changed the map size dynamically, also animating 3515 // pan by default. 3516 invalidateSize: function (options) { 3517 if (!this._loaded) { return this; } 3518 3519 options = extend({ 3520 animate: false, 3521 pan: true 3522 }, options === true ? {animate: true} : options); 3523 3524 var oldSize = this.getSize(); 3525 this._sizeChanged = true; 3526 this._lastCenter = null; 3527 3528 var newSize = this.getSize(), 3529 oldCenter = oldSize.divideBy(2).round(), 3530 newCenter = newSize.divideBy(2).round(), 3531 offset = oldCenter.subtract(newCenter); 3532 3533 if (!offset.x && !offset.y) { return this; } 3534 3535 if (options.animate && options.pan) { 3536 this.panBy(offset); 3537 3538 } else { 3539 if (options.pan) { 3540 this._rawPanBy(offset); 3541 } 3542 3543 this.fire('move'); 3544 3545 if (options.debounceMoveend) { 3546 clearTimeout(this._sizeTimer); 3547 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3548 } else { 3549 this.fire('moveend'); 3550 } 3551 } 3552 3553 // @section Map state change events 3554 // @event resize: ResizeEvent 3555 // Fired when the map is resized. 3556 return this.fire('resize', { 3557 oldSize: oldSize, 3558 newSize: newSize 3559 }); 3560 }, 3561 3562 // @section Methods for modifying map state 3563 // @method stop(): this 3564 // Stops the currently running `panTo` or `flyTo` animation, if any. 3565 stop: function () { 3566 this.setZoom(this._limitZoom(this._zoom)); 3567 if (!this.options.zoomSnap) { 3568 this.fire('viewreset'); 3569 } 3570 return this._stop(); 3571 }, 3572 3573 // @section Geolocation methods 3574 // @method locate(options?: Locate options): this 3575 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3576 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3577 // and optionally sets the map view to the user's location with respect to 3578 // detection accuracy (or to the world view if geolocation failed). 3579 // Note that, if your page doesn't use HTTPS, this method will fail in 3580 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3581 // See `Locate options` for more details. 3582 locate: function (options) { 3583 3584 options = this._locateOptions = extend({ 3585 timeout: 10000, 3586 watch: false 3587 // setView: false 3588 // maxZoom: <Number> 3589 // maximumAge: 0 3590 // enableHighAccuracy: false 3591 }, options); 3592 3593 if (!('geolocation' in navigator)) { 3594 this._handleGeolocationError({ 3595 code: 0, 3596 message: 'Geolocation not supported.' 3597 }); 3598 return this; 3599 } 3600 3601 var onResponse = bind(this._handleGeolocationResponse, this), 3602 onError = bind(this._handleGeolocationError, this); 3603 3604 if (options.watch) { 3605 this._locationWatchId = 3606 navigator.geolocation.watchPosition(onResponse, onError, options); 3607 } else { 3608 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 3609 } 3610 return this; 3611 }, 3612 3613 // @method stopLocate(): this 3614 // Stops watching location previously initiated by `map.locate({watch: true})` 3615 // and aborts resetting the map view if map.locate was called with 3616 // `{setView: true}`. 3617 stopLocate: function () { 3618 if (navigator.geolocation && navigator.geolocation.clearWatch) { 3619 navigator.geolocation.clearWatch(this._locationWatchId); 3620 } 3621 if (this._locateOptions) { 3622 this._locateOptions.setView = false; 3623 } 3624 return this; 3625 }, 3626 3627 _handleGeolocationError: function (error) { 3628 var c = error.code, 3629 message = error.message || 3630 (c === 1 ? 'permission denied' : 3631 (c === 2 ? 'position unavailable' : 'timeout')); 3632 3633 if (this._locateOptions.setView && !this._loaded) { 3634 this.fitWorld(); 3635 } 3636 3637 // @section Location events 3638 // @event locationerror: ErrorEvent 3639 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 3640 this.fire('locationerror', { 3641 code: c, 3642 message: 'Geolocation error: ' + message + '.' 3643 }); 3644 }, 3645 3646 _handleGeolocationResponse: function (pos) { 3647 var lat = pos.coords.latitude, 3648 lng = pos.coords.longitude, 3649 latlng = new LatLng(lat, lng), 3650 bounds = latlng.toBounds(pos.coords.accuracy * 2), 3651 options = this._locateOptions; 3652 3653 if (options.setView) { 3654 var zoom = this.getBoundsZoom(bounds); 3655 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 3656 } 3657 3658 var data = { 3659 latlng: latlng, 3660 bounds: bounds, 3661 timestamp: pos.timestamp 3662 }; 3663 3664 for (var i in pos.coords) { 3665 if (typeof pos.coords[i] === 'number') { 3666 data[i] = pos.coords[i]; 3667 } 3668 } 3669 3670 // @event locationfound: LocationEvent 3671 // Fired when geolocation (using the [`locate`](#map-locate) method) 3672 // went successfully. 3673 this.fire('locationfound', data); 3674 }, 3675 3676 // TODO Appropriate docs section? 3677 // @section Other Methods 3678 // @method addHandler(name: String, HandlerClass: Function): this 3679 // Adds a new `Handler` to the map, given its name and constructor function. 3680 addHandler: function (name, HandlerClass) { 3681 if (!HandlerClass) { return this; } 3682 3683 var handler = this[name] = new HandlerClass(this); 3684 3685 this._handlers.push(handler); 3686 3687 if (this.options[name]) { 3688 handler.enable(); 3689 } 3690 3691 return this; 3692 }, 3693 3694 // @method remove(): this 3695 // Destroys the map and clears all related event listeners. 3696 remove: function () { 3697 3698 this._initEvents(true); 3699 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); } 3700 3701 if (this._containerId !== this._container._leaflet_id) { 3702 throw new Error('Map container is being reused by another instance'); 3703 } 3704 3705 try { 3706 // throws error in IE6-8 3707 delete this._container._leaflet_id; 3708 delete this._containerId; 3709 } catch (e) { 3710 /*eslint-disable */ 3711 this._container._leaflet_id = undefined; 3712 /* eslint-enable */ 3713 this._containerId = undefined; 3714 } 3715 3716 if (this._locationWatchId !== undefined) { 3717 this.stopLocate(); 3718 } 3719 3720 this._stop(); 3721 3722 remove(this._mapPane); 3723 3724 if (this._clearControlPos) { 3725 this._clearControlPos(); 3726 } 3727 if (this._resizeRequest) { 3728 cancelAnimFrame(this._resizeRequest); 3729 this._resizeRequest = null; 3730 } 3731 3732 this._clearHandlers(); 3733 3734 if (this._loaded) { 3735 // @section Map state change events 3736 // @event unload: Event 3737 // Fired when the map is destroyed with [remove](#map-remove) method. 3738 this.fire('unload'); 3739 } 3740 3741 var i; 3742 for (i in this._layers) { 3743 this._layers[i].remove(); 3744 } 3745 for (i in this._panes) { 3746 remove(this._panes[i]); 3747 } 3748 3749 this._layers = []; 3750 this._panes = []; 3751 delete this._mapPane; 3752 delete this._renderer; 3753 3754 return this; 3755 }, 3756 3757 // @section Other Methods 3758 // @method createPane(name: String, container?: HTMLElement): HTMLElement 3759 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 3760 // then returns it. The pane is created as a child of `container`, or 3761 // as a child of the main map pane if not set. 3762 createPane: function (name, container) { 3763 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3764 pane = create$1('div', className, container || this._mapPane); 3765 3766 if (name) { 3767 this._panes[name] = pane; 3768 } 3769 return pane; 3770 }, 3771 3772 // @section Methods for Getting Map State 3773 3774 // @method getCenter(): LatLng 3775 // Returns the geographical center of the map view 3776 getCenter: function () { 3777 this._checkIfLoaded(); 3778 3779 if (this._lastCenter && !this._moved()) { 3780 return this._lastCenter; 3781 } 3782 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3783 }, 3784 3785 // @method getZoom(): Number 3786 // Returns the current zoom level of the map view 3787 getZoom: function () { 3788 return this._zoom; 3789 }, 3790 3791 // @method getBounds(): LatLngBounds 3792 // Returns the geographical bounds visible in the current map view 3793 getBounds: function () { 3794 var bounds = this.getPixelBounds(), 3795 sw = this.unproject(bounds.getBottomLeft()), 3796 ne = this.unproject(bounds.getTopRight()); 3797 3798 return new LatLngBounds(sw, ne); 3799 }, 3800 3801 // @method getMinZoom(): Number 3802 // 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. 3803 getMinZoom: function () { 3804 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3805 }, 3806 3807 // @method getMaxZoom(): Number 3808 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3809 getMaxZoom: function () { 3810 return this.options.maxZoom === undefined ? 3811 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3812 this.options.maxZoom; 3813 }, 3814 3815 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 3816 // Returns the maximum zoom level on which the given bounds fit to the map 3817 // view in its entirety. If `inside` (optional) is set to `true`, the method 3818 // instead returns the minimum zoom level on which the map view fits into 3819 // the given bounds in its entirety. 3820 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3821 bounds = toLatLngBounds(bounds); 3822 padding = toPoint(padding || [0, 0]); 3823 3824 var zoom = this.getZoom() || 0, 3825 min = this.getMinZoom(), 3826 max = this.getMaxZoom(), 3827 nw = bounds.getNorthWest(), 3828 se = bounds.getSouthEast(), 3829 size = this.getSize().subtract(padding), 3830 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3831 snap = any3d ? this.options.zoomSnap : 1, 3832 scalex = size.x / boundsSize.x, 3833 scaley = size.y / boundsSize.y, 3834 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 3835 3836 zoom = this.getScaleZoom(scale, zoom); 3837 3838 if (snap) { 3839 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3840 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3841 } 3842 3843 return Math.max(min, Math.min(max, zoom)); 3844 }, 3845 3846 // @method getSize(): Point 3847 // Returns the current size of the map container (in pixels). 3848 getSize: function () { 3849 if (!this._size || this._sizeChanged) { 3850 this._size = new Point( 3851 this._container.clientWidth || 0, 3852 this._container.clientHeight || 0); 3853 3854 this._sizeChanged = false; 3855 } 3856 return this._size.clone(); 3857 }, 3858 3859 // @method getPixelBounds(): Bounds 3860 // Returns the bounds of the current map view in projected pixel 3861 // coordinates (sometimes useful in layer and overlay implementations). 3862 getPixelBounds: function (center, zoom) { 3863 var topLeftPoint = this._getTopLeftPoint(center, zoom); 3864 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 3865 }, 3866 3867 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 3868 // the map pane? "left point of the map layer" can be confusing, specially 3869 // since there can be negative offsets. 3870 // @method getPixelOrigin(): Point 3871 // Returns the projected pixel coordinates of the top left point of 3872 // the map layer (useful in custom layer and overlay implementations). 3873 getPixelOrigin: function () { 3874 this._checkIfLoaded(); 3875 return this._pixelOrigin; 3876 }, 3877 3878 // @method getPixelWorldBounds(zoom?: Number): Bounds 3879 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 3880 // If `zoom` is omitted, the map's current zoom level is used. 3881 getPixelWorldBounds: function (zoom) { 3882 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 3883 }, 3884 3885 // @section Other Methods 3886 3887 // @method getPane(pane: String|HTMLElement): HTMLElement 3888 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 3889 getPane: function (pane) { 3890 return typeof pane === 'string' ? this._panes[pane] : pane; 3891 }, 3892 3893 // @method getPanes(): Object 3894 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 3895 // the panes as values. 3896 getPanes: function () { 3897 return this._panes; 3898 }, 3899 3900 // @method getContainer: HTMLElement 3901 // Returns the HTML element that contains the map. 3902 getContainer: function () { 3903 return this._container; 3904 }, 3905 3906 3907 // @section Conversion Methods 3908 3909 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 3910 // Returns the scale factor to be applied to a map transition from zoom level 3911 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 3912 getZoomScale: function (toZoom, fromZoom) { 3913 // TODO replace with universal implementation after refactoring projections 3914 var crs = this.options.crs; 3915 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3916 return crs.scale(toZoom) / crs.scale(fromZoom); 3917 }, 3918 3919 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 3920 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 3921 // level and everything is scaled by a factor of `scale`. Inverse of 3922 // [`getZoomScale`](#map-getZoomScale). 3923 getScaleZoom: function (scale, fromZoom) { 3924 var crs = this.options.crs; 3925 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3926 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 3927 return isNaN(zoom) ? Infinity : zoom; 3928 }, 3929 3930 // @method project(latlng: LatLng, zoom: Number): Point 3931 // Projects a geographical coordinate `LatLng` according to the projection 3932 // of the map's CRS, then scales it according to `zoom` and the CRS's 3933 // `Transformation`. The result is pixel coordinate relative to 3934 // the CRS origin. 3935 project: function (latlng, zoom) { 3936 zoom = zoom === undefined ? this._zoom : zoom; 3937 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 3938 }, 3939 3940 // @method unproject(point: Point, zoom: Number): LatLng 3941 // Inverse of [`project`](#map-project). 3942 unproject: function (point, zoom) { 3943 zoom = zoom === undefined ? this._zoom : zoom; 3944 return this.options.crs.pointToLatLng(toPoint(point), zoom); 3945 }, 3946 3947 // @method layerPointToLatLng(point: Point): LatLng 3948 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 3949 // returns the corresponding geographical coordinate (for the current zoom level). 3950 layerPointToLatLng: function (point) { 3951 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 3952 return this.unproject(projectedPoint); 3953 }, 3954 3955 // @method latLngToLayerPoint(latlng: LatLng): Point 3956 // Given a geographical coordinate, returns the corresponding pixel coordinate 3957 // relative to the [origin pixel](#map-getpixelorigin). 3958 latLngToLayerPoint: function (latlng) { 3959 var projectedPoint = this.project(toLatLng(latlng))._round(); 3960 return projectedPoint._subtract(this.getPixelOrigin()); 3961 }, 3962 3963 // @method wrapLatLng(latlng: LatLng): LatLng 3964 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 3965 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 3966 // CRS's bounds. 3967 // By default this means longitude is wrapped around the dateline so its 3968 // value is between -180 and +180 degrees. 3969 wrapLatLng: function (latlng) { 3970 return this.options.crs.wrapLatLng(toLatLng(latlng)); 3971 }, 3972 3973 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 3974 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 3975 // its center is within the CRS's bounds. 3976 // By default this means the center longitude is wrapped around the dateline so its 3977 // value is between -180 and +180 degrees, and the majority of the bounds 3978 // overlaps the CRS's bounds. 3979 wrapLatLngBounds: function (latlng) { 3980 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 3981 }, 3982 3983 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 3984 // Returns the distance between two geographical coordinates according to 3985 // the map's CRS. By default this measures distance in meters. 3986 distance: function (latlng1, latlng2) { 3987 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 3988 }, 3989 3990 // @method containerPointToLayerPoint(point: Point): Point 3991 // Given a pixel coordinate relative to the map container, returns the corresponding 3992 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 3993 containerPointToLayerPoint: function (point) { // (Point) 3994 return toPoint(point).subtract(this._getMapPanePos()); 3995 }, 3996 3997 // @method layerPointToContainerPoint(point: Point): Point 3998 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 3999 // returns the corresponding pixel coordinate relative to the map container. 4000 layerPointToContainerPoint: function (point) { // (Point) 4001 return toPoint(point).add(this._getMapPanePos()); 4002 }, 4003 4004 // @method containerPointToLatLng(point: Point): LatLng 4005 // Given a pixel coordinate relative to the map container, returns 4006 // the corresponding geographical coordinate (for the current zoom level). 4007 containerPointToLatLng: function (point) { 4008 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4009 return this.layerPointToLatLng(layerPoint); 4010 }, 4011 4012 // @method latLngToContainerPoint(latlng: LatLng): Point 4013 // Given a geographical coordinate, returns the corresponding pixel coordinate 4014 // relative to the map container. 4015 latLngToContainerPoint: function (latlng) { 4016 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4017 }, 4018 4019 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4020 // Given a MouseEvent object, returns the pixel coordinate relative to the 4021 // map container where the event took place. 4022 mouseEventToContainerPoint: function (e) { 4023 return getMousePosition(e, this._container); 4024 }, 4025 4026 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4027 // Given a MouseEvent object, returns the pixel coordinate relative to 4028 // the [origin pixel](#map-getpixelorigin) where the event took place. 4029 mouseEventToLayerPoint: function (e) { 4030 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4031 }, 4032 4033 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4034 // Given a MouseEvent object, returns geographical coordinate where the 4035 // event took place. 4036 mouseEventToLatLng: function (e) { // (MouseEvent) 4037 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4038 }, 4039 4040 4041 // map initialization methods 4042 4043 _initContainer: function (id) { 4044 var container = this._container = get(id); 4045 4046 if (!container) { 4047 throw new Error('Map container not found.'); 4048 } else if (container._leaflet_id) { 4049 throw new Error('Map container is already initialized.'); 4050 } 4051 4052 on(container, 'scroll', this._onScroll, this); 4053 this._containerId = stamp(container); 4054 }, 4055 4056 _initLayout: function () { 4057 var container = this._container; 4058 4059 this._fadeAnimated = this.options.fadeAnimation && any3d; 4060 4061 addClass(container, 'leaflet-container' + 4062 (touch ? ' leaflet-touch' : '') + 4063 (retina ? ' leaflet-retina' : '') + 4064 (ielt9 ? ' leaflet-oldie' : '') + 4065 (safari ? ' leaflet-safari' : '') + 4066 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4067 4068 var position = getStyle(container, 'position'); 4069 4070 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { 4071 container.style.position = 'relative'; 4072 } 4073 4074 this._initPanes(); 4075 4076 if (this._initControlPos) { 4077 this._initControlPos(); 4078 } 4079 }, 4080 4081 _initPanes: function () { 4082 var panes = this._panes = {}; 4083 this._paneRenderers = {}; 4084 4085 // @section 4086 // 4087 // Panes are DOM elements used to control the ordering of layers on the map. You 4088 // can access panes with [`map.getPane`](#map-getpane) or 4089 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4090 // [`map.createPane`](#map-createpane) method. 4091 // 4092 // Every map has the following default panes that differ only in zIndex. 4093 // 4094 // @pane mapPane: HTMLElement = 'auto' 4095 // Pane that contains all other map panes 4096 4097 this._mapPane = this.createPane('mapPane', this._container); 4098 setPosition(this._mapPane, new Point(0, 0)); 4099 4100 // @pane tilePane: HTMLElement = 200 4101 // Pane for `GridLayer`s and `TileLayer`s 4102 this.createPane('tilePane'); 4103 // @pane overlayPane: HTMLElement = 400 4104 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4105 this.createPane('overlayPane'); 4106 // @pane shadowPane: HTMLElement = 500 4107 // Pane for overlay shadows (e.g. `Marker` shadows) 4108 this.createPane('shadowPane'); 4109 // @pane markerPane: HTMLElement = 600 4110 // Pane for `Icon`s of `Marker`s 4111 this.createPane('markerPane'); 4112 // @pane tooltipPane: HTMLElement = 650 4113 // Pane for `Tooltip`s. 4114 this.createPane('tooltipPane'); 4115 // @pane popupPane: HTMLElement = 700 4116 // Pane for `Popup`s. 4117 this.createPane('popupPane'); 4118 4119 if (!this.options.markerZoomAnimation) { 4120 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4121 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4122 } 4123 }, 4124 4125 4126 // private methods that modify map state 4127 4128 // @section Map state change events 4129 _resetView: function (center, zoom) { 4130 setPosition(this._mapPane, new Point(0, 0)); 4131 4132 var loading = !this._loaded; 4133 this._loaded = true; 4134 zoom = this._limitZoom(zoom); 4135 4136 this.fire('viewprereset'); 4137 4138 var zoomChanged = this._zoom !== zoom; 4139 this 4140 ._moveStart(zoomChanged, false) 4141 ._move(center, zoom) 4142 ._moveEnd(zoomChanged); 4143 4144 // @event viewreset: Event 4145 // Fired when the map needs to redraw its content (this usually happens 4146 // on map zoom or load). Very useful for creating custom overlays. 4147 this.fire('viewreset'); 4148 4149 // @event load: Event 4150 // Fired when the map is initialized (when its center and zoom are set 4151 // for the first time). 4152 if (loading) { 4153 this.fire('load'); 4154 } 4155 }, 4156 4157 _moveStart: function (zoomChanged, noMoveStart) { 4158 // @event zoomstart: Event 4159 // Fired when the map zoom is about to change (e.g. before zoom animation). 4160 // @event movestart: Event 4161 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4162 if (zoomChanged) { 4163 this.fire('zoomstart'); 4164 } 4165 if (!noMoveStart) { 4166 this.fire('movestart'); 4167 } 4168 return this; 4169 }, 4170 4171 _move: function (center, zoom, data) { 4172 if (zoom === undefined) { 4173 zoom = this._zoom; 4174 } 4175 var zoomChanged = this._zoom !== zoom; 4176 4177 this._zoom = zoom; 4178 this._lastCenter = center; 4179 this._pixelOrigin = this._getNewPixelOrigin(center); 4180 4181 // @event zoom: Event 4182 // Fired repeatedly during any change in zoom level, 4183 // including zoom and fly animations. 4184 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4185 this.fire('zoom', data); 4186 } 4187 4188 // @event move: Event 4189 // Fired repeatedly during any movement of the map, 4190 // including pan and fly animations. 4191 return this.fire('move', data); 4192 }, 4193 4194 _moveEnd: function (zoomChanged) { 4195 // @event zoomend: Event 4196 // Fired when the map zoom changed, after any animations. 4197 if (zoomChanged) { 4198 this.fire('zoomend'); 4199 } 4200 4201 // @event moveend: Event 4202 // Fired when the center of the map stops changing 4203 // (e.g. user stopped dragging the map or after non-centered zoom). 4204 return this.fire('moveend'); 4205 }, 4206 4207 _stop: function () { 4208 cancelAnimFrame(this._flyToFrame); 4209 if (this._panAnim) { 4210 this._panAnim.stop(); 4211 } 4212 return this; 4213 }, 4214 4215 _rawPanBy: function (offset) { 4216 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4217 }, 4218 4219 _getZoomSpan: function () { 4220 return this.getMaxZoom() - this.getMinZoom(); 4221 }, 4222 4223 _panInsideMaxBounds: function () { 4224 if (!this._enforcingBounds) { 4225 this.panInsideBounds(this.options.maxBounds); 4226 } 4227 }, 4228 4229 _checkIfLoaded: function () { 4230 if (!this._loaded) { 4231 throw new Error('Set map center and zoom first.'); 4232 } 4233 }, 4234 4235 // DOM event handling 4236 4237 // @section Interaction events 4238 _initEvents: function (remove) { 4239 this._targets = {}; 4240 this._targets[stamp(this._container)] = this; 4241 4242 var onOff = remove ? off : on; 4243 4244 // @event click: MouseEvent 4245 // Fired when the user clicks (or taps) the map. 4246 // @event dblclick: MouseEvent 4247 // Fired when the user double-clicks (or double-taps) the map. 4248 // @event mousedown: MouseEvent 4249 // Fired when the user pushes the mouse button on the map. 4250 // @event mouseup: MouseEvent 4251 // Fired when the user releases the mouse button on the map. 4252 // @event mouseover: MouseEvent 4253 // Fired when the mouse enters the map. 4254 // @event mouseout: MouseEvent 4255 // Fired when the mouse leaves the map. 4256 // @event mousemove: MouseEvent 4257 // Fired while the mouse moves over the map. 4258 // @event contextmenu: MouseEvent 4259 // Fired when the user pushes the right mouse button on the map, prevents 4260 // default browser context menu from showing if there are listeners on 4261 // this event. Also fired on mobile when the user holds a single touch 4262 // for a second (also called long press). 4263 // @event keypress: KeyboardEvent 4264 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused. 4265 // @event keydown: KeyboardEvent 4266 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event, 4267 // the `keydown` event is fired for keys that produce a character value and for keys 4268 // that do not produce a character value. 4269 // @event keyup: KeyboardEvent 4270 // Fired when the user releases a key from the keyboard while the map is focused. 4271 onOff(this._container, 'click dblclick mousedown mouseup ' + 4272 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this); 4273 4274 if (this.options.trackResize) { 4275 onOff(window, 'resize', this._onResize, this); 4276 } 4277 4278 if (any3d && this.options.transform3DLimit) { 4279 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4280 } 4281 }, 4282 4283 _onResize: function () { 4284 cancelAnimFrame(this._resizeRequest); 4285 this._resizeRequest = requestAnimFrame( 4286 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4287 }, 4288 4289 _onScroll: function () { 4290 this._container.scrollTop = 0; 4291 this._container.scrollLeft = 0; 4292 }, 4293 4294 _onMoveEnd: function () { 4295 var pos = this._getMapPanePos(); 4296 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4297 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4298 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ 4299 this._resetView(this.getCenter(), this.getZoom()); 4300 } 4301 }, 4302 4303 _findEventTargets: function (e, type) { 4304 var targets = [], 4305 target, 4306 isHover = type === 'mouseout' || type === 'mouseover', 4307 src = e.target || e.srcElement, 4308 dragging = false; 4309 4310 while (src) { 4311 target = this._targets[stamp(src)]; 4312 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) { 4313 // Prevent firing click after you just dragged an object. 4314 dragging = true; 4315 break; 4316 } 4317 if (target && target.listens(type, true)) { 4318 if (isHover && !isExternalTarget(src, e)) { break; } 4319 targets.push(target); 4320 if (isHover) { break; } 4321 } 4322 if (src === this._container) { break; } 4323 src = src.parentNode; 4324 } 4325 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) { 4326 targets = [this]; 4327 } 4328 return targets; 4329 }, 4330 4331 _isClickDisabled: function (el) { 4332 while (el !== this._container) { 4333 if (el['_leaflet_disable_click']) { return true; } 4334 el = el.parentNode; 4335 } 4336 }, 4337 4338 _handleDOMEvent: function (e) { 4339 var el = (e.target || e.srcElement); 4340 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) { 4341 return; 4342 } 4343 4344 var type = e.type; 4345 4346 if (type === 'mousedown') { 4347 // prevents outline when clicking on keyboard-focusable element 4348 preventOutline(el); 4349 } 4350 4351 this._fireDOMEvent(e, type); 4352 }, 4353 4354 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4355 4356 _fireDOMEvent: function (e, type, canvasTargets) { 4357 4358 if (e.type === 'click') { 4359 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4360 // @event preclick: MouseEvent 4361 // Fired before mouse click on the map (sometimes useful when you 4362 // want something to happen on click before any existing click 4363 // handlers start running). 4364 var synth = extend({}, e); 4365 synth.type = 'preclick'; 4366 this._fireDOMEvent(synth, synth.type, canvasTargets); 4367 } 4368 4369 // Find the layer the event is propagating from and its parents. 4370 var targets = this._findEventTargets(e, type); 4371 4372 if (canvasTargets) { 4373 var filtered = []; // pick only targets with listeners 4374 for (var i = 0; i < canvasTargets.length; i++) { 4375 if (canvasTargets[i].listens(type, true)) { 4376 filtered.push(canvasTargets[i]); 4377 } 4378 } 4379 targets = filtered.concat(targets); 4380 } 4381 4382 if (!targets.length) { return; } 4383 4384 if (type === 'contextmenu') { 4385 preventDefault(e); 4386 } 4387 4388 var target = targets[0]; 4389 var data = { 4390 originalEvent: e 4391 }; 4392 4393 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') { 4394 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4395 data.containerPoint = isMarker ? 4396 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4397 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4398 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4399 } 4400 4401 for (i = 0; i < targets.length; i++) { 4402 targets[i].fire(type, data, true); 4403 if (data.originalEvent._stopped || 4404 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4405 } 4406 }, 4407 4408 _draggableMoved: function (obj) { 4409 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4410 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4411 }, 4412 4413 _clearHandlers: function () { 4414 for (var i = 0, len = this._handlers.length; i < len; i++) { 4415 this._handlers[i].disable(); 4416 } 4417 }, 4418 4419 // @section Other Methods 4420 4421 // @method whenReady(fn: Function, context?: Object): this 4422 // Runs the given function `fn` when the map gets initialized with 4423 // a view (center and zoom) and at least one layer, or immediately 4424 // if it's already initialized, optionally passing a function context. 4425 whenReady: function (callback, context) { 4426 if (this._loaded) { 4427 callback.call(context || this, {target: this}); 4428 } else { 4429 this.on('load', callback, context); 4430 } 4431 return this; 4432 }, 4433 4434 4435 // private methods for getting map state 4436 4437 _getMapPanePos: function () { 4438 return getPosition(this._mapPane) || new Point(0, 0); 4439 }, 4440 4441 _moved: function () { 4442 var pos = this._getMapPanePos(); 4443 return pos && !pos.equals([0, 0]); 4444 }, 4445 4446 _getTopLeftPoint: function (center, zoom) { 4447 var pixelOrigin = center && zoom !== undefined ? 4448 this._getNewPixelOrigin(center, zoom) : 4449 this.getPixelOrigin(); 4450 return pixelOrigin.subtract(this._getMapPanePos()); 4451 }, 4452 4453 _getNewPixelOrigin: function (center, zoom) { 4454 var viewHalf = this.getSize()._divideBy(2); 4455 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4456 }, 4457 4458 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4459 var topLeft = this._getNewPixelOrigin(center, zoom); 4460 return this.project(latlng, zoom)._subtract(topLeft); 4461 }, 4462 4463 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4464 var topLeft = this._getNewPixelOrigin(center, zoom); 4465 return toBounds([ 4466 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4467 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4468 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4469 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4470 ]); 4471 }, 4472 4473 // layer point of the current center 4474 _getCenterLayerPoint: function () { 4475 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4476 }, 4477 4478 // offset of the specified place to the current center in pixels 4479 _getCenterOffset: function (latlng) { 4480 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4481 }, 4482 4483 // adjust center for view to get inside bounds 4484 _limitCenter: function (center, zoom, bounds) { 4485 4486 if (!bounds) { return center; } 4487 4488 var centerPoint = this.project(center, zoom), 4489 viewHalf = this.getSize().divideBy(2), 4490 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4491 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4492 4493 // If offset is less than a pixel, ignore. 4494 // This prevents unstable projections from getting into 4495 // an infinite loop of tiny offsets. 4496 if (offset.round().equals([0, 0])) { 4497 return center; 4498 } 4499 4500 return this.unproject(centerPoint.add(offset), zoom); 4501 }, 4502 4503 // adjust offset for view to get inside bounds 4504 _limitOffset: function (offset, bounds) { 4505 if (!bounds) { return offset; } 4506 4507 var viewBounds = this.getPixelBounds(), 4508 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4509 4510 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4511 }, 4512 4513 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4514 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4515 var projectedMaxBounds = toBounds( 4516 this.project(maxBounds.getNorthEast(), zoom), 4517 this.project(maxBounds.getSouthWest(), zoom) 4518 ), 4519 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4520 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4521 4522 dx = this._rebound(minOffset.x, -maxOffset.x), 4523 dy = this._rebound(minOffset.y, -maxOffset.y); 4524 4525 return new Point(dx, dy); 4526 }, 4527 4528 _rebound: function (left, right) { 4529 return left + right > 0 ? 4530 Math.round(left - right) / 2 : 4531 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4532 }, 4533 4534 _limitZoom: function (zoom) { 4535 var min = this.getMinZoom(), 4536 max = this.getMaxZoom(), 4537 snap = any3d ? this.options.zoomSnap : 1; 4538 if (snap) { 4539 zoom = Math.round(zoom / snap) * snap; 4540 } 4541 return Math.max(min, Math.min(max, zoom)); 4542 }, 4543 4544 _onPanTransitionStep: function () { 4545 this.fire('move'); 4546 }, 4547 4548 _onPanTransitionEnd: function () { 4549 removeClass(this._mapPane, 'leaflet-pan-anim'); 4550 this.fire('moveend'); 4551 }, 4552 4553 _tryAnimatedPan: function (center, options) { 4554 // difference between the new and current centers in pixels 4555 var offset = this._getCenterOffset(center)._trunc(); 4556 4557 // don't animate too far unless animate: true specified in options 4558 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4559 4560 this.panBy(offset, options); 4561 4562 return true; 4563 }, 4564 4565 _createAnimProxy: function () { 4566 4567 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4568 this._panes.mapPane.appendChild(proxy); 4569 4570 this.on('zoomanim', function (e) { 4571 var prop = TRANSFORM, 4572 transform = this._proxy.style[prop]; 4573 4574 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4575 4576 // workaround for case when transform is the same and so transitionend event is not fired 4577 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4578 this._onZoomTransitionEnd(); 4579 } 4580 }, this); 4581 4582 this.on('load moveend', this._animMoveEnd, this); 4583 4584 this._on('unload', this._destroyAnimProxy, this); 4585 }, 4586 4587 _destroyAnimProxy: function () { 4588 remove(this._proxy); 4589 this.off('load moveend', this._animMoveEnd, this); 4590 delete this._proxy; 4591 }, 4592 4593 _animMoveEnd: function () { 4594 var c = this.getCenter(), 4595 z = this.getZoom(); 4596 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 4597 }, 4598 4599 _catchTransitionEnd: function (e) { 4600 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 4601 this._onZoomTransitionEnd(); 4602 } 4603 }, 4604 4605 _nothingToAnimate: function () { 4606 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 4607 }, 4608 4609 _tryAnimatedZoom: function (center, zoom, options) { 4610 4611 if (this._animatingZoom) { return true; } 4612 4613 options = options || {}; 4614 4615 // don't animate if disabled, not supported or zoom difference is too large 4616 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 4617 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 4618 4619 // offset is the pixel coords of the zoom origin relative to the current center 4620 var scale = this.getZoomScale(zoom), 4621 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 4622 4623 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 4624 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 4625 4626 requestAnimFrame(function () { 4627 this 4628 ._moveStart(true, false) 4629 ._animateZoom(center, zoom, true); 4630 }, this); 4631 4632 return true; 4633 }, 4634 4635 _animateZoom: function (center, zoom, startAnim, noUpdate) { 4636 if (!this._mapPane) { return; } 4637 4638 if (startAnim) { 4639 this._animatingZoom = true; 4640 4641 // remember what center/zoom to set after animation 4642 this._animateToCenter = center; 4643 this._animateToZoom = zoom; 4644 4645 addClass(this._mapPane, 'leaflet-zoom-anim'); 4646 } 4647 4648 // @section Other Events 4649 // @event zoomanim: ZoomAnimEvent 4650 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom. 4651 this.fire('zoomanim', { 4652 center: center, 4653 zoom: zoom, 4654 noUpdate: noUpdate 4655 }); 4656 4657 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 4658 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 4659 }, 4660 4661 _onZoomTransitionEnd: function () { 4662 if (!this._animatingZoom) { return; } 4663 4664 if (this._mapPane) { 4665 removeClass(this._mapPane, 'leaflet-zoom-anim'); 4666 } 4667 4668 this._animatingZoom = false; 4669 4670 this._move(this._animateToCenter, this._animateToZoom); 4671 4672 // This anim frame should prevent an obscure iOS webkit tile loading race condition. 4673 requestAnimFrame(function () { 4674 this._moveEnd(true); 4675 }, this); 4676 } 4677 }); 4678 4679 // @section 4680 4681 // @factory L.map(id: String, options?: Map options) 4682 // Instantiates a map object given the DOM ID of a `<div>` element 4683 // and optionally an object literal with `Map options`. 4684 // 4685 // @alternative 4686 // @factory L.map(el: HTMLElement, options?: Map options) 4687 // Instantiates a map object given an instance of a `<div>` HTML element 4688 // and optionally an object literal with `Map options`. 4689 function createMap(id, options) { 4690 return new Map(id, options); 4691 } 4692 4693 /* 4694 * @class Control 4695 * @aka L.Control 4696 * @inherits Class 4697 * 4698 * L.Control is a base class for implementing map controls. Handles positioning. 4699 * All other controls extend from this class. 4700 */ 4701 4702 var Control = Class.extend({ 4703 // @section 4704 // @aka Control Options 4705 options: { 4706 // @option position: String = 'topright' 4707 // The position of the control (one of the map corners). Possible values are `'topleft'`, 4708 // `'topright'`, `'bottomleft'` or `'bottomright'` 4709 position: 'topright' 4710 }, 4711 4712 initialize: function (options) { 4713 setOptions(this, options); 4714 }, 4715 4716 /* @section 4717 * Classes extending L.Control will inherit the following methods: 4718 * 4719 * @method getPosition: string 4720 * Returns the position of the control. 4721 */ 4722 getPosition: function () { 4723 return this.options.position; 4724 }, 4725 4726 // @method setPosition(position: string): this 4727 // Sets the position of the control. 4728 setPosition: function (position) { 4729 var map = this._map; 4730 4731 if (map) { 4732 map.removeControl(this); 4733 } 4734 4735 this.options.position = position; 4736 4737 if (map) { 4738 map.addControl(this); 4739 } 4740 4741 return this; 4742 }, 4743 4744 // @method getContainer: HTMLElement 4745 // Returns the HTMLElement that contains the control. 4746 getContainer: function () { 4747 return this._container; 4748 }, 4749 4750 // @method addTo(map: Map): this 4751 // Adds the control to the given map. 4752 addTo: function (map) { 4753 this.remove(); 4754 this._map = map; 4755 4756 var container = this._container = this.onAdd(map), 4757 pos = this.getPosition(), 4758 corner = map._controlCorners[pos]; 4759 4760 addClass(container, 'leaflet-control'); 4761 4762 if (pos.indexOf('bottom') !== -1) { 4763 corner.insertBefore(container, corner.firstChild); 4764 } else { 4765 corner.appendChild(container); 4766 } 4767 4768 this._map.on('unload', this.remove, this); 4769 4770 return this; 4771 }, 4772 4773 // @method remove: this 4774 // Removes the control from the map it is currently active on. 4775 remove: function () { 4776 if (!this._map) { 4777 return this; 4778 } 4779 4780 remove(this._container); 4781 4782 if (this.onRemove) { 4783 this.onRemove(this._map); 4784 } 4785 4786 this._map.off('unload', this.remove, this); 4787 this._map = null; 4788 4789 return this; 4790 }, 4791 4792 _refocusOnMap: function (e) { 4793 // if map exists and event is not a keyboard event 4794 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 4795 this._map.getContainer().focus(); 4796 } 4797 } 4798 }); 4799 4800 var control = function (options) { 4801 return new Control(options); 4802 }; 4803 4804 /* @section Extension methods 4805 * @uninheritable 4806 * 4807 * Every control should extend from `L.Control` and (re-)implement the following methods. 4808 * 4809 * @method onAdd(map: Map): HTMLElement 4810 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 4811 * 4812 * @method onRemove(map: Map) 4813 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 4814 */ 4815 4816 /* @namespace Map 4817 * @section Methods for Layers and Controls 4818 */ 4819 Map.include({ 4820 // @method addControl(control: Control): this 4821 // Adds the given control to the map 4822 addControl: function (control) { 4823 control.addTo(this); 4824 return this; 4825 }, 4826 4827 // @method removeControl(control: Control): this 4828 // Removes the given control from the map 4829 removeControl: function (control) { 4830 control.remove(); 4831 return this; 4832 }, 4833 4834 _initControlPos: function () { 4835 var corners = this._controlCorners = {}, 4836 l = 'leaflet-', 4837 container = this._controlContainer = 4838 create$1('div', l + 'control-container', this._container); 4839 4840 function createCorner(vSide, hSide) { 4841 var className = l + vSide + ' ' + l + hSide; 4842 4843 corners[vSide + hSide] = create$1('div', className, container); 4844 } 4845 4846 createCorner('top', 'left'); 4847 createCorner('top', 'right'); 4848 createCorner('bottom', 'left'); 4849 createCorner('bottom', 'right'); 4850 }, 4851 4852 _clearControlPos: function () { 4853 for (var i in this._controlCorners) { 4854 remove(this._controlCorners[i]); 4855 } 4856 remove(this._controlContainer); 4857 delete this._controlCorners; 4858 delete this._controlContainer; 4859 } 4860 }); 4861 4862 /* 4863 * @class Control.Layers 4864 * @aka L.Control.Layers 4865 * @inherits Control 4866 * 4867 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`. 4868 * 4869 * @example 4870 * 4871 * ```js 4872 * var baseLayers = { 4873 * "Mapbox": mapbox, 4874 * "OpenStreetMap": osm 4875 * }; 4876 * 4877 * var overlays = { 4878 * "Marker": marker, 4879 * "Roads": roadsLayer 4880 * }; 4881 * 4882 * L.control.layers(baseLayers, overlays).addTo(map); 4883 * ``` 4884 * 4885 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 4886 * 4887 * ```js 4888 * { 4889 * "<someName1>": layer1, 4890 * "<someName2>": layer2 4891 * } 4892 * ``` 4893 * 4894 * The layer names can contain HTML, which allows you to add additional styling to the items: 4895 * 4896 * ```js 4897 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 4898 * ``` 4899 */ 4900 4901 var Layers = Control.extend({ 4902 // @section 4903 // @aka Control.Layers options 4904 options: { 4905 // @option collapsed: Boolean = true 4906 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. 4907 collapsed: true, 4908 position: 'topright', 4909 4910 // @option autoZIndex: Boolean = true 4911 // 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. 4912 autoZIndex: true, 4913 4914 // @option hideSingleBase: Boolean = false 4915 // If `true`, the base layers in the control will be hidden when there is only one. 4916 hideSingleBase: false, 4917 4918 // @option sortLayers: Boolean = false 4919 // Whether to sort the layers. When `false`, layers will keep the order 4920 // in which they were added to the control. 4921 sortLayers: false, 4922 4923 // @option sortFunction: Function = * 4924 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 4925 // that will be used for sorting the layers, when `sortLayers` is `true`. 4926 // The function receives both the `L.Layer` instances and their names, as in 4927 // `sortFunction(layerA, layerB, nameA, nameB)`. 4928 // By default, it sorts layers alphabetically by their name. 4929 sortFunction: function (layerA, layerB, nameA, nameB) { 4930 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 4931 } 4932 }, 4933 4934 initialize: function (baseLayers, overlays, options) { 4935 setOptions(this, options); 4936 4937 this._layerControlInputs = []; 4938 this._layers = []; 4939 this._lastZIndex = 0; 4940 this._handlingClick = false; 4941 4942 for (var i in baseLayers) { 4943 this._addLayer(baseLayers[i], i); 4944 } 4945 4946 for (i in overlays) { 4947 this._addLayer(overlays[i], i, true); 4948 } 4949 }, 4950 4951 onAdd: function (map) { 4952 this._initLayout(); 4953 this._update(); 4954 4955 this._map = map; 4956 map.on('zoomend', this._checkDisabledLayers, this); 4957 4958 for (var i = 0; i < this._layers.length; i++) { 4959 this._layers[i].layer.on('add remove', this._onLayerChange, this); 4960 } 4961 4962 return this._container; 4963 }, 4964 4965 addTo: function (map) { 4966 Control.prototype.addTo.call(this, map); 4967 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 4968 return this._expandIfNotCollapsed(); 4969 }, 4970 4971 onRemove: function () { 4972 this._map.off('zoomend', this._checkDisabledLayers, this); 4973 4974 for (var i = 0; i < this._layers.length; i++) { 4975 this._layers[i].layer.off('add remove', this._onLayerChange, this); 4976 } 4977 }, 4978 4979 // @method addBaseLayer(layer: Layer, name: String): this 4980 // Adds a base layer (radio button entry) with the given name to the control. 4981 addBaseLayer: function (layer, name) { 4982 this._addLayer(layer, name); 4983 return (this._map) ? this._update() : this; 4984 }, 4985 4986 // @method addOverlay(layer: Layer, name: String): this 4987 // Adds an overlay (checkbox entry) with the given name to the control. 4988 addOverlay: function (layer, name) { 4989 this._addLayer(layer, name, true); 4990 return (this._map) ? this._update() : this; 4991 }, 4992 4993 // @method removeLayer(layer: Layer): this 4994 // Remove the given layer from the control. 4995 removeLayer: function (layer) { 4996 layer.off('add remove', this._onLayerChange, this); 4997 4998 var obj = this._getLayer(stamp(layer)); 4999 if (obj) { 5000 this._layers.splice(this._layers.indexOf(obj), 1); 5001 } 5002 return (this._map) ? this._update() : this; 5003 }, 5004 5005 // @method expand(): this 5006 // Expand the control container if collapsed. 5007 expand: function () { 5008 addClass(this._container, 'leaflet-control-layers-expanded'); 5009 this._section.style.height = null; 5010 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 5011 if (acceptableHeight < this._section.clientHeight) { 5012 addClass(this._section, 'leaflet-control-layers-scrollbar'); 5013 this._section.style.height = acceptableHeight + 'px'; 5014 } else { 5015 removeClass(this._section, 'leaflet-control-layers-scrollbar'); 5016 } 5017 this._checkDisabledLayers(); 5018 return this; 5019 }, 5020 5021 // @method collapse(): this 5022 // Collapse the control container if expanded. 5023 collapse: function () { 5024 removeClass(this._container, 'leaflet-control-layers-expanded'); 5025 return this; 5026 }, 5027 5028 _initLayout: function () { 5029 var className = 'leaflet-control-layers', 5030 container = this._container = create$1('div', className), 5031 collapsed = this.options.collapsed; 5032 5033 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 5034 container.setAttribute('aria-haspopup', true); 5035 5036 disableClickPropagation(container); 5037 disableScrollPropagation(container); 5038 5039 var section = this._section = create$1('section', className + '-list'); 5040 5041 if (collapsed) { 5042 this._map.on('click', this.collapse, this); 5043 5044 on(container, { 5045 mouseenter: function () { 5046 on(section, 'click', preventDefault); 5047 this.expand(); 5048 setTimeout(function () { 5049 off(section, 'click', preventDefault); 5050 }); 5051 }, 5052 mouseleave: this.collapse 5053 }, this); 5054 } 5055 5056 var link = this._layersLink = create$1('a', className + '-toggle', container); 5057 link.href = '#'; 5058 link.title = 'Layers'; 5059 5060 on(link, 'click', preventDefault); // prevent link function 5061 on(link, 'focus', this.expand, this); 5062 5063 if (!collapsed) { 5064 this.expand(); 5065 } 5066 5067 this._baseLayersList = create$1('div', className + '-base', section); 5068 this._separator = create$1('div', className + '-separator', section); 5069 this._overlaysList = create$1('div', className + '-overlays', section); 5070 5071 container.appendChild(section); 5072 }, 5073 5074 _getLayer: function (id) { 5075 for (var i = 0; i < this._layers.length; i++) { 5076 5077 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5078 return this._layers[i]; 5079 } 5080 } 5081 }, 5082 5083 _addLayer: function (layer, name, overlay) { 5084 if (this._map) { 5085 layer.on('add remove', this._onLayerChange, this); 5086 } 5087 5088 this._layers.push({ 5089 layer: layer, 5090 name: name, 5091 overlay: overlay 5092 }); 5093 5094 if (this.options.sortLayers) { 5095 this._layers.sort(bind(function (a, b) { 5096 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5097 }, this)); 5098 } 5099 5100 if (this.options.autoZIndex && layer.setZIndex) { 5101 this._lastZIndex++; 5102 layer.setZIndex(this._lastZIndex); 5103 } 5104 5105 this._expandIfNotCollapsed(); 5106 }, 5107 5108 _update: function () { 5109 if (!this._container) { return this; } 5110 5111 empty(this._baseLayersList); 5112 empty(this._overlaysList); 5113 5114 this._layerControlInputs = []; 5115 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5116 5117 for (i = 0; i < this._layers.length; i++) { 5118 obj = this._layers[i]; 5119 this._addItem(obj); 5120 overlaysPresent = overlaysPresent || obj.overlay; 5121 baseLayersPresent = baseLayersPresent || !obj.overlay; 5122 baseLayersCount += !obj.overlay ? 1 : 0; 5123 } 5124 5125 // Hide base layers section if there's only one layer. 5126 if (this.options.hideSingleBase) { 5127 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5128 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5129 } 5130 5131 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5132 5133 return this; 5134 }, 5135 5136 _onLayerChange: function (e) { 5137 if (!this._handlingClick) { 5138 this._update(); 5139 } 5140 5141 var obj = this._getLayer(stamp(e.target)); 5142 5143 // @namespace Map 5144 // @section Layer events 5145 // @event baselayerchange: LayersControlEvent 5146 // Fired when the base layer is changed through the [layers control](#control-layers). 5147 // @event overlayadd: LayersControlEvent 5148 // Fired when an overlay is selected through the [layers control](#control-layers). 5149 // @event overlayremove: LayersControlEvent 5150 // Fired when an overlay is deselected through the [layers control](#control-layers). 5151 // @namespace Control.Layers 5152 var type = obj.overlay ? 5153 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5154 (e.type === 'add' ? 'baselayerchange' : null); 5155 5156 if (type) { 5157 this._map.fire(type, obj); 5158 } 5159 }, 5160 5161 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079) 5162 _createRadioElement: function (name, checked) { 5163 5164 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5165 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5166 5167 var radioFragment = document.createElement('div'); 5168 radioFragment.innerHTML = radioHtml; 5169 5170 return radioFragment.firstChild; 5171 }, 5172 5173 _addItem: function (obj) { 5174 var label = document.createElement('label'), 5175 checked = this._map.hasLayer(obj.layer), 5176 input; 5177 5178 if (obj.overlay) { 5179 input = document.createElement('input'); 5180 input.type = 'checkbox'; 5181 input.className = 'leaflet-control-layers-selector'; 5182 input.defaultChecked = checked; 5183 } else { 5184 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked); 5185 } 5186 5187 this._layerControlInputs.push(input); 5188 input.layerId = stamp(obj.layer); 5189 5190 on(input, 'click', this._onInputClick, this); 5191 5192 var name = document.createElement('span'); 5193 name.innerHTML = ' ' + obj.name; 5194 5195 // Helps from preventing layer control flicker when checkboxes are disabled 5196 // https://github.com/Leaflet/Leaflet/issues/2771 5197 var holder = document.createElement('div'); 5198 5199 label.appendChild(holder); 5200 holder.appendChild(input); 5201 holder.appendChild(name); 5202 5203 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5204 container.appendChild(label); 5205 5206 this._checkDisabledLayers(); 5207 return label; 5208 }, 5209 5210 _onInputClick: function () { 5211 var inputs = this._layerControlInputs, 5212 input, layer; 5213 var addedLayers = [], 5214 removedLayers = []; 5215 5216 this._handlingClick = true; 5217 5218 for (var i = inputs.length - 1; i >= 0; i--) { 5219 input = inputs[i]; 5220 layer = this._getLayer(input.layerId).layer; 5221 5222 if (input.checked) { 5223 addedLayers.push(layer); 5224 } else if (!input.checked) { 5225 removedLayers.push(layer); 5226 } 5227 } 5228 5229 // Bugfix issue 2318: Should remove all old layers before readding new ones 5230 for (i = 0; i < removedLayers.length; i++) { 5231 if (this._map.hasLayer(removedLayers[i])) { 5232 this._map.removeLayer(removedLayers[i]); 5233 } 5234 } 5235 for (i = 0; i < addedLayers.length; i++) { 5236 if (!this._map.hasLayer(addedLayers[i])) { 5237 this._map.addLayer(addedLayers[i]); 5238 } 5239 } 5240 5241 this._handlingClick = false; 5242 5243 this._refocusOnMap(); 5244 }, 5245 5246 _checkDisabledLayers: function () { 5247 var inputs = this._layerControlInputs, 5248 input, 5249 layer, 5250 zoom = this._map.getZoom(); 5251 5252 for (var i = inputs.length - 1; i >= 0; i--) { 5253 input = inputs[i]; 5254 layer = this._getLayer(input.layerId).layer; 5255 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5256 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5257 5258 } 5259 }, 5260 5261 _expandIfNotCollapsed: function () { 5262 if (this._map && !this.options.collapsed) { 5263 this.expand(); 5264 } 5265 return this; 5266 } 5267 5268 }); 5269 5270 5271 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5272 // 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. 5273 var layers = function (baseLayers, overlays, options) { 5274 return new Layers(baseLayers, overlays, options); 5275 }; 5276 5277 /* 5278 * @class Control.Zoom 5279 * @aka L.Control.Zoom 5280 * @inherits Control 5281 * 5282 * 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`. 5283 */ 5284 5285 var Zoom = Control.extend({ 5286 // @section 5287 // @aka Control.Zoom options 5288 options: { 5289 position: 'topleft', 5290 5291 // @option zoomInText: String = '+' 5292 // The text set on the 'zoom in' button. 5293 zoomInText: '<span aria-hidden="true">+</span>', 5294 5295 // @option zoomInTitle: String = 'Zoom in' 5296 // The title set on the 'zoom in' button. 5297 zoomInTitle: 'Zoom in', 5298 5299 // @option zoomOutText: String = '−' 5300 // The text set on the 'zoom out' button. 5301 zoomOutText: '<span aria-hidden="true">−</span>', 5302 5303 // @option zoomOutTitle: String = 'Zoom out' 5304 // The title set on the 'zoom out' button. 5305 zoomOutTitle: 'Zoom out' 5306 }, 5307 5308 onAdd: function (map) { 5309 var zoomName = 'leaflet-control-zoom', 5310 container = create$1('div', zoomName + ' leaflet-bar'), 5311 options = this.options; 5312 5313 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5314 zoomName + '-in', container, this._zoomIn); 5315 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5316 zoomName + '-out', container, this._zoomOut); 5317 5318 this._updateDisabled(); 5319 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5320 5321 return container; 5322 }, 5323 5324 onRemove: function (map) { 5325 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5326 }, 5327 5328 disable: function () { 5329 this._disabled = true; 5330 this._updateDisabled(); 5331 return this; 5332 }, 5333 5334 enable: function () { 5335 this._disabled = false; 5336 this._updateDisabled(); 5337 return this; 5338 }, 5339 5340 _zoomIn: function (e) { 5341 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5342 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5343 } 5344 }, 5345 5346 _zoomOut: function (e) { 5347 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5348 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5349 } 5350 }, 5351 5352 _createButton: function (html, title, className, container, fn) { 5353 var link = create$1('a', className, container); 5354 link.innerHTML = html; 5355 link.href = '#'; 5356 link.title = title; 5357 5358 /* 5359 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5360 */ 5361 link.setAttribute('role', 'button'); 5362 link.setAttribute('aria-label', title); 5363 5364 disableClickPropagation(link); 5365 on(link, 'click', stop); 5366 on(link, 'click', fn, this); 5367 on(link, 'click', this._refocusOnMap, this); 5368 5369 return link; 5370 }, 5371 5372 _updateDisabled: function () { 5373 var map = this._map, 5374 className = 'leaflet-disabled'; 5375 5376 removeClass(this._zoomInButton, className); 5377 removeClass(this._zoomOutButton, className); 5378 this._zoomInButton.setAttribute('aria-disabled', 'false'); 5379 this._zoomOutButton.setAttribute('aria-disabled', 'false'); 5380 5381 if (this._disabled || map._zoom === map.getMinZoom()) { 5382 addClass(this._zoomOutButton, className); 5383 this._zoomOutButton.setAttribute('aria-disabled', 'true'); 5384 } 5385 if (this._disabled || map._zoom === map.getMaxZoom()) { 5386 addClass(this._zoomInButton, className); 5387 this._zoomInButton.setAttribute('aria-disabled', 'true'); 5388 } 5389 } 5390 }); 5391 5392 // @namespace Map 5393 // @section Control options 5394 // @option zoomControl: Boolean = true 5395 // Whether a [zoom control](#control-zoom) is added to the map by default. 5396 Map.mergeOptions({ 5397 zoomControl: true 5398 }); 5399 5400 Map.addInitHook(function () { 5401 if (this.options.zoomControl) { 5402 // @section Controls 5403 // @property zoomControl: Control.Zoom 5404 // The default zoom control (only available if the 5405 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5406 this.zoomControl = new Zoom(); 5407 this.addControl(this.zoomControl); 5408 } 5409 }); 5410 5411 // @namespace Control.Zoom 5412 // @factory L.control.zoom(options: Control.Zoom options) 5413 // Creates a zoom control 5414 var zoom = function (options) { 5415 return new Zoom(options); 5416 }; 5417 5418 /* 5419 * @class Control.Scale 5420 * @aka L.Control.Scale 5421 * @inherits Control 5422 * 5423 * 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`. 5424 * 5425 * @example 5426 * 5427 * ```js 5428 * L.control.scale().addTo(map); 5429 * ``` 5430 */ 5431 5432 var Scale = Control.extend({ 5433 // @section 5434 // @aka Control.Scale options 5435 options: { 5436 position: 'bottomleft', 5437 5438 // @option maxWidth: Number = 100 5439 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5440 maxWidth: 100, 5441 5442 // @option metric: Boolean = True 5443 // Whether to show the metric scale line (m/km). 5444 metric: true, 5445 5446 // @option imperial: Boolean = True 5447 // Whether to show the imperial scale line (mi/ft). 5448 imperial: true 5449 5450 // @option updateWhenIdle: Boolean = false 5451 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5452 }, 5453 5454 onAdd: function (map) { 5455 var className = 'leaflet-control-scale', 5456 container = create$1('div', className), 5457 options = this.options; 5458 5459 this._addScales(options, className + '-line', container); 5460 5461 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5462 map.whenReady(this._update, this); 5463 5464 return container; 5465 }, 5466 5467 onRemove: function (map) { 5468 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5469 }, 5470 5471 _addScales: function (options, className, container) { 5472 if (options.metric) { 5473 this._mScale = create$1('div', className, container); 5474 } 5475 if (options.imperial) { 5476 this._iScale = create$1('div', className, container); 5477 } 5478 }, 5479 5480 _update: function () { 5481 var map = this._map, 5482 y = map.getSize().y / 2; 5483 5484 var maxMeters = map.distance( 5485 map.containerPointToLatLng([0, y]), 5486 map.containerPointToLatLng([this.options.maxWidth, y])); 5487 5488 this._updateScales(maxMeters); 5489 }, 5490 5491 _updateScales: function (maxMeters) { 5492 if (this.options.metric && maxMeters) { 5493 this._updateMetric(maxMeters); 5494 } 5495 if (this.options.imperial && maxMeters) { 5496 this._updateImperial(maxMeters); 5497 } 5498 }, 5499 5500 _updateMetric: function (maxMeters) { 5501 var meters = this._getRoundNum(maxMeters), 5502 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5503 5504 this._updateScale(this._mScale, label, meters / maxMeters); 5505 }, 5506 5507 _updateImperial: function (maxMeters) { 5508 var maxFeet = maxMeters * 3.2808399, 5509 maxMiles, miles, feet; 5510 5511 if (maxFeet > 5280) { 5512 maxMiles = maxFeet / 5280; 5513 miles = this._getRoundNum(maxMiles); 5514 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5515 5516 } else { 5517 feet = this._getRoundNum(maxFeet); 5518 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5519 } 5520 }, 5521 5522 _updateScale: function (scale, text, ratio) { 5523 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5524 scale.innerHTML = text; 5525 }, 5526 5527 _getRoundNum: function (num) { 5528 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5529 d = num / pow10; 5530 5531 d = d >= 10 ? 10 : 5532 d >= 5 ? 5 : 5533 d >= 3 ? 3 : 5534 d >= 2 ? 2 : 1; 5535 5536 return pow10 * d; 5537 } 5538 }); 5539 5540 5541 // @factory L.control.scale(options?: Control.Scale options) 5542 // Creates an scale control with the given options. 5543 var scale = function (options) { 5544 return new Scale(options); 5545 }; 5546 5547 /* 5548 * @class Control.Attribution 5549 * @aka L.Control.Attribution 5550 * @inherits Control 5551 * 5552 * 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. 5553 */ 5554 5555 var Attribution = Control.extend({ 5556 // @section 5557 // @aka Control.Attribution options 5558 options: { 5559 position: 'bottomright', 5560 5561 // @option prefix: String = 'Leaflet' 5562 // The HTML text shown before the attributions. Pass `false` to disable. 5563 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">Leaflet</a>' 5564 5565 }, 5566 5567 initialize: function (options) { 5568 setOptions(this, options); 5569 5570 this._attributions = {}; 5571 }, 5572 5573 onAdd: function (map) { 5574 map.attributionControl = this; 5575 this._container = create$1('div', 'leaflet-control-attribution'); 5576 disableClickPropagation(this._container); 5577 5578 // TODO ugly, refactor 5579 for (var i in map._layers) { 5580 if (map._layers[i].getAttribution) { 5581 this.addAttribution(map._layers[i].getAttribution()); 5582 } 5583 } 5584 5585 this._update(); 5586 5587 map.on('layeradd', this._addAttribution, this); 5588 5589 return this._container; 5590 }, 5591 5592 onRemove: function (map) { 5593 map.off('layeradd', this._addAttribution, this); 5594 }, 5595 5596 _addAttribution: function (ev) { 5597 if (ev.layer.getAttribution) { 5598 this.addAttribution(ev.layer.getAttribution()); 5599 ev.layer.once('remove', function () { 5600 this.removeAttribution(ev.layer.getAttribution()); 5601 }, this); 5602 } 5603 }, 5604 5605 // @method setPrefix(prefix: String): this 5606 // Sets the text before the attributions. 5607 setPrefix: function (prefix) { 5608 this.options.prefix = prefix; 5609 this._update(); 5610 return this; 5611 }, 5612 5613 // @method addAttribution(text: String): this 5614 // Adds an attribution text (e.g. `'Vector data © Mapbox'`). 5615 addAttribution: function (text) { 5616 if (!text) { return this; } 5617 5618 if (!this._attributions[text]) { 5619 this._attributions[text] = 0; 5620 } 5621 this._attributions[text]++; 5622 5623 this._update(); 5624 5625 return this; 5626 }, 5627 5628 // @method removeAttribution(text: String): this 5629 // Removes an attribution text. 5630 removeAttribution: function (text) { 5631 if (!text) { return this; } 5632 5633 if (this._attributions[text]) { 5634 this._attributions[text]--; 5635 this._update(); 5636 } 5637 5638 return this; 5639 }, 5640 5641 _update: function () { 5642 if (!this._map) { return; } 5643 5644 var attribs = []; 5645 5646 for (var i in this._attributions) { 5647 if (this._attributions[i]) { 5648 attribs.push(i); 5649 } 5650 } 5651 5652 var prefixAndAttribs = []; 5653 5654 if (this.options.prefix) { 5655 prefixAndAttribs.push(this.options.prefix); 5656 } 5657 if (attribs.length) { 5658 prefixAndAttribs.push(attribs.join(', ')); 5659 } 5660 5661 this._container.innerHTML = prefixAndAttribs.join(' | '); 5662 } 5663 }); 5664 5665 // @namespace Map 5666 // @section Control options 5667 // @option attributionControl: Boolean = true 5668 // Whether a [attribution control](#control-attribution) is added to the map by default. 5669 Map.mergeOptions({ 5670 attributionControl: true 5671 }); 5672 5673 Map.addInitHook(function () { 5674 if (this.options.attributionControl) { 5675 new Attribution().addTo(this); 5676 } 5677 }); 5678 5679 // @namespace Control.Attribution 5680 // @factory L.control.attribution(options: Control.Attribution options) 5681 // Creates an attribution control. 5682 var attribution = function (options) { 5683 return new Attribution(options); 5684 }; 5685 5686 Control.Layers = Layers; 5687 Control.Zoom = Zoom; 5688 Control.Scale = Scale; 5689 Control.Attribution = Attribution; 5690 5691 control.layers = layers; 5692 control.zoom = zoom; 5693 control.scale = scale; 5694 control.attribution = attribution; 5695 5696 /* 5697 L.Handler is a base class for handler classes that are used internally to inject 5698 interaction features like dragging to classes like Map and Marker. 5699 */ 5700 5701 // @class Handler 5702 // @aka L.Handler 5703 // Abstract class for map interaction handlers 5704 5705 var Handler = Class.extend({ 5706 initialize: function (map) { 5707 this._map = map; 5708 }, 5709 5710 // @method enable(): this 5711 // Enables the handler 5712 enable: function () { 5713 if (this._enabled) { return this; } 5714 5715 this._enabled = true; 5716 this.addHooks(); 5717 return this; 5718 }, 5719 5720 // @method disable(): this 5721 // Disables the handler 5722 disable: function () { 5723 if (!this._enabled) { return this; } 5724 5725 this._enabled = false; 5726 this.removeHooks(); 5727 return this; 5728 }, 5729 5730 // @method enabled(): Boolean 5731 // Returns `true` if the handler is enabled 5732 enabled: function () { 5733 return !!this._enabled; 5734 } 5735 5736 // @section Extension methods 5737 // Classes inheriting from `Handler` must implement the two following methods: 5738 // @method addHooks() 5739 // Called when the handler is enabled, should add event hooks. 5740 // @method removeHooks() 5741 // Called when the handler is disabled, should remove the event hooks added previously. 5742 }); 5743 5744 // @section There is static function which can be called without instantiating L.Handler: 5745 // @function addTo(map: Map, name: String): this 5746 // Adds a new Handler to the given map with the given name. 5747 Handler.addTo = function (map, name) { 5748 map.addHandler(name, this); 5749 return this; 5750 }; 5751 5752 var Mixin = {Events: Events}; 5753 5754 /* 5755 * @class Draggable 5756 * @aka L.Draggable 5757 * @inherits Evented 5758 * 5759 * A class for making DOM elements draggable (including touch support). 5760 * Used internally for map and marker dragging. Only works for elements 5761 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 5762 * 5763 * @example 5764 * ```js 5765 * var draggable = new L.Draggable(elementToDrag); 5766 * draggable.enable(); 5767 * ``` 5768 */ 5769 5770 var START = touch ? 'touchstart mousedown' : 'mousedown'; 5771 5772 var Draggable = Evented.extend({ 5773 5774 options: { 5775 // @section 5776 // @aka Draggable options 5777 // @option clickTolerance: Number = 3 5778 // The max number of pixels a user can shift the mouse pointer during a click 5779 // for it to be considered a valid click (as opposed to a mouse drag). 5780 clickTolerance: 3 5781 }, 5782 5783 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 5784 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 5785 initialize: function (element, dragStartTarget, preventOutline, options) { 5786 setOptions(this, options); 5787 5788 this._element = element; 5789 this._dragStartTarget = dragStartTarget || element; 5790 this._preventOutline = preventOutline; 5791 }, 5792 5793 // @method enable() 5794 // Enables the dragging ability 5795 enable: function () { 5796 if (this._enabled) { return; } 5797 5798 on(this._dragStartTarget, START, this._onDown, this); 5799 5800 this._enabled = true; 5801 }, 5802 5803 // @method disable() 5804 // Disables the dragging ability 5805 disable: function () { 5806 if (!this._enabled) { return; } 5807 5808 // If we're currently dragging this draggable, 5809 // disabling it counts as first ending the drag. 5810 if (Draggable._dragging === this) { 5811 this.finishDrag(); 5812 } 5813 5814 off(this._dragStartTarget, START, this._onDown, this); 5815 5816 this._enabled = false; 5817 this._moved = false; 5818 }, 5819 5820 _onDown: function (e) { 5821 // Ignore the event if disabled; this happens in IE11 5822 // under some circumstances, see #3666. 5823 if (!this._enabled) { return; } 5824 5825 this._moved = false; 5826 5827 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 5828 5829 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 5830 Draggable._dragging = this; // Prevent dragging multiple objects at once. 5831 5832 if (this._preventOutline) { 5833 preventOutline(this._element); 5834 } 5835 5836 disableImageDrag(); 5837 disableTextSelection(); 5838 5839 if (this._moving) { return; } 5840 5841 // @event down: Event 5842 // Fired when a drag is about to start. 5843 this.fire('down'); 5844 5845 var first = e.touches ? e.touches[0] : e, 5846 sizedParent = getSizedParentNode(this._element); 5847 5848 this._startPoint = new Point(first.clientX, first.clientY); 5849 5850 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 5851 this._parentScale = getScale(sizedParent); 5852 5853 var mouseevent = e.type === 'mousedown'; 5854 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this); 5855 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this); 5856 }, 5857 5858 _onMove: function (e) { 5859 // Ignore the event if disabled; this happens in IE11 5860 // under some circumstances, see #3666. 5861 if (!this._enabled) { return; } 5862 5863 if (e.touches && e.touches.length > 1) { 5864 this._moved = true; 5865 return; 5866 } 5867 5868 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 5869 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 5870 5871 if (!offset.x && !offset.y) { return; } 5872 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 5873 5874 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 5875 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 5876 // and we can use the cached value for the scale. 5877 offset.x /= this._parentScale.x; 5878 offset.y /= this._parentScale.y; 5879 5880 preventDefault(e); 5881 5882 if (!this._moved) { 5883 // @event dragstart: Event 5884 // Fired when a drag starts 5885 this.fire('dragstart'); 5886 5887 this._moved = true; 5888 this._startPos = getPosition(this._element).subtract(offset); 5889 5890 addClass(document.body, 'leaflet-dragging'); 5891 5892 this._lastTarget = e.target || e.srcElement; 5893 // IE and Edge do not give the <use> element, so fetch it 5894 // if necessary 5895 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) { 5896 this._lastTarget = this._lastTarget.correspondingUseElement; 5897 } 5898 addClass(this._lastTarget, 'leaflet-drag-target'); 5899 } 5900 5901 this._newPos = this._startPos.add(offset); 5902 this._moving = true; 5903 5904 cancelAnimFrame(this._animRequest); 5905 this._lastEvent = e; 5906 this._animRequest = requestAnimFrame(this._updatePosition, this, true); 5907 }, 5908 5909 _updatePosition: function () { 5910 var e = {originalEvent: this._lastEvent}; 5911 5912 // @event predrag: Event 5913 // Fired continuously during dragging *before* each corresponding 5914 // update of the element's position. 5915 this.fire('predrag', e); 5916 setPosition(this._element, this._newPos); 5917 5918 // @event drag: Event 5919 // Fired continuously during dragging. 5920 this.fire('drag', e); 5921 }, 5922 5923 _onUp: function () { 5924 // Ignore the event if disabled; this happens in IE11 5925 // under some circumstances, see #3666. 5926 if (!this._enabled) { return; } 5927 this.finishDrag(); 5928 }, 5929 5930 finishDrag: function () { 5931 removeClass(document.body, 'leaflet-dragging'); 5932 5933 if (this._lastTarget) { 5934 removeClass(this._lastTarget, 'leaflet-drag-target'); 5935 this._lastTarget = null; 5936 } 5937 5938 off(document, 'mousemove touchmove', this._onMove, this); 5939 off(document, 'mouseup touchend touchcancel', this._onUp, this); 5940 5941 enableImageDrag(); 5942 enableTextSelection(); 5943 5944 if (this._moved && this._moving) { 5945 // ensure drag is not fired after dragend 5946 cancelAnimFrame(this._animRequest); 5947 5948 // @event dragend: DragEndEvent 5949 // Fired when the drag ends. 5950 this.fire('dragend', { 5951 distance: this._newPos.distanceTo(this._startPos) 5952 }); 5953 } 5954 5955 this._moving = false; 5956 Draggable._dragging = false; 5957 } 5958 5959 }); 5960 5961 /* 5962 * @namespace LineUtil 5963 * 5964 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 5965 */ 5966 5967 // Simplify polyline with vertex reduction and Douglas-Peucker simplification. 5968 // Improves rendering performance dramatically by lessening the number of points to draw. 5969 5970 // @function simplify(points: Point[], tolerance: Number): Point[] 5971 // Dramatically reduces the number of points in a polyline while retaining 5972 // its shape and returns a new array of simplified points, using the 5973 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). 5974 // Used for a huge performance boost when processing/displaying Leaflet polylines for 5975 // each zoom level and also reducing visual noise. tolerance affects the amount of 5976 // simplification (lesser value means higher quality but slower and with more points). 5977 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). 5978 function simplify(points, tolerance) { 5979 if (!tolerance || !points.length) { 5980 return points.slice(); 5981 } 5982 5983 var sqTolerance = tolerance * tolerance; 5984 5985 // stage 1: vertex reduction 5986 points = _reducePoints(points, sqTolerance); 5987 5988 // stage 2: Douglas-Peucker simplification 5989 points = _simplifyDP(points, sqTolerance); 5990 5991 return points; 5992 } 5993 5994 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 5995 // Returns the distance between point `p` and segment `p1` to `p2`. 5996 function pointToSegmentDistance(p, p1, p2) { 5997 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 5998 } 5999 6000 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 6001 // Returns the closest point from a point `p` on a segment `p1` to `p2`. 6002 function closestPointOnSegment(p, p1, p2) { 6003 return _sqClosestPointOnSegment(p, p1, p2); 6004 } 6005 6006 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm 6007 function _simplifyDP(points, sqTolerance) { 6008 6009 var len = points.length, 6010 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 6011 markers = new ArrayConstructor(len); 6012 6013 markers[0] = markers[len - 1] = 1; 6014 6015 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 6016 6017 var i, 6018 newPoints = []; 6019 6020 for (i = 0; i < len; i++) { 6021 if (markers[i]) { 6022 newPoints.push(points[i]); 6023 } 6024 } 6025 6026 return newPoints; 6027 } 6028 6029 function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6030 6031 var maxSqDist = 0, 6032 index, i, sqDist; 6033 6034 for (i = first + 1; i <= last - 1; i++) { 6035 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6036 6037 if (sqDist > maxSqDist) { 6038 index = i; 6039 maxSqDist = sqDist; 6040 } 6041 } 6042 6043 if (maxSqDist > sqTolerance) { 6044 markers[index] = 1; 6045 6046 _simplifyDPStep(points, markers, sqTolerance, first, index); 6047 _simplifyDPStep(points, markers, sqTolerance, index, last); 6048 } 6049 } 6050 6051 // reduce points that are too close to each other to a single point 6052 function _reducePoints(points, sqTolerance) { 6053 var reducedPoints = [points[0]]; 6054 6055 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6056 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6057 reducedPoints.push(points[i]); 6058 prev = i; 6059 } 6060 } 6061 if (prev < len - 1) { 6062 reducedPoints.push(points[len - 1]); 6063 } 6064 return reducedPoints; 6065 } 6066 6067 var _lastCode; 6068 6069 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean 6070 // Clips the segment a to b by rectangular bounds with the 6071 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6072 // (modifying the segment points directly!). Used by Leaflet to only show polyline 6073 // points that are on the screen or near, increasing performance. 6074 function clipSegment(a, b, bounds, useLastCode, round) { 6075 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6076 codeB = _getBitCode(b, bounds), 6077 6078 codeOut, p, newCode; 6079 6080 // save 2nd code to avoid calculating it on the next segment 6081 _lastCode = codeB; 6082 6083 while (true) { 6084 // if a,b is inside the clip window (trivial accept) 6085 if (!(codeA | codeB)) { 6086 return [a, b]; 6087 } 6088 6089 // if a,b is outside the clip window (trivial reject) 6090 if (codeA & codeB) { 6091 return false; 6092 } 6093 6094 // other cases 6095 codeOut = codeA || codeB; 6096 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6097 newCode = _getBitCode(p, bounds); 6098 6099 if (codeOut === codeA) { 6100 a = p; 6101 codeA = newCode; 6102 } else { 6103 b = p; 6104 codeB = newCode; 6105 } 6106 } 6107 } 6108 6109 function _getEdgeIntersection(a, b, code, bounds, round) { 6110 var dx = b.x - a.x, 6111 dy = b.y - a.y, 6112 min = bounds.min, 6113 max = bounds.max, 6114 x, y; 6115 6116 if (code & 8) { // top 6117 x = a.x + dx * (max.y - a.y) / dy; 6118 y = max.y; 6119 6120 } else if (code & 4) { // bottom 6121 x = a.x + dx * (min.y - a.y) / dy; 6122 y = min.y; 6123 6124 } else if (code & 2) { // right 6125 x = max.x; 6126 y = a.y + dy * (max.x - a.x) / dx; 6127 6128 } else if (code & 1) { // left 6129 x = min.x; 6130 y = a.y + dy * (min.x - a.x) / dx; 6131 } 6132 6133 return new Point(x, y, round); 6134 } 6135 6136 function _getBitCode(p, bounds) { 6137 var code = 0; 6138 6139 if (p.x < bounds.min.x) { // left 6140 code |= 1; 6141 } else if (p.x > bounds.max.x) { // right 6142 code |= 2; 6143 } 6144 6145 if (p.y < bounds.min.y) { // bottom 6146 code |= 4; 6147 } else if (p.y > bounds.max.y) { // top 6148 code |= 8; 6149 } 6150 6151 return code; 6152 } 6153 6154 // square distance (to avoid unnecessary Math.sqrt calls) 6155 function _sqDist(p1, p2) { 6156 var dx = p2.x - p1.x, 6157 dy = p2.y - p1.y; 6158 return dx * dx + dy * dy; 6159 } 6160 6161 // return closest point on segment or distance to that point 6162 function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6163 var x = p1.x, 6164 y = p1.y, 6165 dx = p2.x - x, 6166 dy = p2.y - y, 6167 dot = dx * dx + dy * dy, 6168 t; 6169 6170 if (dot > 0) { 6171 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6172 6173 if (t > 1) { 6174 x = p2.x; 6175 y = p2.y; 6176 } else if (t > 0) { 6177 x += dx * t; 6178 y += dy * t; 6179 } 6180 } 6181 6182 dx = p.x - x; 6183 dy = p.y - y; 6184 6185 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6186 } 6187 6188 6189 // @function isFlat(latlngs: LatLng[]): Boolean 6190 // Returns true if `latlngs` is a flat array, false is nested. 6191 function isFlat(latlngs) { 6192 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6193 } 6194 6195 function _flat(latlngs) { 6196 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6197 return isFlat(latlngs); 6198 } 6199 6200 var LineUtil = { 6201 __proto__: null, 6202 simplify: simplify, 6203 pointToSegmentDistance: pointToSegmentDistance, 6204 closestPointOnSegment: closestPointOnSegment, 6205 clipSegment: clipSegment, 6206 _getEdgeIntersection: _getEdgeIntersection, 6207 _getBitCode: _getBitCode, 6208 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6209 isFlat: isFlat, 6210 _flat: _flat 6211 }; 6212 6213 /* 6214 * @namespace PolyUtil 6215 * Various utility functions for polygon geometries. 6216 */ 6217 6218 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6219 * 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)). 6220 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6221 * performance. Note that polygon points needs different algorithm for clipping 6222 * than polyline, so there's a separate method for it. 6223 */ 6224 function clipPolygon(points, bounds, round) { 6225 var clippedPoints, 6226 edges = [1, 4, 2, 8], 6227 i, j, k, 6228 a, b, 6229 len, edge, p; 6230 6231 for (i = 0, len = points.length; i < len; i++) { 6232 points[i]._code = _getBitCode(points[i], bounds); 6233 } 6234 6235 // for each edge (left, bottom, right, top) 6236 for (k = 0; k < 4; k++) { 6237 edge = edges[k]; 6238 clippedPoints = []; 6239 6240 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6241 a = points[i]; 6242 b = points[j]; 6243 6244 // if a is inside the clip window 6245 if (!(a._code & edge)) { 6246 // if b is outside the clip window (a->b goes out of screen) 6247 if (b._code & edge) { 6248 p = _getEdgeIntersection(b, a, edge, bounds, round); 6249 p._code = _getBitCode(p, bounds); 6250 clippedPoints.push(p); 6251 } 6252 clippedPoints.push(a); 6253 6254 // else if b is inside the clip window (a->b enters the screen) 6255 } else if (!(b._code & edge)) { 6256 p = _getEdgeIntersection(b, a, edge, bounds, round); 6257 p._code = _getBitCode(p, bounds); 6258 clippedPoints.push(p); 6259 } 6260 } 6261 points = clippedPoints; 6262 } 6263 6264 return points; 6265 } 6266 6267 var PolyUtil = { 6268 __proto__: null, 6269 clipPolygon: clipPolygon 6270 }; 6271 6272 /* 6273 * @namespace Projection 6274 * @section 6275 * Leaflet comes with a set of already defined Projections out of the box: 6276 * 6277 * @projection L.Projection.LonLat 6278 * 6279 * Equirectangular, or Plate Carree projection â the most simple projection, 6280 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6281 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6282 * `EPSG:4326` and `Simple` CRS. 6283 */ 6284 6285 var LonLat = { 6286 project: function (latlng) { 6287 return new Point(latlng.lng, latlng.lat); 6288 }, 6289 6290 unproject: function (point) { 6291 return new LatLng(point.y, point.x); 6292 }, 6293 6294 bounds: new Bounds([-180, -90], [180, 90]) 6295 }; 6296 6297 /* 6298 * @namespace Projection 6299 * @projection L.Projection.Mercator 6300 * 6301 * Elliptical Mercator projection â more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS. 6302 */ 6303 6304 var Mercator = { 6305 R: 6378137, 6306 R_MINOR: 6356752.314245179, 6307 6308 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6309 6310 project: function (latlng) { 6311 var d = Math.PI / 180, 6312 r = this.R, 6313 y = latlng.lat * d, 6314 tmp = this.R_MINOR / r, 6315 e = Math.sqrt(1 - tmp * tmp), 6316 con = e * Math.sin(y); 6317 6318 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6319 y = -r * Math.log(Math.max(ts, 1E-10)); 6320 6321 return new Point(latlng.lng * d * r, y); 6322 }, 6323 6324 unproject: function (point) { 6325 var d = 180 / Math.PI, 6326 r = this.R, 6327 tmp = this.R_MINOR / r, 6328 e = Math.sqrt(1 - tmp * tmp), 6329 ts = Math.exp(-point.y / r), 6330 phi = Math.PI / 2 - 2 * Math.atan(ts); 6331 6332 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6333 con = e * Math.sin(phi); 6334 con = Math.pow((1 - con) / (1 + con), e / 2); 6335 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6336 phi += dphi; 6337 } 6338 6339 return new LatLng(phi * d, point.x * d / r); 6340 } 6341 }; 6342 6343 /* 6344 * @class Projection 6345 6346 * An object with methods for projecting geographical coordinates of the world onto 6347 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). 6348 6349 * @property bounds: Bounds 6350 * The bounds (specified in CRS units) where the projection is valid 6351 6352 * @method project(latlng: LatLng): Point 6353 * Projects geographical coordinates into a 2D point. 6354 * Only accepts actual `L.LatLng` instances, not arrays. 6355 6356 * @method unproject(point: Point): LatLng 6357 * The inverse of `project`. Projects a 2D point into a geographical location. 6358 * Only accepts actual `L.Point` instances, not arrays. 6359 6360 * Note that the projection instances do not inherit from Leaflet's `Class` object, 6361 * and can't be instantiated. Also, new classes can't inherit from them, 6362 * and methods can't be added to them with the `include` function. 6363 6364 */ 6365 6366 var index = { 6367 __proto__: null, 6368 LonLat: LonLat, 6369 Mercator: Mercator, 6370 SphericalMercator: SphericalMercator 6371 }; 6372 6373 /* 6374 * @namespace CRS 6375 * @crs L.CRS.EPSG3395 6376 * 6377 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6378 */ 6379 var EPSG3395 = extend({}, Earth, { 6380 code: 'EPSG:3395', 6381 projection: Mercator, 6382 6383 transformation: (function () { 6384 var scale = 0.5 / (Math.PI * Mercator.R); 6385 return toTransformation(scale, 0.5, -scale, 0.5); 6386 }()) 6387 }); 6388 6389 /* 6390 * @namespace CRS 6391 * @crs L.CRS.EPSG4326 6392 * 6393 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6394 * 6395 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6396 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6397 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6398 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 6399 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6400 */ 6401 6402 var EPSG4326 = extend({}, Earth, { 6403 code: 'EPSG:4326', 6404 projection: LonLat, 6405 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 6406 }); 6407 6408 /* 6409 * @namespace CRS 6410 * @crs L.CRS.Simple 6411 * 6412 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6413 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6414 * axis should still be inverted (going from bottom to top). `distance()` returns 6415 * simple euclidean distance. 6416 */ 6417 6418 var Simple = extend({}, CRS, { 6419 projection: LonLat, 6420 transformation: toTransformation(1, 0, -1, 0), 6421 6422 scale: function (zoom) { 6423 return Math.pow(2, zoom); 6424 }, 6425 6426 zoom: function (scale) { 6427 return Math.log(scale) / Math.LN2; 6428 }, 6429 6430 distance: function (latlng1, latlng2) { 6431 var dx = latlng2.lng - latlng1.lng, 6432 dy = latlng2.lat - latlng1.lat; 6433 6434 return Math.sqrt(dx * dx + dy * dy); 6435 }, 6436 6437 infinite: true 6438 }); 6439 6440 CRS.Earth = Earth; 6441 CRS.EPSG3395 = EPSG3395; 6442 CRS.EPSG3857 = EPSG3857; 6443 CRS.EPSG900913 = EPSG900913; 6444 CRS.EPSG4326 = EPSG4326; 6445 CRS.Simple = Simple; 6446 6447 /* 6448 * @class Layer 6449 * @inherits Evented 6450 * @aka L.Layer 6451 * @aka ILayer 6452 * 6453 * A set of methods from the Layer base class that all Leaflet layers use. 6454 * Inherits all methods, options and events from `L.Evented`. 6455 * 6456 * @example 6457 * 6458 * ```js 6459 * var layer = L.marker(latlng).addTo(map); 6460 * layer.addTo(map); 6461 * layer.remove(); 6462 * ``` 6463 * 6464 * @event add: Event 6465 * Fired after the layer is added to a map 6466 * 6467 * @event remove: Event 6468 * Fired after the layer is removed from a map 6469 */ 6470 6471 6472 var Layer = Evented.extend({ 6473 6474 // Classes extending `L.Layer` will inherit the following options: 6475 options: { 6476 // @option pane: String = 'overlayPane' 6477 // 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. 6478 pane: 'overlayPane', 6479 6480 // @option attribution: String = null 6481 // 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. 6482 attribution: null, 6483 6484 bubblingMouseEvents: true 6485 }, 6486 6487 /* @section 6488 * Classes extending `L.Layer` will inherit the following methods: 6489 * 6490 * @method addTo(map: Map|LayerGroup): this 6491 * Adds the layer to the given map or layer group. 6492 */ 6493 addTo: function (map) { 6494 map.addLayer(this); 6495 return this; 6496 }, 6497 6498 // @method remove: this 6499 // Removes the layer from the map it is currently active on. 6500 remove: function () { 6501 return this.removeFrom(this._map || this._mapToAdd); 6502 }, 6503 6504 // @method removeFrom(map: Map): this 6505 // Removes the layer from the given map 6506 // 6507 // @alternative 6508 // @method removeFrom(group: LayerGroup): this 6509 // Removes the layer from the given `LayerGroup` 6510 removeFrom: function (obj) { 6511 if (obj) { 6512 obj.removeLayer(this); 6513 } 6514 return this; 6515 }, 6516 6517 // @method getPane(name? : String): HTMLElement 6518 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 6519 getPane: function (name) { 6520 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 6521 }, 6522 6523 addInteractiveTarget: function (targetEl) { 6524 this._map._targets[stamp(targetEl)] = this; 6525 return this; 6526 }, 6527 6528 removeInteractiveTarget: function (targetEl) { 6529 delete this._map._targets[stamp(targetEl)]; 6530 return this; 6531 }, 6532 6533 // @method getAttribution: String 6534 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 6535 getAttribution: function () { 6536 return this.options.attribution; 6537 }, 6538 6539 _layerAdd: function (e) { 6540 var map = e.target; 6541 6542 // check in case layer gets added and then removed before the map is ready 6543 if (!map.hasLayer(this)) { return; } 6544 6545 this._map = map; 6546 this._zoomAnimated = map._zoomAnimated; 6547 6548 if (this.getEvents) { 6549 var events = this.getEvents(); 6550 map.on(events, this); 6551 this.once('remove', function () { 6552 map.off(events, this); 6553 }, this); 6554 } 6555 6556 this.onAdd(map); 6557 6558 this.fire('add'); 6559 map.fire('layeradd', {layer: this}); 6560 } 6561 }); 6562 6563 /* @section Extension methods 6564 * @uninheritable 6565 * 6566 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 6567 * 6568 * @method onAdd(map: Map): this 6569 * 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). 6570 * 6571 * @method onRemove(map: Map): this 6572 * 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). 6573 * 6574 * @method getEvents(): Object 6575 * 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. 6576 * 6577 * @method getAttribution(): String 6578 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 6579 * 6580 * @method beforeAdd(map: Map): this 6581 * 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. 6582 */ 6583 6584 6585 /* @namespace Map 6586 * @section Layer events 6587 * 6588 * @event layeradd: LayerEvent 6589 * Fired when a new layer is added to the map. 6590 * 6591 * @event layerremove: LayerEvent 6592 * Fired when some layer is removed from the map 6593 * 6594 * @section Methods for Layers and Controls 6595 */ 6596 Map.include({ 6597 // @method addLayer(layer: Layer): this 6598 // Adds the given layer to the map 6599 addLayer: function (layer) { 6600 if (!layer._layerAdd) { 6601 throw new Error('The provided object is not a Layer.'); 6602 } 6603 6604 var id = stamp(layer); 6605 if (this._layers[id]) { return this; } 6606 this._layers[id] = layer; 6607 6608 layer._mapToAdd = this; 6609 6610 if (layer.beforeAdd) { 6611 layer.beforeAdd(this); 6612 } 6613 6614 this.whenReady(layer._layerAdd, layer); 6615 6616 return this; 6617 }, 6618 6619 // @method removeLayer(layer: Layer): this 6620 // Removes the given layer from the map. 6621 removeLayer: function (layer) { 6622 var id = stamp(layer); 6623 6624 if (!this._layers[id]) { return this; } 6625 6626 if (this._loaded) { 6627 layer.onRemove(this); 6628 } 6629 6630 delete this._layers[id]; 6631 6632 if (this._loaded) { 6633 this.fire('layerremove', {layer: layer}); 6634 layer.fire('remove'); 6635 } 6636 6637 layer._map = layer._mapToAdd = null; 6638 6639 return this; 6640 }, 6641 6642 // @method hasLayer(layer: Layer): Boolean 6643 // Returns `true` if the given layer is currently added to the map 6644 hasLayer: function (layer) { 6645 return stamp(layer) in this._layers; 6646 }, 6647 6648 /* @method eachLayer(fn: Function, context?: Object): this 6649 * Iterates over the layers of the map, optionally specifying context of the iterator function. 6650 * ``` 6651 * map.eachLayer(function(layer){ 6652 * layer.bindPopup('Hello'); 6653 * }); 6654 * ``` 6655 */ 6656 eachLayer: function (method, context) { 6657 for (var i in this._layers) { 6658 method.call(context, this._layers[i]); 6659 } 6660 return this; 6661 }, 6662 6663 _addLayers: function (layers) { 6664 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 6665 6666 for (var i = 0, len = layers.length; i < len; i++) { 6667 this.addLayer(layers[i]); 6668 } 6669 }, 6670 6671 _addZoomLimit: function (layer) { 6672 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 6673 this._zoomBoundLayers[stamp(layer)] = layer; 6674 this._updateZoomLevels(); 6675 } 6676 }, 6677 6678 _removeZoomLimit: function (layer) { 6679 var id = stamp(layer); 6680 6681 if (this._zoomBoundLayers[id]) { 6682 delete this._zoomBoundLayers[id]; 6683 this._updateZoomLevels(); 6684 } 6685 }, 6686 6687 _updateZoomLevels: function () { 6688 var minZoom = Infinity, 6689 maxZoom = -Infinity, 6690 oldZoomSpan = this._getZoomSpan(); 6691 6692 for (var i in this._zoomBoundLayers) { 6693 var options = this._zoomBoundLayers[i].options; 6694 6695 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 6696 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 6697 } 6698 6699 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 6700 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 6701 6702 // @section Map state change events 6703 // @event zoomlevelschange: Event 6704 // Fired when the number of zoomlevels on the map is changed due 6705 // to adding or removing a layer. 6706 if (oldZoomSpan !== this._getZoomSpan()) { 6707 this.fire('zoomlevelschange'); 6708 } 6709 6710 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 6711 this.setZoom(this._layersMaxZoom); 6712 } 6713 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 6714 this.setZoom(this._layersMinZoom); 6715 } 6716 } 6717 }); 6718 6719 /* 6720 * @class LayerGroup 6721 * @aka L.LayerGroup 6722 * @inherits Layer 6723 * 6724 * Used to group several layers and handle them as one. If you add it to the map, 6725 * any layers added or removed from the group will be added/removed on the map as 6726 * well. Extends `Layer`. 6727 * 6728 * @example 6729 * 6730 * ```js 6731 * L.layerGroup([marker1, marker2]) 6732 * .addLayer(polyline) 6733 * .addTo(map); 6734 * ``` 6735 */ 6736 6737 var LayerGroup = Layer.extend({ 6738 6739 initialize: function (layers, options) { 6740 setOptions(this, options); 6741 6742 this._layers = {}; 6743 6744 var i, len; 6745 6746 if (layers) { 6747 for (i = 0, len = layers.length; i < len; i++) { 6748 this.addLayer(layers[i]); 6749 } 6750 } 6751 }, 6752 6753 // @method addLayer(layer: Layer): this 6754 // Adds the given layer to the group. 6755 addLayer: function (layer) { 6756 var id = this.getLayerId(layer); 6757 6758 this._layers[id] = layer; 6759 6760 if (this._map) { 6761 this._map.addLayer(layer); 6762 } 6763 6764 return this; 6765 }, 6766 6767 // @method removeLayer(layer: Layer): this 6768 // Removes the given layer from the group. 6769 // @alternative 6770 // @method removeLayer(id: Number): this 6771 // Removes the layer with the given internal ID from the group. 6772 removeLayer: function (layer) { 6773 var id = layer in this._layers ? layer : this.getLayerId(layer); 6774 6775 if (this._map && this._layers[id]) { 6776 this._map.removeLayer(this._layers[id]); 6777 } 6778 6779 delete this._layers[id]; 6780 6781 return this; 6782 }, 6783 6784 // @method hasLayer(layer: Layer): Boolean 6785 // Returns `true` if the given layer is currently added to the group. 6786 // @alternative 6787 // @method hasLayer(id: Number): Boolean 6788 // Returns `true` if the given internal ID is currently added to the group. 6789 hasLayer: function (layer) { 6790 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer); 6791 return layerId in this._layers; 6792 }, 6793 6794 // @method clearLayers(): this 6795 // Removes all the layers from the group. 6796 clearLayers: function () { 6797 return this.eachLayer(this.removeLayer, this); 6798 }, 6799 6800 // @method invoke(methodName: String, â¦): this 6801 // Calls `methodName` on every layer contained in this group, passing any 6802 // additional parameters. Has no effect if the layers contained do not 6803 // implement `methodName`. 6804 invoke: function (methodName) { 6805 var args = Array.prototype.slice.call(arguments, 1), 6806 i, layer; 6807 6808 for (i in this._layers) { 6809 layer = this._layers[i]; 6810 6811 if (layer[methodName]) { 6812 layer[methodName].apply(layer, args); 6813 } 6814 } 6815 6816 return this; 6817 }, 6818 6819 onAdd: function (map) { 6820 this.eachLayer(map.addLayer, map); 6821 }, 6822 6823 onRemove: function (map) { 6824 this.eachLayer(map.removeLayer, map); 6825 }, 6826 6827 // @method eachLayer(fn: Function, context?: Object): this 6828 // Iterates over the layers of the group, optionally specifying context of the iterator function. 6829 // ```js 6830 // group.eachLayer(function (layer) { 6831 // layer.bindPopup('Hello'); 6832 // }); 6833 // ``` 6834 eachLayer: function (method, context) { 6835 for (var i in this._layers) { 6836 method.call(context, this._layers[i]); 6837 } 6838 return this; 6839 }, 6840 6841 // @method getLayer(id: Number): Layer 6842 // Returns the layer with the given internal ID. 6843 getLayer: function (id) { 6844 return this._layers[id]; 6845 }, 6846 6847 // @method getLayers(): Layer[] 6848 // Returns an array of all the layers added to the group. 6849 getLayers: function () { 6850 var layers = []; 6851 this.eachLayer(layers.push, layers); 6852 return layers; 6853 }, 6854 6855 // @method setZIndex(zIndex: Number): this 6856 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 6857 setZIndex: function (zIndex) { 6858 return this.invoke('setZIndex', zIndex); 6859 }, 6860 6861 // @method getLayerId(layer: Layer): Number 6862 // Returns the internal ID for a layer 6863 getLayerId: function (layer) { 6864 return stamp(layer); 6865 } 6866 }); 6867 6868 6869 // @factory L.layerGroup(layers?: Layer[], options?: Object) 6870 // Create a layer group, optionally given an initial set of layers and an `options` object. 6871 var layerGroup = function (layers, options) { 6872 return new LayerGroup(layers, options); 6873 }; 6874 6875 /* 6876 * @class FeatureGroup 6877 * @aka L.FeatureGroup 6878 * @inherits LayerGroup 6879 * 6880 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 6881 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 6882 * * Events are propagated to the `FeatureGroup`, so if the group has an event 6883 * handler, it will handle events from any of the layers. This includes mouse events 6884 * and custom events. 6885 * * Has `layeradd` and `layerremove` events 6886 * 6887 * @example 6888 * 6889 * ```js 6890 * L.featureGroup([marker1, marker2, polyline]) 6891 * .bindPopup('Hello world!') 6892 * .on('click', function() { alert('Clicked on a member of the group!'); }) 6893 * .addTo(map); 6894 * ``` 6895 */ 6896 6897 var FeatureGroup = LayerGroup.extend({ 6898 6899 addLayer: function (layer) { 6900 if (this.hasLayer(layer)) { 6901 return this; 6902 } 6903 6904 layer.addEventParent(this); 6905 6906 LayerGroup.prototype.addLayer.call(this, layer); 6907 6908 // @event layeradd: LayerEvent 6909 // Fired when a layer is added to this `FeatureGroup` 6910 return this.fire('layeradd', {layer: layer}); 6911 }, 6912 6913 removeLayer: function (layer) { 6914 if (!this.hasLayer(layer)) { 6915 return this; 6916 } 6917 if (layer in this._layers) { 6918 layer = this._layers[layer]; 6919 } 6920 6921 layer.removeEventParent(this); 6922 6923 LayerGroup.prototype.removeLayer.call(this, layer); 6924 6925 // @event layerremove: LayerEvent 6926 // Fired when a layer is removed from this `FeatureGroup` 6927 return this.fire('layerremove', {layer: layer}); 6928 }, 6929 6930 // @method setStyle(style: Path options): this 6931 // Sets the given path options to each layer of the group that has a `setStyle` method. 6932 setStyle: function (style) { 6933 return this.invoke('setStyle', style); 6934 }, 6935 6936 // @method bringToFront(): this 6937 // Brings the layer group to the top of all other layers 6938 bringToFront: function () { 6939 return this.invoke('bringToFront'); 6940 }, 6941 6942 // @method bringToBack(): this 6943 // Brings the layer group to the back of all other layers 6944 bringToBack: function () { 6945 return this.invoke('bringToBack'); 6946 }, 6947 6948 // @method getBounds(): LatLngBounds 6949 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 6950 getBounds: function () { 6951 var bounds = new LatLngBounds(); 6952 6953 for (var id in this._layers) { 6954 var layer = this._layers[id]; 6955 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 6956 } 6957 return bounds; 6958 } 6959 }); 6960 6961 // @factory L.featureGroup(layers?: Layer[], options?: Object) 6962 // Create a feature group, optionally given an initial set of layers and an `options` object. 6963 var featureGroup = function (layers, options) { 6964 return new FeatureGroup(layers, options); 6965 }; 6966 6967 /* 6968 * @class Icon 6969 * @aka L.Icon 6970 * 6971 * Represents an icon to provide when creating a marker. 6972 * 6973 * @example 6974 * 6975 * ```js 6976 * var myIcon = L.icon({ 6977 * iconUrl: 'my-icon.png', 6978 * iconRetinaUrl: '[email protected]', 6979 * iconSize: [38, 95], 6980 * iconAnchor: [22, 94], 6981 * popupAnchor: [-3, -76], 6982 * shadowUrl: 'my-icon-shadow.png', 6983 * shadowRetinaUrl: '[email protected]', 6984 * shadowSize: [68, 95], 6985 * shadowAnchor: [22, 94] 6986 * }); 6987 * 6988 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 6989 * ``` 6990 * 6991 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 6992 * 6993 */ 6994 6995 var Icon = Class.extend({ 6996 6997 /* @section 6998 * @aka Icon options 6999 * 7000 * @option iconUrl: String = null 7001 * **(required)** The URL to the icon image (absolute or relative to your script path). 7002 * 7003 * @option iconRetinaUrl: String = null 7004 * The URL to a retina sized version of the icon image (absolute or relative to your 7005 * script path). Used for Retina screen devices. 7006 * 7007 * @option iconSize: Point = null 7008 * Size of the icon image in pixels. 7009 * 7010 * @option iconAnchor: Point = null 7011 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 7012 * will be aligned so that this point is at the marker's geographical location. Centered 7013 * by default if size is specified, also can be set in CSS with
7013negative margins. 7014 * 7015 * @option popupAnchor: Point = [0, 0] 7016 * The coordinates of the point from which popups will "open", relative to the icon anchor. 7017 * 7018 * @option tooltipAnchor: Point = [0, 0] 7019 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7020 * 7021 * @option shadowUrl: String = null 7022 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7023 * 7024 * @option shadowRetinaUrl: String = null 7025 * 7026 * @option shadowSize: Point = null 7027 * Size of the shadow image in pixels. 7028 * 7029 * @option shadowAnchor: Point = null 7030 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7031 * as iconAnchor if not specified). 7032 * 7033 * @option className: String = '' 7034 * A custom class name to assign to both icon and shadow images. Empty by default. 7035 */ 7036 7037 options: { 7038 popupAnchor: [0, 0], 7039 tooltipAnchor: [0, 0], 7040 7041 // @option crossOrigin: Boolean|String = false 7042 // Whether the crossOrigin attribute will be added to the tiles. 7043 // 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. 7044 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 7045 crossOrigin: false 7046 }, 7047 7048 initialize: function (options) { 7049 setOptions(this, options); 7050 }, 7051 7052 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7053 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7054 // styled according to the options. 7055 createIcon: function (oldIcon) { 7056 return this._createIcon('icon', oldIcon); 7057 }, 7058 7059 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7060 // As `createIcon`, but for the shadow beneath it. 7061 createShadow: function (oldIcon) { 7062 return this._createIcon('shadow', oldIcon); 7063 }, 7064 7065 _createIcon: function (name, oldIcon) { 7066 var src = this._getIconUrl(name); 7067 7068 if (!src) { 7069 if (name === 'icon') { 7070 throw new Error('iconUrl not set in Icon options (see the docs).'); 7071 } 7072 return null; 7073 } 7074 7075 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7076 this._setIconStyles(img, name); 7077 7078 if (this.options.crossOrigin || this.options.crossOrigin === '') { 7079 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 7080 } 7081 7082 return img; 7083 }, 7084 7085 _setIconStyles: function (img, name) { 7086 var options = this.options; 7087 var sizeOption = options[name + 'Size']; 7088 7089 if (typeof sizeOption === 'number') { 7090 sizeOption = [sizeOption, sizeOption]; 7091 } 7092 7093 var size = toPoint(sizeOption), 7094 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7095 size && size.divideBy(2, true)); 7096 7097 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7098 7099 if (anchor) { 7100 img.style.marginLeft = (-anchor.x) + 'px'; 7101 img.style.marginTop = (-anchor.y) + 'px'; 7102 } 7103 7104 if (size) { 7105 img.style.width = size.x + 'px'; 7106 img.style.height = size.y + 'px'; 7107 } 7108 }, 7109 7110 _createImg: function (src, el) { 7111 el = el || document.createElement('img'); 7112 el.src = src; 7113 return el; 7114 }, 7115 7116 _getIconUrl: function (name) { 7117 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7118 } 7119 }); 7120 7121 7122 // @factory L.icon(options: Icon options) 7123 // Creates an icon instance with the given options. 7124 function icon(options) { 7125 return new Icon(options); 7126 } 7127 7128 /* 7129 * @miniclass Icon.Default (Icon) 7130 * @aka L.Icon.Default 7131 * @section 7132 * 7133 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7134 * no icon is specified. Points to the blue marker image distributed with Leaflet 7135 * releases. 7136 * 7137 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7138 * (which is a set of `Icon options`). 7139 * 7140 * If you want to _completely_ replace the default icon, override the 7141 * `L.Marker.prototype.options.icon` with your own icon instead. 7142 */ 7143 7144 var IconDefault = Icon.extend({ 7145 7146 options: { 7147 iconUrl: 'marker-icon.png', 7148 iconRetinaUrl: 'marker-icon-2x.png', 7149 shadowUrl: 'marker-shadow.png', 7150 iconSize: [25, 41], 7151 iconAnchor: [12, 41], 7152 popupAnchor: [1, -34], 7153 tooltipAnchor: [16, -28], 7154 shadowSize: [41, 41] 7155 }, 7156 7157 _getIconUrl: function (name) { 7158 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only 7159 IconDefault.imagePath = this._detectIconPath(); 7160 } 7161 7162 // @option imagePath: String 7163 // `Icon.Default` will try to auto-detect the location of the 7164 // blue icon images. If you are placing these images in a non-standard 7165 // way, set this option to point to the right path. 7166 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7167 }, 7168 7169 _detectIconPath: function () { 7170 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7171 var path = getStyle(el, 'background-image') || 7172 getStyle(el, 'backgroundImage'); // IE8 7173 7174 document.body.removeChild(el); 7175 7176 if (path === null || path.indexOf('url') !== 0) { 7177 path = ''; 7178 } else { 7179 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, ''); 7180 } 7181 7182 return path; 7183 } 7184 }); 7185 7186 /* 7187 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7188 */ 7189 7190 7191 /* @namespace Marker 7192 * @section Interaction handlers 7193 * 7194 * 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: 7195 * 7196 * ```js 7197 * marker.dragging.disable(); 7198 * ``` 7199 * 7200 * @property dragging: Handler 7201 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7202 */ 7203 7204 var MarkerDrag = Handler.extend({ 7205 initialize: function (marker) { 7206 this._marker = marker; 7207 }, 7208 7209 addHooks: function () { 7210 var icon = this._marker._icon; 7211 7212 if (!this._draggable) { 7213 this._draggable = new Draggable(icon, icon, true); 7214 } 7215 7216 this._draggable.on({ 7217 dragstart: this._onDragStart, 7218 predrag: this._onPreDrag, 7219 drag: this._onDrag, 7220 dragend: this._onDragEnd 7221 }, this).enable(); 7222 7223 addClass(icon, 'leaflet-marker-draggable'); 7224 }, 7225 7226 removeHooks: function () { 7227 this._draggable.off({ 7228 dragstart: this._onDragStart, 7229 predrag: this._onPreDrag, 7230 drag: this._onDrag, 7231 dragend: this._onDragEnd 7232 }, this).disable(); 7233 7234 if (this._marker._icon) { 7235 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7236 } 7237 }, 7238 7239 moved: function () { 7240 return this._draggable && this._draggable._moved; 7241 }, 7242 7243 _adjustPan: function (e) { 7244 var marker = this._marker, 7245 map = marker._map, 7246 speed = this._marker.options.autoPanSpeed, 7247 padding = this._marker.options.autoPanPadding, 7248 iconPos = getPosition(marker._icon), 7249 bounds = map.getPixelBounds(), 7250 origin = map.getPixelOrigin(); 7251 7252 var panBounds = toBounds( 7253 bounds.min._subtract(origin).add(padding), 7254 bounds.max._subtract(origin).subtract(padding) 7255 ); 7256 7257 if (!panBounds.contains(iconPos)) { 7258 // Compute incremental movement 7259 var movement = toPoint( 7260 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7261 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7262 7263 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7264 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7265 ).multiplyBy(speed); 7266 7267 map.panBy(movement, {animate: false}); 7268 7269 this._draggable._newPos._add(movement); 7270 this._draggable._startPos._add(movement); 7271 7272 setPosition(marker._icon, this._draggable._newPos); 7273 this._onDrag(e); 7274
vendor: 11,130 bytes, lines 7275-7718
7275 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7276 } 7277 }, 7278 7279 _onDragStart: function () { 7280 // @section Dragging events 7281 // @event dragstart: Event 7282 // Fired when the user starts dragging the marker. 7283 7284 // @event movestart: Event 7285 // Fired when the marker starts moving (because of dragging). 7286 7287 this._oldLatLng = this._marker.getLatLng(); 7288 7289 // When using ES6 imports it could not be set when `Popup` was not imported as well 7290 this._marker.closePopup && this._marker.closePopup(); 7291 7292 this._marker 7293 .fire('movestart') 7294 .fire('dragstart'); 7295 }, 7296 7297 _onPreDrag: function (e) { 7298 if (this._marker.options.autoPan) { 7299 cancelAnimFrame(this._panRequest); 7300 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7301 } 7302 }, 7303 7304 _onDrag: function (e) { 7305 var marker = this._marker, 7306 shadow = marker._shadow, 7307 iconPos = getPosition(marker._icon), 7308 latlng = marker._map.layerPointToLatLng(iconPos); 7309 7310 // update shadow position 7311 if (shadow) { 7312 setPosition(shadow, iconPos); 7313 } 7314 7315 marker._latlng = latlng; 7316 e.latlng = latlng; 7317 e.oldLatLng = this._oldLatLng; 7318 7319 // @event drag: Event 7320 // Fired repeatedly while the user drags the marker. 7321 marker 7322 .fire('move', e) 7323 .fire('drag', e); 7324 }, 7325 7326 _onDragEnd: function (e) { 7327 // @event dragend: DragEndEvent 7328 // Fired when the user stops dragging the marker. 7329 7330 cancelAnimFrame(this._panRequest); 7331 7332 // @event moveend: Event 7333 // Fired when the marker stops moving (because of dragging). 7334 delete this._oldLatLng; 7335 this._marker 7336 .fire('moveend') 7337 .fire('dragend', e); 7338 } 7339 }); 7340 7341 /* 7342 * @class Marker 7343 * @inherits Interactive layer 7344 * @aka L.Marker 7345 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7346 * 7347 * @example 7348 * 7349 * ```js 7350 * L.marker([50.5, 30.5]).addTo(map); 7351 * ``` 7352 */ 7353 7354 var Marker = Layer.extend({ 7355 7356 // @section 7357 // @aka Marker options 7358 options: { 7359 // @option icon: Icon = * 7360 // Icon instance to use for rendering the marker. 7361 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7362 // If not specified, a common instance of `L.Icon.Default` is used. 7363 icon: new IconDefault(), 7364 7365 // Option inherited from "Interactive layer" abstract class 7366 interactive: true, 7367 7368 // @option keyboard: Boolean = true 7369 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7370 keyboard: true, 7371 7372 // @option title: String = '' 7373 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7374 title: '', 7375 7376 // @option alt: String = '' 7377 // Text for the `alt` attribute of the icon image (useful for accessibility). 7378 alt: '', 7379 7380 // @option zIndexOffset: Number = 0 7381 // 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). 7382 zIndexOffset: 0, 7383 7384 // @option opacity: Number = 1.0 7385 // The opacity of the marker. 7386 opacity: 1, 7387 7388 // @option riseOnHover: Boolean = false 7389 // If `true`, the marker will get on top of others when you hover the mouse over it. 7390 riseOnHover: false, 7391 7392 // @option riseOffset: Number = 250 7393 // The z-index offset used for the `riseOnHover` feature. 7394 riseOffset: 250, 7395 7396 // @option pane: String = 'markerPane' 7397 // `Map pane` where the markers icon will be added. 7398 pane: 'markerPane', 7399 7400 // @option shadowPane: String = 'shadowPane' 7401 // `Map pane` where the markers shadow will be added. 7402 shadowPane: 'shadowPane', 7403 7404 // @option bubblingMouseEvents: Boolean = false 7405 // When `true`, a mouse event on this marker will trigger the same event on the map 7406 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7407 bubblingMouseEvents: false, 7408 7409 // @section Draggable marker options 7410 // @option draggable: Boolean = false 7411 // Whether the marker is draggable with mouse/touch or not. 7412 draggable: false, 7413 7414 // @option autoPan: Boolean = false 7415 // Whether to pan the map when dragging this marker near its edge or not. 7416 autoPan: false, 7417 7418 // @option autoPanPadding: Point = Point(50, 50) 7419 // Distance (in pixels to the left/right and to the top/bottom) of the 7420 // map edge to start panning the map. 7421 autoPanPadding: [50, 50], 7422 7423 // @option autoPanSpeed: Number = 10 7424 // Number of pixels the map should pan by. 7425 autoPanSpeed: 10 7426 }, 7427 7428 /* @section 7429 * 7430 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 7431 */ 7432 7433 initialize: function (latlng, options) { 7434 setOptions(this, options); 7435 this._latlng = toLatLng(latlng); 7436 }, 7437 7438 onAdd: function (map) { 7439 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 7440 7441 if (this._zoomAnimated) { 7442 map.on('zoomanim', this._animateZoom, this); 7443 } 7444 7445 this._initIcon(); 7446 this.update(); 7447 }, 7448 7449 onRemove: function (map) { 7450 if (this.dragging && this.dragging.enabled()) { 7451 this.options.draggable = true; 7452 this.dragging.removeHooks(); 7453 } 7454 delete this.dragging; 7455 7456 if (this._zoomAnimated) { 7457 map.off('zoomanim', this._animateZoom, this); 7458 } 7459 7460 this._removeIcon(); 7461 this._removeShadow(); 7462 }, 7463 7464 getEvents: function () { 7465 return { 7466 zoom: this.update, 7467 viewreset: this.update 7468 }; 7469 }, 7470 7471 // @method getLatLng: LatLng 7472 // Returns the current geographical position of the marker. 7473 getLatLng: function () { 7474 return this._latlng; 7475 }, 7476 7477 // @method setLatLng(latlng: LatLng): this 7478 // Changes the marker position to the given point. 7479 setLatLng: function (latlng) { 7480 var oldLatLng = this._latlng; 7481 this._latlng = toLatLng(latlng); 7482 this.update(); 7483 7484 // @event move: Event 7485 // 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`. 7486 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7487 }, 7488 7489 // @method setZIndexOffset(offset: Number): this 7490 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 7491 setZIndexOffset: function (offset) { 7492 this.options.zIndexOffset = offset; 7493 return this.update(); 7494 }, 7495 7496 // @method getIcon: Icon 7497 // Returns the current icon used by the marker 7498 getIcon: function () { 7499 return this.options.icon; 7500 }, 7501 7502 // @method setIcon(icon: Icon): this 7503 // Changes the marker icon. 7504 setIcon: function (icon) { 7505 7506 this.options.icon = icon; 7507 7508 if (this._map) { 7509 this._initIcon(); 7510 this.update(); 7511 } 7512 7513 if (this._popup) { 7514 this.bindPopup(this._popup, this._popup.options); 7515 } 7516 7517 return this; 7518 }, 7519 7520 getElement: function () { 7521 return this._icon; 7522 }, 7523 7524 update: function () { 7525 7526 if (this._icon && this._map) { 7527 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 7528 this._setPos(pos); 7529 } 7530 7531 return this; 7532 }, 7533 7534 _initIcon: function () { 7535 var options = this.options, 7536 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7537 7538 var icon = options.icon.createIcon(this._icon), 7539 addIcon = false; 7540 7541 // if we're not reusing the icon, remove the old one and init new one 7542 if (icon !== this._icon) { 7543 if (this._icon) { 7544 this._removeIcon(); 7545 } 7546 addIcon = true; 7547 7548 if (options.title) { 7549 icon.title = options.title; 7550 } 7551 7552 if (icon.tagName === 'IMG') { 7553 icon.alt = options.alt || ''; 7554 } 7555 } 7556 7557 addClass(icon, classToAdd); 7558 7559 if (options.keyboard) { 7560 icon.tabIndex = '0'; 7561 } 7562 7563 this._icon = icon; 7564 7565 if (options.riseOnHover) { 7566 this.on({ 7567 mouseover: this._bringToFront, 7568 mouseout: this._resetZIndex 7569 }); 7570 } 7571 7572 var newShadow = options.icon.createShadow(this._shadow), 7573 addShadow = false; 7574 7575 if (newShadow !== this._shadow) { 7576 this._removeShadow(); 7577 addShadow = true; 7578 } 7579 7580 if (newShadow) { 7581 addClass(newShadow, classToAdd); 7582 newShadow.alt = ''; 7583 } 7584 this._shadow = newShadow; 7585 7586 7587 if (options.opacity < 1) { 7588 this._updateOpacity(); 7589 } 7590 7591 7592 if (addIcon) { 7593 this.getPane().appendChild(this._icon); 7594 } 7595 this._initInteraction(); 7596 if (newShadow && addShadow) { 7597 this.getPane(options.shadowPane).appendChild(this._shadow); 7598 } 7599 }, 7600 7601 _removeIcon: function () { 7602 if (this.options.riseOnHover) { 7603 this.off({ 7604 mouseover: this._bringToFront, 7605 mouseout: this._resetZIndex 7606 }); 7607 } 7608 7609 remove(this._icon); 7610 this.removeInteractiveTarget(this._icon); 7611 7612 this._icon = null; 7613 }, 7614 7615 _removeShadow: function () { 7616 if (this._shadow) { 7617 remove(this._shadow); 7618 } 7619 this._shadow = null; 7620 }, 7621 7622 _setPos: function (pos) { 7623 7624 if (this._icon) { 7625 setPosition(this._icon, pos); 7626 } 7627 7628 if (this._shadow) { 7629 setPosition(this._shadow, pos); 7630 } 7631 7632 this._zIndex = pos.y + this.options.zIndexOffset; 7633 7634 this._resetZIndex(); 7635 }, 7636 7637 _updateZIndex: function (offset) { 7638 if (this._icon) { 7639 this._icon.style.zIndex = this._zIndex + offset; 7640 } 7641 }, 7642 7643 _animateZoom: function (opt) { 7644 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 7645 7646 this._setPos(pos); 7647 }, 7648 7649 _initInteraction: function () { 7650 7651 if (!this.options.interactive) { return; } 7652 7653 addClass(this._icon, 'leaflet-interactive'); 7654 7655 this.addInteractiveTarget(this._icon); 7656 7657 if (MarkerDrag) { 7658 var draggable = this.options.draggable; 7659 if (this.dragging) { 7660 draggable = this.dragging.enabled(); 7661 this.dragging.disable(); 7662 } 7663 7664 this.dragging = new MarkerDrag(this); 7665 7666 if (draggable) { 7667 this.dragging.enable(); 7668 } 7669 } 7670 }, 7671 7672 // @method setOpacity(opacity: Number): this 7673 // Changes the opacity of the marker. 7674 setOpacity: function (opacity) { 7675 this.options.opacity = opacity; 7676 if (this._map) { 7677 this._updateOpacity(); 7678 } 7679 7680 return this; 7681 }, 7682 7683 _updateOpacity: function () { 7684 var opacity = this.options.opacity; 7685 7686 if (this._icon) { 7687 setOpacity(this._icon, opacity); 7688 } 7689 7690 if (this._shadow) { 7691 setOpacity(this._shadow, opacity); 7692 } 7693 }, 7694 7695 _bringToFront: function () { 7696 this._updateZIndex(this.options.riseOffset); 7697 }, 7698 7699 _resetZIndex: function () { 7700 this._updateZIndex(0); 7701 }, 7702 7703 _getPopupAnchor: function () { 7704 return this.options.icon.options.popupAnchor; 7705 }, 7706 7707 _getTooltipAnchor: function () { 7708 return this.options.icon.options.tooltipAnchor; 7709 } 7710 }); 7711 7712 7713 // factory L.marker(latlng: LatLng, options? : Marker options) 7714 7715 // @factory L.marker(latlng: LatLng, options? : Marker options) 7716 // Instantiates a Marker object given a geographical point and optionally an options object. 7717 function marker(latlng, options) { 7718 return new Marker(latlng, options);
vendor: 13,798 bytes, lines 7718-8185
7718 7719 } 7720 7721 /* 7722 * @class Path 7723 * @aka L.Path 7724 * @inherits Interactive layer 7725 * 7726 * An abstract class that contains options and constants shared between vector 7727 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 7728 */ 7729 7730 var Path = Layer.extend({ 7731 7732 // @section 7733 // @aka Path options 7734 options: { 7735 // @option stroke: Boolean = true 7736 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 7737 stroke: true, 7738 7739 // @option color: String = '#3388ff' 7740 // Stroke color 7741 color: '#3388ff', 7742 7743 // @option weight: Number = 3 7744 // Stroke width in pixels 7745 weight: 3, 7746 7747 // @option opacity: Number = 1.0 7748 // Stroke opacity 7749 opacity: 1, 7750 7751 // @option lineCap: String= 'round' 7752 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 7753 lineCap: 'round', 7754 7755 // @option lineJoin: String = 'round' 7756 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 7757 lineJoin: 'round', 7758 7759 // @option dashArray: String = null 7760 // 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). 7761 dashArray: null, 7762 7763 // @option dashOffset: String = null 7764 // 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). 7765 dashOffset: null, 7766 7767 // @option fill: Boolean = depends 7768 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 7769 fill: false, 7770 7771 // @option fillColor: String = * 7772 // Fill color. Defaults to the value of the [`color`](#path-color) option 7773 fillColor: null, 7774 7775 // @option fillOpacity: Number = 0.2 7776 // Fill opacity. 7777 fillOpacity: 0.2, 7778 7779 // @option fillRule: String = 'evenodd' 7780 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 7781 fillRule: 'evenodd', 7782 7783 // className: '', 7784 7785 // Option inherited from "Interactive layer" abstract class 7786 interactive: true, 7787 7788 // @option bubblingMouseEvents: Boolean = true 7789 // When `true`, a mouse event on this path will trigger the same event on the map 7790 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7791 bubblingMouseEvents: true 7792 }, 7793 7794 beforeAdd: function (map) { 7795 // Renderer is set here because we need to call renderer.getEvents 7796 // before this.getEvents. 7797 this._renderer = map.getRenderer(this); 7798 }, 7799 7800 onAdd: function () { 7801 this._renderer._initPath(this); 7802 this._reset(); 7803 this._renderer._addPath(this); 7804 }, 7805 7806 onRemove: function () { 7807 this._renderer._removePath(this); 7808 }, 7809 7810 // @method redraw(): this 7811 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 7812 redraw: function () { 7813 if (this._map) { 7814 this._renderer._updatePath(this); 7815 } 7816 return this; 7817 }, 7818 7819 // @method setStyle(style: Path options): this 7820 // Changes the appearance of a Path based on the options in the `Path options` object. 7821 setStyle: function (style) { 7822 setOptions(this, style); 7823 if (this._renderer) { 7824 this._renderer._updateStyle(this); 7825 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) { 7826 this._updateBounds(); 7827 } 7828 } 7829 return this; 7830 }, 7831 7832 // @method bringToFront(): this 7833 // Brings the layer to the top of all path layers. 7834 bringToFront: function () { 7835 if (this._renderer) { 7836 this._renderer._bringToFront(this); 7837 } 7838 return this; 7839 }, 7840 7841 // @method bringToBack(): this 7842 // Brings the layer to the bottom of all path layers. 7843 bringToBack: function () { 7844 if (this._renderer) { 7845 this._renderer._bringToBack(this); 7846 } 7847 return this; 7848 }, 7849 7850 getElement: function () { 7851 return this._path; 7852 }, 7853 7854 _reset: function () { 7855 // defined in child classes 7856 this._project(); 7857 this._update(); 7858 }, 7859 7860 _clickTolerance: function () { 7861 // used when doing hit detection for Canvas layers 7862 return (this.options.stroke ? this.options.weight / 2 : 0) + 7863 (this._renderer.options.tolerance || 0); 7864 } 7865 }); 7866 7867 /* 7868 * @class CircleMarker 7869 * @aka L.CircleMarker 7870 * @inherits Path 7871 * 7872 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 7873 */ 7874 7875 var CircleMarker = Path.extend({ 7876 7877 // @section 7878 // @aka CircleMarker options 7879 options: { 7880 fill: true, 7881 7882 // @option radius: Number = 10 7883 // Radius of the circle marker, in pixels 7884 radius: 10 7885 }, 7886 7887 initialize: function (latlng, options) { 7888 setOptions(this, options); 7889 this._latlng = toLatLng(latlng); 7890 this._radius = this.options.radius; 7891 }, 7892 7893 // @method setLatLng(latLng: LatLng): this 7894 // Sets the position of a circle marker to a new location. 7895 setLatLng: function (latlng) { 7896 var oldLatLng = this._latlng; 7897 this._latlng = toLatLng(latlng); 7898 this.redraw(); 7899 7900 // @event move: Event 7901 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 7902 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7903 }, 7904 7905 // @method getLatLng(): LatLng 7906 // Returns the current geographical position of the circle marker 7907 getLatLng: function () { 7908 return this._latlng; 7909 }, 7910 7911 // @method setRadius(radius: Number): this 7912 // Sets the radius of a circle marker. Units are in pixels. 7913 setRadius: function (radius) { 7914 this.options.radius = this._radius = radius; 7915 return this.redraw(); 7916 }, 7917 7918 // @method getRadius(): Number 7919 // Returns the current radius of the circle 7920 getRadius: function () { 7921 return this._radius; 7922 }, 7923 7924 setStyle : function (options) { 7925 var radius = options && options.radius || this._radius; 7926 Path.prototype.setStyle.call(this, options); 7927 this.setRadius(radius); 7928 return this; 7929 }, 7930 7931 _project: function () { 7932 this._point = this._map.latLngToLayerPoint(this._latlng); 7933 this._updateBounds(); 7934 }, 7935 7936 _updateBounds: function () { 7937 var r = this._radius, 7938 r2 = this._radiusY || r, 7939 w = this._clickTolerance(), 7940 p = [r + w, r2 + w]; 7941 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 7942 }, 7943 7944 _update: function () { 7945 if (this._map) { 7946 this._updatePath(); 7947 } 7948 }, 7949 7950 _updatePath: function () { 7951 this._renderer._updateCircle(this); 7952 }, 7953 7954 _empty: function () { 7955 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 7956 }, 7957 7958 // Needed by the `Canvas` renderer for interactivity 7959 _containsPoint: function (p) { 7960 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 7961 } 7962 }); 7963 7964 7965 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 7966 // Instantiates a circle marker object given a geographical point, and an optional options object. 7967 function circleMarker(latlng, options) { 7968 return new CircleMarker(latlng, options); 7969 } 7970 7971 /* 7972 * @class Circle 7973 * @aka L.Circle 7974 * @inherits CircleMarker 7975 * 7976 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 7977 * 7978 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 7979 * 7980 * @example 7981 * 7982 * ```js 7983 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 7984 * ``` 7985 */ 7986 7987 var Circle = CircleMarker.extend({ 7988 7989 initialize: function (latlng, options, legacyOptions) { 7990 if (typeof options === 'number') { 7991 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 7992 options = extend({}, legacyOptions, {radius: options}); 7993 } 7994 setOptions(this, options); 7995 this._latlng = toLatLng(latlng); 7996 7997 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 7998 7999 // @section 8000 // @aka Circle options 8001 // @option radius: Number; Radius of the circle, in meters. 8002 this._mRadius = this.options.radius; 8003 }, 8004 8005 // @method setRadius(radius: Number): this 8006 // Sets the radius of a circle. Units are in meters. 8007 setRadius: function (radius) { 8008 this._mRadius = radius; 8009 return this.redraw(); 8010 }, 8011 8012 // @method getRadius(): Number 8013 // Returns the current radius of a circle. Units are in meters. 8014 getRadius: function () { 8015 return this._mRadius; 8016 }, 8017 8018 // @method getBounds(): LatLngBounds 8019 // Returns the `LatLngBounds` of the path. 8020 getBounds: function () { 8021 var half = [this._radius, this._radiusY || this._radius]; 8022 8023 return new LatLngBounds( 8024 this._map.layerPointToLatLng(this._point.subtract(half)), 8025 this._map.layerPointToLatLng(this._point.add(half))); 8026 }, 8027 8028 setStyle: Path.prototype.setStyle, 8029 8030 _project: function () { 8031 8032 var lng = this._latlng.lng, 8033 lat = this._latlng.lat, 8034 map = this._map, 8035 crs = map.options.crs; 8036 8037 if (crs.distance === Earth.distance) { 8038 var d = Math.PI / 180, 8039 latR = (this._mRadius / Earth.R) / d, 8040 top = map.project([lat + latR, lng]), 8041 bottom = map.project([lat - latR, lng]), 8042 p = top.add(bottom).divideBy(2), 8043 lat2 = map.unproject(p).lat, 8044 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8045 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8046 8047 if (isNaN(lngR) || lngR === 0) { 8048 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8049 } 8050 8051 this._point = p.subtract(map.getPixelOrigin()); 8052 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 8053 this._radiusY = p.y - top.y; 8054 8055 } else { 8056 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8057 8058 this._point = map.latLngToLayerPoint(this._latlng); 8059 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8060 } 8061 8062 this._updateBounds(); 8063 } 8064 }); 8065 8066 // @factory L.circle(latlng: LatLng, options?: Circle options) 8067 // Instantiates a circle object given a geographical point, and an options object 8068 // which contains the circle radius. 8069 // @alternative 8070 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8071 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8072 // Do not use in new applications or plugins. 8073 function circle(latlng, options, legacyOptions) { 8074 return new Circle(latlng, options, legacyOptions); 8075 } 8076 8077 /* 8078 * @class Polyline 8079 * @aka L.Polyline 8080 * @inherits Path 8081 * 8082 * A class for drawing polyline overlays on a map. Extends `Path`. 8083 * 8084 * @example 8085 * 8086 * ```js 8087 * // create a red polyline from an array of LatLng points 8088 * var latlngs = [ 8089 * [45.51, -122.68], 8090 * [37.77, -122.43], 8091 * [34.04, -118.2] 8092 * ]; 8093 * 8094 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8095 * 8096 * // zoom the map to the polyline 8097 * map.fitBounds(polyline.getBounds()); 8098 * ``` 8099 * 8100 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8101 * 8102 * ```js 8103 * // create a red polyline from an array of arrays of LatLng points 8104 * var latlngs = [ 8105 * [[45.51, -122.68], 8106 * [37.77, -122.43], 8107 * [34.04, -118.2]], 8108 * [[40.78, -73.91], 8109 * [41.83, -87.62], 8110 * [32.76, -96.72]] 8111 * ]; 8112 * ``` 8113 */ 8114 8115 8116 var Polyline = Path.extend({ 8117 8118 // @section 8119 // @aka Polyline options 8120 options: { 8121 // @option smoothFactor: Number = 1.0 8122 // How much to simplify the polyline on each zoom level. More means 8123 // better performance and smoother look, and less means more accurate representation. 8124 smoothFactor: 1.0, 8125 8126 // @option noClip: Boolean = false 8127 // Disable polyline clipping. 8128 noClip: false 8129 }, 8130 8131 initialize: function (latlngs, options) { 8132 setOptions(this, options); 8133 this._setLatLngs(latlngs); 8134 }, 8135 8136 // @method getLatLngs(): LatLng[] 8137 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8138 getLatLngs: function () { 8139 return this._latlngs; 8140 }, 8141 8142 // @method setLatLngs(latlngs: LatLng[]): this 8143 // Replaces all the points in the polyline with the given array of geographical points. 8144 setLatLngs: function (latlngs) { 8145 this._setLatLngs(latlngs); 8146 return this.redraw(); 8147 }, 8148 8149 // @method isEmpty(): Boolean 8150 // Returns `true` if the Polyline has no LatLngs. 8151 isEmpty: function () { 8152 return !this._latlngs.length; 8153 }, 8154 8155 // @method closestLayerPoint(p: Point): Point 8156 // Returns the point closest to `p` on the Polyline. 8157 closestLayerPoint: function (p) { 8158 var minDistance = Infinity, 8159 minPoint = null, 8160 closest = _sqClosestPointOnSegment, 8161 p1, p2; 8162 8163 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8164 var points = this._parts[j]; 8165 8166 for (var i = 1, len = points.length; i < len; i++) { 8167 p1 = points[i - 1]; 8168 p2 = points[i]; 8169 8170 var sqDist = closest(p, p1, p2, true); 8171 8172 if (sqDist < minDistance) { 8173 minDistance = sqDist; 8174 minPoint = closest(p, p1, p2); 8175 } 8176 } 8177 } 8178 if (minPoint) { 8179 minPoint.distance = Math.sqrt(minDistance); 8180 } 8181 return minPoint; 8182 }, 8183 8184 // @method getCenter(): LatLng 8185 // Returns the center ([centroid](http
8185://en.wikipedia.org/wiki/Centroid)) of the polyline. 8186 getCenter: function () { 8187 // throws error when not yet added to map as this center calculation requires projected coordinates 8188 if (!this._map) { 8189 throw new Error('Must add layer to map before using getCenter()'); 8190 } 8191 8192 var i, halfDist, segDist, dist, p1, p2, ratio, 8193 points = this._rings[0], 8194 len = points.length; 8195 8196 if (!len) { return null; } 8197 8198 // polyline centroid algorithm; only uses the first ring if there are multiple 8199 8200 for (i = 0, halfDist = 0; i < len - 1; i++) { 8201 halfDist += points[i].distanceTo(points[i + 1]) / 2; 8202 } 8203 8204 // The line is so small in the current view that all points are on the same pixel. 8205 if (halfDist === 0) { 8206 return this._map.layerPointToLatLng(points[0]); 8207 } 8208 8209 for (i = 0, dist = 0; i < len - 1; i++) { 8210 p1 = points[i]; 8211 p2 = points[i + 1]; 8212 segDist = p1.distanceTo(p2); 8213 dist += segDist; 8214 8215 if (dist > halfDist) { 8216 ratio = (dist - halfDist) / segDist; 8217 return this._map.layerPointToLatLng([ 8218 p2.x - ratio * (p2.x - p1.x), 8219 p2.y - ratio * (p2.y - p1.y) 8220 ]); 8221 } 8222 } 8223 }, 8224 8225 // @method getBounds(): LatLngBounds 8226 // Returns the `LatLngBounds` of the path.
vendor: 11,124 bytes, lines 8227-8623
8227 getBounds: function () { 8228 return this._bounds; 8229 }, 8230 8231 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this 8232 // Adds a given point to the polyline. By default, adds to the first ring of 8233 // the polyline in case of a multi-polyline, but can be overridden by passing 8234 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8235 addLatLng: function (latlng, latlngs) { 8236 latlngs = latlngs || this._defaultShape(); 8237 latlng = toLatLng(latlng); 8238 latlngs.push(latlng); 8239 this._bounds.extend(latlng); 8240 return this.redraw(); 8241 }, 8242 8243 _setLatLngs: function (latlngs) { 8244 this._bounds = new LatLngBounds(); 8245 this._latlngs = this._convertLatLngs(latlngs); 8246 }, 8247 8248 _defaultShape: function () { 8249 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8250 }, 8251 8252 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8253 _convertLatLngs: function (latlngs) { 8254 var result = [], 8255 flat = isFlat(latlngs); 8256 8257 for (var i = 0, len = latlngs.length; i < len; i++) { 8258 if (flat) { 8259 result[i] = toLatLng(latlngs[i]); 8260 this._bounds.extend(result[i]); 8261 } else { 8262 result[i] = this._convertLatLngs(latlngs[i]); 8263 } 8264 } 8265 8266 return result; 8267 }, 8268 8269 _project: function () { 8270 var pxBounds = new Bounds(); 8271 this._rings = []; 8272 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8273 8274 if (this._bounds.isValid() && pxBounds.isValid()) { 8275 this._rawPxBounds = pxBounds; 8276 this._updateBounds(); 8277 } 8278 }, 8279 8280 _updateBounds: function () { 8281 var w = this._clickTolerance(), 8282 p = new Point(w, w); 8283 8284 if (!this._rawPxBounds) { 8285 return; 8286 } 8287 8288 this._pxBounds = new Bounds([ 8289 this._rawPxBounds.min.subtract(p), 8290 this._rawPxBounds.max.add(p) 8291 ]); 8292 }, 8293 8294 // recursively turns latlngs into a set of rings with projected coordinates 8295 _projectLatlngs: function (latlngs, result, projectedBounds) { 8296 var flat = latlngs[0] instanceof LatLng, 8297 len = latlngs.length, 8298 i, ring; 8299 8300 if (flat) { 8301 ring = []; 8302 for (i = 0; i < len; i++) { 8303 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8304 projectedBounds.extend(ring[i]); 8305 } 8306 result.push(ring); 8307 } else { 8308 for (i = 0; i < len; i++) { 8309 this._projectLatlngs(latlngs[i], result, projectedBounds); 8310 } 8311 } 8312 }, 8313 8314 // clip polyline by renderer bounds so that we have less to render for performance 8315 _clipPoints: function () { 8316 var bounds = this._renderer._bounds; 8317 8318 this._parts = []; 8319 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8320 return; 8321 } 8322 8323 if (this.options.noClip) { 8324 this._parts = this._rings; 8325 return; 8326 } 8327 8328 var parts = this._parts, 8329 i, j, k, len, len2, segment, points; 8330 8331 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8332 points = this._rings[i]; 8333 8334 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8335 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8336 8337 if (!segment) { continue; } 8338 8339 parts[k] = parts[k] || []; 8340 parts[k].push(segment[0]); 8341 8342 // if segment goes out of screen, or it's the last one, it's the end of the line part 8343 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8344 parts[k].push(segment[1]); 8345 k++; 8346 } 8347 } 8348 } 8349 }, 8350 8351 // simplify each clipped part of the polyline for performance 8352 _simplifyPoints: function () { 8353 var parts = this._parts, 8354 tolerance = this.options.smoothFactor; 8355 8356 for (var i = 0, len = parts.length; i < len; i++) { 8357 parts[i] = simplify(parts[i], tolerance); 8358 } 8359 }, 8360 8361 _update: function () { 8362 if (!this._map) { return; } 8363 8364 this._clipPoints(); 8365 this._simplifyPoints(); 8366 this._updatePath(); 8367 }, 8368 8369 _updatePath: function () { 8370 this._renderer._updatePoly(this); 8371 }, 8372 8373 // Needed by the `Canvas` renderer for interactivity 8374 _containsPoint: function (p, closed) { 8375 var i, j, k, len, len2, part, 8376 w = this._clickTolerance(); 8377 8378 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8379 8380 // hit detection for polylines 8381 for (i = 0, len = this._parts.length; i < len; i++) { 8382 part = this._parts[i]; 8383 8384 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8385 if (!closed && (j === 0)) { continue; } 8386 8387 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8388 return true; 8389 } 8390 } 8391 } 8392 return false; 8393 } 8394 }); 8395 8396 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8397 // Instantiates a polyline object given an array of geographical points and 8398 // optionally an options object. You can create a `Polyline` object with 8399 // multiple separate lines (`MultiPolyline`) by passing an array of arrays 8400 // of geographic points. 8401 function polyline(latlngs, options) { 8402 return new Polyline(latlngs, options); 8403 } 8404 8405 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8406 Polyline._flat = _flat; 8407 8408 /* 8409 * @class Polygon 8410 * @aka L.Polygon 8411 * @inherits Polyline 8412 * 8413 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8414 * 8415 * 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. 8416 * 8417 * 8418 * @example 8419 * 8420 * ```js 8421 * // create a red polygon from an array of LatLng points 8422 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 8423 * 8424 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8425 * 8426 * // zoom the map to the polygon 8427 * map.fitBounds(polygon.getBounds()); 8428 * ``` 8429 * 8430 * 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: 8431 * 8432 * ```js 8433 * var latlngs = [ 8434 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8435 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8436 * ]; 8437 * ``` 8438 * 8439 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8440 * 8441 * ```js 8442 * var latlngs = [ 8443 * [ // first polygon 8444 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8445 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8446 * ], 8447 * [ // second polygon 8448 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 8449 * ] 8450 * ]; 8451 * ``` 8452 */ 8453 8454 var Polygon = Polyline.extend({ 8455 8456 options: { 8457 fill: true 8458 }, 8459 8460 isEmpty: function () { 8461 return !this._latlngs.length || !this._latlngs[0].length; 8462 }, 8463 8464 getCenter: function () { 8465 // throws error when not yet added to map as this center calculation requires projected coordinates 8466 if (!this._map) { 8467 throw new Error('Must add layer to map before using getCenter()'); 8468 } 8469 8470 var i, j, p1, p2, f, area, x, y, center, 8471 points = this._rings[0], 8472 len = points.length; 8473 8474 if (!len) { return null; } 8475 8476 // polygon centroid algorithm; only uses the first ring if there are multiple 8477 8478 area = x = y = 0; 8479 8480 for (i = 0, j = len - 1; i < len; j = i++) { 8481 p1 = points[i]; 8482 p2 = points[j]; 8483 8484 f = p1.y * p2.x - p2.y * p1.x; 8485 x += (p1.x + p2.x) * f; 8486 y += (p1.y + p2.y) * f; 8487 area += f * 3; 8488 } 8489 8490 if (area === 0) { 8491 // Polygon is so small that all points are on same pixel. 8492 center = points[0]; 8493 } else { 8494 center = [x / area, y / area]; 8495 } 8496 return this._map.layerPointToLatLng(center); 8497 }, 8498 8499 _convertLatLngs: function (latlngs) { 8500 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 8501 len = result.length; 8502 8503 // remove last point if it equals first one 8504 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 8505 result.pop(); 8506 } 8507 return result; 8508 }, 8509 8510 _setLatLngs: function (latlngs) { 8511 Polyline.prototype._setLatLngs.call(this, latlngs); 8512 if (isFlat(this._latlngs)) { 8513 this._latlngs = [this._latlngs]; 8514 } 8515 }, 8516 8517 _defaultShape: function () { 8518 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8519 }, 8520 8521 _clipPoints: function () { 8522 // polygons need a different clipping algorithm so we redefine that 8523 8524 var bounds = this._renderer._bounds, 8525 w = this.options.weight, 8526 p = new Point(w, w); 8527 8528 // increase clip padding by stroke width to avoid stroke on clip edges 8529 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8530 8531 this._parts = []; 8532 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8533 return; 8534 } 8535 8536 if (this.options.noClip) { 8537 this._parts = this._rings; 8538 return; 8539 } 8540 8541 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8542 clipped = clipPolygon(this._rings[i], bounds, true); 8543 if (clipped.length) { 8544 this._parts.push(clipped); 8545 } 8546 } 8547 }, 8548 8549 _updatePath: function () { 8550 this._renderer._updatePoly(this, true); 8551 }, 8552 8553 // Needed by the `Canvas` renderer for interactivity 8554 _containsPoint: function (p) { 8555 var inside = false, 8556 part, p1, p2, i, j, k, len, len2; 8557 8558 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8559 8560 // ray casting algorithm for detecting if point is in polygon 8561 for (i = 0, len = this._parts.length; i < len; i++) { 8562 part = this._parts[i]; 8563 8564 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8565 p1 = part[j]; 8566 p2 = part[k]; 8567 8568 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)) { 8569 inside = !inside; 8570 } 8571 } 8572 } 8573 8574 // also check if it's on polygon stroke 8575 return inside || Polyline.prototype._containsPoint.call(this, p, true); 8576 } 8577 8578 }); 8579 8580 8581 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8582 function polygon(latlngs, options) { 8583 return new Polygon(latlngs, options); 8584 } 8585 8586 /* 8587 * @class GeoJSON 8588 * @aka L.GeoJSON 8589 * @inherits FeatureGroup 8590 * 8591 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 8592 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 8593 * 8594 * @example 8595 * 8596 * ```js 8597 * L.geoJSON(data, { 8598 * style: function (feature) { 8599 * return {color: feature.properties.color}; 8600 * } 8601 * }).bindPopup(function (layer) { 8602 * return layer.feature.properties.description; 8603 * }).addTo(map); 8604 * ``` 8605 */ 8606 8607 var GeoJSON = FeatureGroup.extend({ 8608 8609 /* @section 8610 * @aka GeoJSON options 8611 * 8612 * @option pointToLayer: Function = * 8613 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 8614 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 8615 * The default is to spawn a default `Marker`: 8616 * ```js 8617 * function(geoJsonPoint, latlng) { 8618 * return L.marker(latlng); 8619 * } 8620 * ``` 8621 * 8622 * @option style: Function = * 8623 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8624 * called internally when data is added. 8625 * The default value is to not override any defaults: 8626 * ```js 8627 * function (geoJsonFeature) { 8628 * return {} 8629 * } 8630 * ``` 8631 * 8632 * @option onEachFeature: Function = * 8633 * A `Function` that will be called once for each created `Feature`, after it has 8634 * been created and styled. Useful for attaching events and popups to features. 8635 * The default is to do nothing with the newly created layers: 8636 * ```js 8637 * function (feature, layer) {} 8638 * ``` 8639 * 8640 * @option filter: Function = * 8641 * A `Function` that will be used to decide whether to include a feature or not. 8642 * The default is to include all features: 8643 * ```js 8644 * function (geoJsonFeature) { 8645 * return true; 8646 * } 8647 * ``` 8648 * Note: dynamically changing the `filter` option will have effect only on newly 8649 * added data. It will _not_ re-evaluate already included features. 8650 * 8651 * @option coordsToLatLng: Function = * 8652 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 8653 * The default is the `coordsToLatLng` static method. 8654 * 8655 * @option markersInheritOptions: Boolean = false 8656 * Whether default Markers for "Point" type Features inherit from group options. 8657 */ 8658 8659 initialize: function (geojson, options) { 8660 setOptions(this, options); 8661 8662 this._layers = {}; 8663 8664 if (geojson) { 8665 this.addData(geojson); 8666 } 8667 }, 8668 8669 // @method addData( <GeoJSON> data ): this 8670 // Adds a GeoJSON object to the layer. 8671 addData: function (geojson) { 8672 var features = isArray(geojson) ? geojson : geojson.features, 8673 i, len, feature; 8674 8675 if (features) { 8676 for (i = 0, len = features.length; i < len; i++) { 8677 // only add this if geometry or geometries are set and not null 8678 feature = features[i]; 8679 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 8680 this.addData(feature); 8681 } 8682 } 8683 return this; 8684 } 8685 8686 var options = this.options; 8687 8688 if (options.filter && !options.filter(geojson)) { return this; } 8689 8690 var layer = geometryToLayer(geojson, options); 8691 if (!layer) { 8692 return this; 8693 } 8694 layer.feature = asFeature(geojson); 8695 8696 layer.defaultOptions = layer.options; 8697 this.resetStyle(layer); 8698 8699 if (options.onEachFeature) { 8700 options.onEachFeature(geojson, layer); 8701 } 8702 8703 return this.addLayer(layer); 8704 }, 8705 8706 // @method resetStyle( <Path> layer? ): this 8707 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 8708 // If `layer` is omitted, the style of all features in the current layer is reset. 8709 resetStyle: function (layer) { 8710 if (layer === undefined) { 8711 return this.eachLayer(this.resetStyle, this); 8712 } 8713 // reset any custom styles 8714 layer.options = extend({}, layer.defaultOptions); 8715 this._setLayerStyle(layer, this.options.style); 8716 return this; 8717 }, 8718 8719 // @method setStyle( <Function> style ): this 8720 // Changes styles of GeoJSON vector layers with the given style function. 8721 setStyle: function (style) { 8722 return this.eachLayer(function (layer) { 8723 this._setLayerStyle(layer, style); 8724 }, this); 8725 }, 8726 8727 _setLayerStyle: function (layer, style) { 8728 if (layer.setStyle) { 8729 if (typeof style === 'function') { 8730 style = style(layer.feature); 8731 } 8732 layer.setStyle(style); 8733 } 8734 } 8735 }); 8736 8737 // @section 8738 // There are several static functions which can be called without instantiating L.GeoJSON: 8739 8740 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 8741 // Creates a `Layer` from a given GeoJSON feature. Can use a custom 8742 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 8743 // functions if provided as options. 8744 function geometryToLayer(geojson, options) { 8745 8746 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 8747 coords = geometry ? geometry.coordinates : null, 8748 layers = [], 8749 pointToLayer = options && options.pointToLayer, 8750 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 8751 latlng, latlngs, i, len; 8752 8753 if (!coords && !geometry) { 8754 return null; 8755 } 8756 8757 switch (geometry.type) { 8758 case 'Point': 8759 latlng = _coordsToLatLng(coords); 8760 return _pointToLayer(pointToLayer, geojson, latlng, options); 8761 8762 case 'MultiPoint':
vendor: 57,055 bytes, lines 8763-10655
8763 for (i = 0, len = coords.length; i < len; i++) { 8764 latlng = _coordsToLatLng(coords[i]); 8765 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 8766 } 8767 return new FeatureGroup(layers); 8768 8769 case 'LineString': 8770 case 'MultiLineString': 8771 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 8772 return new Polyline(latlngs, options); 8773 8774 case 'Polygon': 8775 case 'MultiPolygon': 8776 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 8777 return new Polygon(latlngs, options); 8778 8779 case 'GeometryCollection': 8780 for (i = 0, len = geometry.geometries.length; i < len; i++) { 8781 var layer = geometryToLayer({ 8782 geometry: geometry.geometries[i], 8783 type: 'Feature', 8784 properties: geojson.properties 8785 }, options); 8786 8787 if (layer) { 8788 layers.push(layer); 8789 } 8790 } 8791 return new FeatureGroup(layers); 8792 8793 default: 8794 throw new Error('Invalid GeoJSON object.'); 8795 } 8796 } 8797 8798 function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 8799 return pointToLayerFn ? 8800 pointToLayerFn(geojson, latlng) : 8801 new Marker(latlng, options && options.markersInheritOptions && options); 8802 } 8803 8804 // @function coordsToLatLng(coords: Array): LatLng 8805 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 8806 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 8807 function coordsToLatLng(coords) { 8808 return new LatLng(coords[1], coords[0], coords[2]); 8809 } 8810 8811 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 8812 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 8813 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 8814 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 8815 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 8816 var latlngs = []; 8817 8818 for (var i = 0, len = coords.length, latlng; i < len; i++) { 8819 latlng = levelsDeep ? 8820 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 8821 (_coordsToLatLng || coordsToLatLng)(coords[i]); 8822 8823 latlngs.push(latlng); 8824 } 8825 8826 return latlngs; 8827 } 8828 8829 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array 8830 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 8831 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 8832 function latLngToCoords(latlng, precision) { 8833 return latlng.alt !== undefined ? 8834 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 8835 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 8836 } 8837 8838 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array 8839 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 8840 // `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. 8841 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 8842 function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 8843 var coords = []; 8844 8845 for (var i = 0, len = latlngs.length; i < len; i++) { 8846 coords.push(levelsDeep ? 8847 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : 8848 latLngToCoords(latlngs[i], precision)); 8849 } 8850 8851 if (!levelsDeep && closed) { 8852 coords.push(coords[0]); 8853 } 8854 8855 return coords; 8856 } 8857 8858 function getFeature(layer, newGeometry) { 8859 return layer.feature ? 8860 extend({}, layer.feature, {geometry: newGeometry}) : 8861 asFeature(newGeometry); 8862 } 8863 8864 // @function asFeature(geojson: Object): Object 8865 // Normalize GeoJSON geometries/features into GeoJSON features. 8866 function asFeature(geojson) { 8867 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 8868 return geojson; 8869 } 8870 8871 return { 8872 type: 'Feature', 8873 properties: {}, 8874 geometry: geojson 8875 }; 8876 } 8877 8878 var PointToGeoJSON = { 8879 toGeoJSON: function (precision) { 8880 return getFeature(this, { 8881 type: 'Point', 8882 coordinates: latLngToCoords(this.getLatLng(), precision) 8883 }); 8884 } 8885 }; 8886 8887 // @namespace Marker 8888 // @section Other methods 8889 // @method toGeoJSON(precision?: Number|false): Object 8890 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8891 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 8892 Marker.include(PointToGeoJSON); 8893 8894 // @namespace CircleMarker 8895 // @method toGeoJSON(precision?: Number|false): Object 8896 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8897 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 8898 Circle.include(PointToGeoJSON); 8899 CircleMarker.include(PointToGeoJSON); 8900 8901 8902 // @namespace Polyline 8903 // @method toGeoJSON(precision?: Number|false): Object 8904 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8905 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 8906 Polyline.include({ 8907 toGeoJSON: function (precision) { 8908 var multi = !isFlat(this._latlngs); 8909 8910 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 8911 8912 return getFeature(this, { 8913 type: (multi ? 'Multi' : '') + 'LineString', 8914 coordinates: coords 8915 }); 8916 } 8917 }); 8918 8919 // @namespace Polygon 8920 // @method toGeoJSON(precision?: Number|false): Object 8921 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8922 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 8923 Polygon.include({ 8924 toGeoJSON: function (precision) { 8925 var holes = !isFlat(this._latlngs), 8926 multi = holes && !isFlat(this._latlngs[0]); 8927 8928 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 8929 8930 if (!holes) { 8931 coords = [coords]; 8932 } 8933 8934 return getFeature(this, { 8935 type: (multi ? 'Multi' : '') + 'Polygon', 8936 coordinates: coords 8937 }); 8938 } 8939 }); 8940 8941 8942 // @namespace LayerGroup 8943 LayerGroup.include({ 8944 toMultiPoint: function (precision) { 8945 var coords = []; 8946 8947 this.eachLayer(function (layer) { 8948 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 8949 }); 8950 8951 return getFeature(this, { 8952 type: 'MultiPoint', 8953 coordinates: coords 8954 }); 8955 }, 8956 8957 // @method toGeoJSON(precision?: Number|false): Object 8958 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8959 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 8960 toGeoJSON: function (precision) { 8961 8962 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 8963 8964 if (type === 'MultiPoint') { 8965 return this.toMultiPoint(precision); 8966 } 8967 8968 var isGeometryCollection = type === 'GeometryCollection', 8969 jsons = []; 8970 8971 this.eachLayer(function (layer) { 8972 if (layer.toGeoJSON) { 8973 var json = layer.toGeoJSON(precision); 8974 if (isGeometryCollection) { 8975 jsons.push(json.geometry); 8976 } else { 8977 var feature = asFeature(json); 8978 // Squash nested feature collections 8979 if (feature.type === 'FeatureCollection') { 8980 jsons.push.apply(jsons, feature.features); 8981 } else { 8982 jsons.push(feature); 8983 } 8984 } 8985 } 8986 }); 8987 8988 if (isGeometryCollection) { 8989 return getFeature(this, { 8990 geometries: jsons, 8991 type: 'GeometryCollection' 8992 }); 8993 } 8994 8995 return { 8996 type: 'FeatureCollection', 8997 features: jsons 8998 }; 8999 } 9000 }); 9001 9002 // @namespace GeoJSON 9003 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9004 // Creates a GeoJSON layer. Optionally accepts an object in 9005 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 9006 // (you can alternatively add it later with `addData` method) and an `options` object. 9007 function geoJSON(geojson, options) { 9008 return new GeoJSON(geojson, options); 9009 } 9010 9011 // Backward compatibility. 9012 var geoJson = geoJSON; 9013 9014 /* 9015 * @class ImageOverlay 9016 * @aka L.ImageOverlay 9017 * @inherits Interactive layer 9018 * 9019 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 9020 * 9021 * @example 9022 * 9023 * ```js 9024 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 9025 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 9026 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 9027 * ``` 9028 */ 9029 9030 var ImageOverlay = Layer.extend({ 9031 9032 // @section 9033 // @aka ImageOverlay options 9034 options: { 9035 // @option opacity: Number = 1.0 9036 // The opacity of the image overlay. 9037 opacity: 1, 9038 9039 // @option alt: String = '' 9040 // Text for the `alt` attribute of the image (useful for accessibility). 9041 alt: '', 9042 9043 // @option interactive: Boolean = false 9044 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 9045 interactive: false, 9046 9047 // @option crossOrigin: Boolean|String = false 9048 // Whether the crossOrigin attribute will be added to the image. 9049 // 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. 9050 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 9051 crossOrigin: false, 9052 9053 // @option errorOverlayUrl: String = '' 9054 // URL to the overlay image to show in place of the overlay that failed to load. 9055 errorOverlayUrl: '', 9056 9057 // @option zIndex: Number = 1 9058 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 9059 zIndex: 1, 9060 9061 // @option className: String = '' 9062 // A custom class name to assign to the image. Empty by default. 9063 className: '' 9064 }, 9065 9066 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 9067 this._url = url; 9068 this._bounds = toLatLngBounds(bounds); 9069 9070 setOptions(this, options); 9071 }, 9072 9073 onAdd: function () { 9074 if (!this._image) { 9075 this._initImage(); 9076 9077 if (this.options.opacity < 1) { 9078 this._updateOpacity(); 9079 } 9080 } 9081 9082 if (this.options.interactive) { 9083 addClass(this._image, 'leaflet-interactive'); 9084 this.addInteractiveTarget(this._image); 9085 } 9086 9087 this.getPane().appendChild(this._image); 9088 this._reset(); 9089 }, 9090 9091 onRemove: function () { 9092 remove(this._image); 9093 if (this.options.interactive) { 9094 this.removeInteractiveTarget(this._image); 9095 } 9096 }, 9097 9098 // @method setOpacity(opacity: Number): this 9099 // Sets the opacity of the overlay. 9100 setOpacity: function (opacity) { 9101 this.options.opacity = opacity; 9102 9103 if (this._image) { 9104 this._updateOpacity(); 9105 } 9106 return this; 9107 }, 9108 9109 setStyle: function (styleOpts) { 9110 if (styleOpts.opacity) { 9111 this.setOpacity(styleOpts.opacity); 9112 } 9113 return this; 9114 }, 9115 9116 // @method bringToFront(): this 9117 // Brings the layer to the top of all overlays. 9118 bringToFront: function () { 9119 if (this._map) { 9120 toFront(this._image); 9121 } 9122 return this; 9123 }, 9124 9125 // @method bringToBack(): this 9126 // Brings the layer to the bottom of all overlays. 9127 bringToBack: function () { 9128 if (this._map) { 9129 toBack(this._image); 9130 } 9131 return this; 9132 }, 9133 9134 // @method setUrl(url: String): this 9135 // Changes the URL of the image. 9136 setUrl: function (url) { 9137 this._url = url; 9138 9139 if (this._image) { 9140 this._image.src = url; 9141 } 9142 return this; 9143 }, 9144 9145 // @method setBounds(bounds: LatLngBounds): this 9146 // Update the bounds that this ImageOverlay covers 9147 setBounds: function (bounds) { 9148 this._bounds = toLatLngBounds(bounds); 9149 9150 if (this._map) { 9151 this._reset(); 9152 } 9153 return this; 9154 }, 9155 9156 getEvents: function () { 9157 var events = { 9158 zoom: this._reset, 9159 viewreset: this._reset 9160 }; 9161 9162 if (this._zoomAnimated) { 9163 events.zoomanim = this._animateZoom; 9164 } 9165 9166 return events; 9167 }, 9168 9169 // @method setZIndex(value: Number): this 9170 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9171 setZIndex: function (value) { 9172 this.options.zIndex = value; 9173 this._updateZIndex(); 9174 return this; 9175 }, 9176 9177 // @method getBounds(): LatLngBounds 9178 // Get the bounds that this ImageOverlay covers 9179 getBounds: function () { 9180 return this._bounds; 9181 }, 9182 9183 // @method getElement(): HTMLElement 9184 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9185 // used by this overlay. 9186 getElement: function () { 9187 return this._image; 9188 }, 9189 9190 _initImage: function () { 9191 var wasElementSupplied = this._url.tagName === 'IMG'; 9192 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9193 9194 addClass(img, 'leaflet-image-layer'); 9195 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9196 if (this.options.className) { addClass(img, this.options.className); } 9197 9198 img.onselectstart = falseFn; 9199 img.onmousemove = falseFn; 9200 9201 // @event load: Event 9202 // Fired when the ImageOverlay layer has loaded its image 9203 img.onload = bind(this.fire, this, 'load'); 9204 img.onerror = bind(this._overlayOnError, this, 'error'); 9205 9206 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9207 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9208 } 9209 9210 if (this.options.zIndex) { 9211 this._updateZIndex(); 9212 } 9213 9214 if (wasElementSupplied) { 9215 this._url = img.src; 9216 return; 9217 } 9218 9219 img.src = this._url; 9220 img.alt = this.options.alt; 9221 }, 9222 9223 _animateZoom: function (e) { 9224 var scale = this._map.getZoomScale(e.zoom), 9225 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9226 9227 setTransform(this._image, offset, scale); 9228 }, 9229 9230 _reset: function () { 9231 var image = this._image, 9232 bounds = new Bounds( 9233 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9234 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9235 size = bounds.getSize(); 9236 9237 setPosition(image, bounds.min); 9238 9239 image.style.width = size.x + 'px'; 9240 image.style.height = size.y + 'px'; 9241 }, 9242 9243 _updateOpacity: function () { 9244 setOpacity(this._image, this.options.opacity); 9245 }, 9246 9247 _updateZIndex: function () { 9248 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9249 this._image.style.zIndex = this.options.zIndex; 9250 } 9251 }, 9252 9253 _overlayOnError: function () { 9254 // @event error: Event 9255 // Fired when the ImageOverlay layer fails to load its image 9256 this.fire('error'); 9257 9258 var errorUrl = this.options.errorOverlayUrl; 9259 if (errorUrl && this._url !== errorUrl) { 9260 this._url = errorUrl; 9261 this._image.src = errorUrl; 9262 } 9263 } 9264 }); 9265 9266 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9267 // Instantiates an image overlay object given the URL of the image and the 9268 // geographical bounds it is tied to. 9269 var imageOverlay = function (url, bounds, options) { 9270 return new ImageOverlay(url, bounds, options); 9271 }; 9272 9273 /* 9274 * @class VideoOverlay 9275 * @aka L.VideoOverlay 9276 * @inherits ImageOverlay 9277 * 9278 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9279 * 9280 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9281 * HTML5 element. 9282 * 9283 * @example 9284 * 9285 * ```js 9286 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9287 * videoBounds = [[ 32, -130], [ 13, -100]]; 9288 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9289 * ``` 9290 */ 9291 9292 var VideoOverlay = ImageOverlay.extend({ 9293 9294 // @section 9295 // @aka VideoOverlay options 9296 options: { 9297 // @option autoplay: Boolean = true 9298 // Whether the video starts playing automatically when loaded. 9299 autoplay: true, 9300 9301 // @option loop: Boolean = true 9302 // Whether the video will loop back to the beginning when played. 9303 loop: true, 9304 9305 // @option keepAspectRatio: Boolean = true 9306 // Whether the video will save aspect ratio after the projection. 9307 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 9308 keepAspectRatio: true, 9309 9310 // @option muted: Boolean = false 9311 // Whether the video starts on mute when loaded. 9312 muted: false 9313 }, 9314 9315 _initImage: function () { 9316 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9317 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9318 9319 addClass(vid, 'leaflet-image-layer'); 9320 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9321 if (this.options.className) { addClass(vid, this.options.className); } 9322 9323 vid.onselectstart = falseFn; 9324 vid.onmousemove = falseFn; 9325 9326 // @event load: Event 9327 // Fired when the video has finished loading the first frame 9328 vid.onloadeddata = bind(this.fire, this, 'load'); 9329 9330 if (wasElementSupplied) { 9331 var sourceElements = vid.getElementsByTagName('source'); 9332 var sources = []; 9333 for (var j = 0; j < sourceElements.length; j++) { 9334 sources.push(sourceElements[j].src); 9335 } 9336 9337 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9338 return; 9339 } 9340 9341 if (!isArray(this._url)) { this._url = [this._url]; } 9342 9343 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) { 9344 vid.style['objectFit'] = 'fill'; 9345 } 9346 vid.autoplay = !!this.options.autoplay; 9347 vid.loop = !!this.options.loop; 9348 vid.muted = !!this.options.muted; 9349 for (var i = 0; i < this._url.length; i++) { 9350 var source = create$1('source'); 9351 source.src = this._url[i]; 9352 vid.appendChild(source); 9353 } 9354 } 9355 9356 // @method getElement(): HTMLVideoElement 9357 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9358 // used by this overlay. 9359 }); 9360 9361 9362 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9363 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9364 // geographical bounds it is tied to. 9365 9366 function videoOverlay(video, bounds, options) { 9367 return new VideoOverlay(video, bounds, options); 9368 } 9369 9370 /* 9371 * @class SVGOverlay 9372 * @aka L.SVGOverlay 9373 * @inherits ImageOverlay 9374 * 9375 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`. 9376 * 9377 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. 9378 * 9379 * @example 9380 * 9381 * ```js 9382 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9383 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); 9384 * svgElement.setAttribute('viewBox', "0 0 200 200"); 9385 * 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"/>'; 9386 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ]; 9387 * L.svgOverlay(svgElement, svgElementBounds).addTo(map); 9388 * ``` 9389 */ 9390 9391 var SVGOverlay = ImageOverlay.extend({ 9392 _initImage: function () { 9393 var el = this._image = this._url; 9394 9395 addClass(el, 'leaflet-image-layer'); 9396 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); } 9397 if (this.options.className) { addClass(el, this.options.className); } 9398 9399 el.onselectstart = falseFn; 9400 el.onmousemove = falseFn; 9401 } 9402 9403 // @method getElement(): SVGElement 9404 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement) 9405 // used by this overlay. 9406 }); 9407 9408 9409 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options) 9410 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to. 9411 // A viewBox attribute is required on the SVG element to zoom in and out properly. 9412 9413 function svgOverlay(el, bounds, options) { 9414 return new SVGOverlay(el, bounds, options); 9415 } 9416 9417 /* 9418 * @class DivOverlay 9419 * @inherits Layer 9420 * @aka L.DivOverlay 9421 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins. 9422 */ 9423 9424 // @namespace DivOverlay 9425 var DivOverlay = Layer.extend({ 9426 9427 // @section 9428 // @aka DivOverlay options 9429 options: { 9430 // @option offset: Point = Point(0, 7) 9431 // The offset of the popup position. Useful to control the anchor 9432 // of the popup when opening it on some overlays. 9433 offset: [0, 7], 9434 9435 // @option className: String = '' 9436 // A custom CSS class name to assign to the popup. 9437 className: '', 9438 9439 // @option pane: String = 'popupPane' 9440 // `Map pane` where the popup will be added. 9441 pane: 'popupPane' 9442 }, 9443 9444 initialize: function (options, source) { 9445 setOptions(this, options); 9446 9447 this._source = source; 9448 }, 9449 9450 onAdd: function (map) { 9451 this._zoomAnimated = map._zoomAnimated; 9452 9453 if (!this._container) { 9454 this._initLayout(); 9455 } 9456 9457 if (map._fadeAnimated) { 9458 setOpacity(this._container, 0); 9459 } 9460 9461 clearTimeout(this._removeTimeout); 9462 this.getPane().appendChild(this._container); 9463 this.update(); 9464 9465 if (map._fadeAnimated) { 9466 setOpacity(this._container, 1); 9467 } 9468 9469 this.bringToFront(); 9470 }, 9471 9472 onRemove: function (map) { 9473 if (map._fadeAnimated) { 9474 setOpacity(this._container, 0); 9475 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200); 9476 } else { 9477 remove(this._container); 9478 } 9479 }, 9480 9481 // @namespace Popup 9482 // @method getLatLng: LatLng 9483 // Returns the geographical point of popup. 9484 getLatLng: function () { 9485 return this._latlng; 9486 }, 9487 9488 // @method setLatLng(latlng: LatLng): this 9489 // Sets the geographical point where the popup will open. 9490 setLatLng: function (latlng) { 9491 this._latlng = toLatLng(latlng); 9492 if (this._map) { 9493 this._updatePosition(); 9494 this._adjustPan(); 9495 } 9496 return this; 9497 }, 9498 9499 // @method getContent: String|HTMLElement 9500 // Returns the content of the popup. 9501 getContent: function () { 9502 return this._content; 9503 }, 9504 9505 // @method setContent(htmlContent: String|HTMLElement|Function): this 9506 // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup. 9507 setContent: function (content) { 9508 this._content = content; 9509 this.update(); 9510 return this; 9511 }, 9512 9513 // @method getElement: String|HTMLElement 9514 // Returns the HTML container of the popup. 9515 getElement: function () { 9516 return this._container; 9517 }, 9518 9519 // @method update: null 9520 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded. 9521 update: function () { 9522 if (!this._map) { return; } 9523 9524 this._container.style.visibility = 'hidden'; 9525 9526 this._updateContent(); 9527 this._updateLayout(); 9528 this._updatePosition(); 9529 9530 this._container.style.visibility = ''; 9531 9532 this._adjustPan(); 9533 }, 9534 9535 getEvents: function () { 9536 var events = { 9537 zoom: this._updatePosition, 9538 viewreset: this._updatePosition 9539 }; 9540 9541 if (this._zoomAnimated) { 9542 events.zoomanim = this._animateZoom; 9543 } 9544 return events; 9545 }, 9546 9547 // @method isOpen: Boolean 9548 // Returns `true` when the popup is visible on the map. 9549 isOpen: function () { 9550 return !!this._map && this._map.hasLayer(this); 9551 }, 9552 9553 // @method bringToFront: this 9554 // Brings this popup in front of other popups (in the same map pane). 9555 bringToFront: function () { 9556 if (this._map) { 9557 toFront(this._container); 9558 } 9559 return this; 9560 }, 9561 9562 // @method bringToBack: this 9563 // Brings this popup to the back of other popups (in the same map pane). 9564 bringToBack: function () { 9565 if (this._map) { 9566 toBack(this._container); 9567 } 9568 return this; 9569 }, 9570 9571 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup) 9572 _prepareOpen: function (latlng) { 9573 var source = this._source; 9574 if (!source._map) { return false; } 9575 9576 if (source instanceof FeatureGroup) { 9577 source = null; 9578 var layers = this._source._layers; 9579 for (var id in layers) { 9580 if (layers[id]._map) { 9581 source = layers[id]; 9582 break; 9583 } 9584 } 9585 if (!source) { return false; } // Unable to get source layer. 9586 9587 // set overlay source to this layer 9588 this._source = source; 9589 } 9590 9591 if (!latlng) { 9592 if (source.getCenter) { 9593 latlng = source.getCenter(); 9594 } else if (source.getLatLng) { 9595 latlng = source.getLatLng(); 9596 } else if (source.getBounds) { 9597 latlng = source.getBounds().getCenter(); 9598 } else { 9599 throw new Error('Unable to get source layer LatLng.'); 9600 } 9601 } 9602 this.setLatLng(latlng); 9603 9604 if (this._map) { 9605 // update the overlay (content, layout, etc...) 9606 this.update(); 9607 } 9608 9609 return true; 9610 }, 9611 9612 _updateContent: function () { 9613 if (!this._content) { return; } 9614 9615 var node = this._contentNode; 9616 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 9617 9618 if (typeof content === 'string') { 9619 node.innerHTML = content; 9620 } else { 9621 while (node.hasChildNodes()) { 9622 node.removeChild(node.firstChild); 9623 } 9624 node.appendChild(content); 9625 } 9626 this.fire('contentupdate'); 9627 }, 9628 9629 _updatePosition: function () { 9630 if (!this._map) { return; } 9631 9632 var pos = this._map.latLngToLayerPoint(this._latlng), 9633 offset = toPoint(this.options.offset), 9634 anchor = this._getAnchor(); 9635 9636 if (this._zoomAnimated) { 9637 setPosition(this._container, pos.add(anchor)); 9638 } else { 9639 offset = offset.add(pos).add(anchor); 9640 } 9641 9642 var bottom = this._containerBottom = -offset.y, 9643 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 9644 9645 // bottom position the popup in case the height of the popup changes (images loading etc) 9646 this._container.style.bottom = bottom + 'px'; 9647 this._container.style.left = left + 'px'; 9648 }, 9649 9650 _getAnchor: function () { 9651 return [0, 0]; 9652 } 9653 9654 }); 9655 9656 /* 9657 * @class Popup 9658 * @inherits DivOverlay 9659 * @aka L.Popup 9660 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 9661 * open popups while making sure that only one popup is open at one time 9662 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 9663 * 9664 * @example 9665 * 9666 * If you want to just bind a popup to marker click and then open it, it's really easy: 9667 * 9668 * ```js 9669 * marker.bindPopup(popupContent).openPopup(); 9670 * ``` 9671 * Path overlays like polylines also have a `bindPopup` method. 9672 * Here's a more complicated way to open a popup on a map: 9673 * 9674 * ```js 9675 * var popup = L.popup() 9676 * .setLatLng(latlng) 9677 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 9678 * .openOn(map); 9679 * ``` 9680 */ 9681 9682 9683 // @namespace Popup 9684 var Popup = DivOverlay.extend({ 9685 9686 // @section 9687 // @aka Popup options 9688 options: { 9689 // @option maxWidth: Number = 300 9690 // Max width of the popup, in pixels. 9691 maxWidth: 300, 9692 9693 // @option minWidth: Number = 50 9694 // Min width of the popup, in pixels. 9695 minWidth: 50, 9696 9697 // @option maxHeight: Number = null 9698 // If set, creates a scrollable container of the given height 9699 // inside a popup if its content exceeds it. 9700 maxHeight: null, 9701 9702 // @option autoPan: Boolean = true 9703 // Set it to `false` if you don't want the map to do panning animation 9704 // to fit the opened popup. 9705 autoPan: true, 9706 9707 // @option autoPanPaddingTopLeft: Point = null 9708 // The margin between the popup and the top left corner of the map 9709 // view after autopanning was performed. 9710 autoPanPaddingTopLeft: null, 9711 9712 // @option autoPanPaddingBottomRight: Point = null 9713 // The margin between the popup and the bottom right corner of the map 9714 // view after autopanning was performed. 9715 autoPanPaddingBottomRight: null, 9716 9717 // @option autoPanPadding: Point = Point(5, 5) 9718 // Equivalent of setting both top left and bottom right autopan padding to the same value. 9719 autoPanPadding: [5, 5], 9720 9721 // @option keepInView: Boolean = false 9722 // Set it to `true` if you want to prevent users from panning the popup 9723 // off of the screen while it is open. 9724 keepInView: false, 9725 9726 // @option closeButton: Boolean = true 9727 // Controls the presence of a close button in the popup. 9728 closeButton: true, 9729 9730 // @option autoClose: Boolean = true 9731 // Set it to `false` if you want to override the default behavior of 9732 // the popup closing when another popup is opened. 9733 autoClose: true, 9734 9735 // @option closeOnEscapeKey: Boolean = true 9736 // Set it to `false` if you want to override the default behavior of 9737 // the ESC key for closing of the popup. 9738 closeOnEscapeKey: true, 9739 9740 // @option closeOnClick: Boolean = * 9741 // Set it if you want to override the default behavior of the popup closing when user clicks 9742 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 9743 9744 // @option className: String = '' 9745 // A custom CSS class name to assign to the popup. 9746 className: '' 9747 }, 9748 9749 // @namespace Popup 9750 // @method openOn(map: Map): this 9751 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`. 9752 openOn: function (map) { 9753 map.openPopup(this); 9754 return this; 9755 }, 9756 9757 onAdd: function (map) { 9758 DivOverlay.prototype.onAdd.call(this, map); 9759 9760 // @namespace Map 9761 // @section Popup events 9762 // @event popupopen: PopupEvent 9763 // Fired when a popup is opened in the map 9764 map.fire('popupopen', {popup: this}); 9765 9766 if (this._source) { 9767 // @namespace Layer 9768 // @section Popup events 9769 // @event popupopen: PopupEvent 9770 // Fired when a popup bound to this layer is opened 9771 this._source.fire('popupopen', {popup: this}, true); 9772 // For non-path layers, we toggle the popup when clicking 9773 // again the layer, so prevent the map to reopen it. 9774 if (!(this._source instanceof Path)) { 9775 this._source.on('preclick', stopPropagation); 9776 } 9777 } 9778 }, 9779 9780 onRemove: function (map) { 9781 DivOverlay.prototype.onRemove.call(this, map); 9782 9783 // @namespace Map 9784 // @section Popup events 9785 // @event popupclose: PopupEvent 9786 // Fired when a popup in the map is closed 9787 map.fire('popupclose', {popup: this}); 9788 9789 if (this._source) { 9790 // @namespace Layer 9791 // @section Popup events 9792 // @event popupclose: PopupEvent 9793 // Fired when a popup bound to this layer is closed 9794 this._source.fire('popupclose', {popup: this}, true); 9795 if (!(this._source instanceof Path)) { 9796 this._source.off('preclick', stopPropagation); 9797 } 9798 } 9799 }, 9800 9801 getEvents: function () { 9802 var events = DivOverlay.prototype.getEvents.call(this); 9803 9804 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 9805 events.preclick = this._close; 9806 } 9807 9808 if (this.options.keepInView) { 9809 events.moveend = this._adjustPan; 9810 } 9811 9812 return events; 9813 }, 9814 9815 _close: function () { 9816 if (this._map) { 9817 this._map.closePopup(this); 9818 } 9819 }, 9820 9821 _initLayout: function () { 9822 var prefix = 'leaflet-popup', 9823 container = this._container = create$1('div', 9824 prefix + ' ' + (this.options.className || '') + 9825 ' leaflet-zoom-animated'); 9826 9827 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container); 9828 this._contentNode = create$1('div', prefix + '-content', wrapper); 9829 9830 disableClickPropagation(container); 9831 disableScrollPropagation(this._contentNode); 9832 on(container, 'contextmenu', stopPropagation); 9833 9834 this._tipContainer = create$1('div', prefix + '-tip-container', container); 9835 this._tip = create$1('div', prefix + '-tip', this._tipContainer); 9836 9837 if (this.options.closeButton) { 9838 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container); 9839 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399 9840 closeButton.setAttribute('aria-label', 'Close popup'); 9841 closeButton.href = '#close'; 9842 closeButton.innerHTML = '<span aria-hidden="true">×</span>'; 9843 9844 on(closeButton, 'click', this._close, this); 9845 } 9846 }, 9847 9848 _updateLayout: function () { 9849 var container = this._contentNode, 9850 style = container.style; 9851 9852 style.width = ''; 9853 style.whiteSpace = 'nowrap'; 9854 9855 var width = container.offsetWidth; 9856 width = Math.min(width, this.options.maxWidth); 9857 width = Math.max(width, this.options.minWidth); 9858 9859 style.width = (width + 1) + 'px'; 9860 style.whiteSpace = ''; 9861 9862 style.height = ''; 9863 9864 var height = container.offsetHeight, 9865 maxHeight = this.options.maxHeight, 9866 scrolledClass = 'leaflet-popup-scrolled'; 9867 9868 if (maxHeight && height > maxHeight) { 9869 style.height = maxHeight + 'px'; 9870 addClass(container, scrolledClass); 9871 } else { 9872 removeClass(container, scrolledClass); 9873 } 9874 9875 this._containerWidth = this._container.offsetWidth; 9876 }, 9877 9878 _animateZoom: function (e) { 9879 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 9880 anchor = this._getAnchor(); 9881 setPosition(this._container, pos.add(anchor)); 9882 }, 9883 9884 _adjustPan: function (e) { 9885 if (!this.options.autoPan) { return; } 9886 if (this._map._panAnim) { this._map._panAnim.stop(); } 9887 9888 var map = this._map, 9889 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0, 9890 containerHeight = this._container.offsetHeight + marginBottom, 9891 containerWidth = this._containerWidth, 9892 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 9893 9894 layerPos._add(getPosition(this._container)); 9895 9896 var containerPos = map.layerPointToContainerPoint(layerPos), 9897 padding = toPoint(this.options.autoPanPadding), 9898 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), 9899 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), 9900 size = map.getSize(), 9901 dx = 0, 9902 dy = 0; 9903 9904 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 9905 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 9906 } 9907 if (containerPos.x - dx - paddingTL.x < 0) { // left 9908 dx = containerPos.x - paddingTL.x; 9909 } 9910 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 9911 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 9912 } 9913 if (containerPos.y - dy - paddingTL.y < 0) { // top 9914 dy = containerPos.y - paddingTL.y; 9915 } 9916 9917 // @namespace Map 9918 // @section Popup events 9919 // @event autopanstart: Event 9920 // Fired when the map starts autopanning when opening a popup. 9921 if (dx || dy) { 9922 map 9923 .fire('autopanstart') 9924 .panBy([dx, dy], {animate: e && e.type === 'moveend'}); 9925 } 9926 }, 9927 9928 _getAnchor: function () { 9929 // Where should we anchor the popup on the source layer? 9930 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 9931 } 9932 9933 }); 9934 9935 // @namespace Popup 9936 // @factory L.popup(options?: Popup options, source?: Layer) 9937 // 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. 9938 var popup = function (options, source) { 9939 return new Popup(options, source); 9940 }; 9941 9942 9943 /* @namespace Map 9944 * @section Interaction Options 9945 * @option closePopupOnClick: Boolean = true 9946 * Set it to `false` if you don't want popups to close when user clicks the map. 9947 */ 9948 Map.mergeOptions({ 9949 closePopupOnClick: true 9950 }); 9951 9952 9953 // @namespace Map 9954 // @section Methods for Layers and Controls 9955 Map.include({ 9956 // @method openPopup(popup: Popup): this 9957 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 9958 // @alternative 9959 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 9960 // Creates a popup with the specified content and options and opens it in the given point on a map. 9961 openPopup: function (popup, latlng, options) { 9962 if (!(popup instanceof Popup)) { 9963 popup = new Popup(options).setContent(popup); 9964 } 9965 9966 if (latlng) { 9967 popup.setLatLng(latlng); 9968 } 9969 9970 if (this.hasLayer(popup)) { 9971 return this; 9972 } 9973 9974 if (this._popup && this._popup.options.autoClose) { 9975 this.closePopup(); 9976 } 9977 9978 this._popup = popup; 9979 return this.addLayer(popup); 9980 }, 9981 9982 // @method closePopup(popup?: Popup): this 9983 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 9984 closePopup: function (popup) { 9985 if (!popup || popup === this._popup) { 9986 popup = this._popup; 9987 this._popup = null; 9988 } 9989 if (popup) { 9990 this.removeLayer(popup); 9991 } 9992 return this; 9993 } 9994 }); 9995 9996 /* 9997 * @namespace Layer 9998 * @section Popup methods example 9999 * 10000 * All layers share a set of methods convenient for binding popups to it. 10001 * 10002 * ```js 10003 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 10004 * layer.openPopup(); 10005 * layer.closePopup(); 10006 * ``` 10007 * 10008 * 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. 10009 */ 10010 10011 // @section Popup methods 10012 Layer.include({ 10013 10014 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 10015 // Binds a popup to the layer with the passed `content` and sets up the 10016 // necessary event listeners. If a `Function` is passed it will receive 10017 // the layer as the first argument and should return a `String` or `HTMLElement`. 10018 bindPopup: function (content, options) { 10019 10020 if (content instanceof Popup) { 10021 setOptions(content, options); 10022 this._popup = content; 10023 content._source = this; 10024 } else { 10025 if (!this._popup || options) { 10026 this._popup = new Popup(options, this); 10027 } 10028 this._popup.setContent(content); 10029 } 10030 10031 if (!this._popupHandlersAdded) { 10032 this.on({ 10033 click: this._openPopup, 10034 keypress: this._onKeyPress, 10035 remove: this.closePopup, 10036 move: this._movePopup 10037 }); 10038 this._popupHandlersAdded = true; 10039 } 10040 10041 return this; 10042 }, 10043 10044 // @method unbindPopup(): this 10045 // Removes the popup previously bound with `bindPopup`. 10046 unbindPopup: function () { 10047 if (this._popup) { 10048 this.off({ 10049 click: this._openPopup, 10050 keypress: this._onKeyPress, 10051 remove: this.closePopup, 10052 move: this._movePopup 10053 }); 10054 this._popupHandlersAdded = false; 10055 this._popup = null; 10056 } 10057 return this; 10058 }, 10059 10060 // @method openPopup(latlng?: LatLng): this 10061 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 10062 openPopup: function (latlng) { 10063 if (this._popup && this._popup._prepareOpen(latlng)) { 10064 // open the popup on the map 10065 this._map.openPopup(this._popup, latlng); 10066 } 10067 10068 return this; 10069 }, 10070 10071 // @method closePopup(): this 10072 // Closes the popup bound to this layer if it is open. 10073 closePopup: function () { 10074 if (this._popup) { 10075 this._popup._close(); 10076 } 10077 return this; 10078 }, 10079 10080 // @method togglePopup(): this 10081 // Opens or closes the popup bound to this layer depending on its current state. 10082 togglePopup: function () { 10083 if (this._popup) { 10084 if (this._popup._map) { 10085 this.closePopup(); 10086 } else { 10087 this.openPopup(); 10088 } 10089 } 10090 return this; 10091 }, 10092 10093 // @method isPopupOpen(): boolean 10094 // Returns `true` if the popup bound to this layer is currently open. 10095 isPopupOpen: function () { 10096 return (this._popup ? this._popup.isOpen() : false); 10097 }, 10098 10099 // @method setPopupContent(content: String|HTMLElement|Popup): this 10100 // Sets the content of the popup bound to this layer. 10101 setPopupContent: function (content) { 10102 if (this._popup) { 10103 this._popup.setContent(content); 10104 } 10105 return this; 10106 }, 10107 10108 // @method getPopup(): Popup 10109 // Returns the popup bound to this layer. 10110 getPopup: function () { 10111 return this._popup; 10112 }, 10113 10114 _openPopup: function (e) { 10115 if (!this._popup || !this._map) { 10116 return; 10117 } 10118 // prevent map click 10119 stop(e); 10120 10121 var target = e.layer || e.target; 10122 if (this._popup._source === target && !(target instanceof Path)) { 10123 // treat it like a marker and figure out 10124 // if we should toggle it open/closed 10125 if (this._map.hasLayer(this._popup)) { 10126 this.closePopup(); 10127 } else { 10128 this.openPopup(e.latlng); 10129 } 10130 return; 10131 } 10132 this._popup._source = target; 10133 this.openPopup(e.latlng); 10134 }, 10135 10136 _movePopup: function (e) { 10137 this._popup.setLatLng(e.latlng); 10138 }, 10139 10140 _onKeyPress: function (e) { 10141 if (e.originalEvent.keyCode === 13) { 10142 this._openPopup(e); 10143 } 10144 } 10145 }); 10146 10147 /* 10148 * @class Tooltip 10149 * @inherits DivOverlay 10150 * @aka L.Tooltip 10151 * Used to display small texts on top of map layers. 10152 * 10153 * @example 10154 * 10155 * ```js 10156 * marker.bindTooltip("my tooltip text").openTooltip(); 10157 * ``` 10158 * Note about tooltip offset. Leaflet takes two options in consideration 10159 * for computing tooltip offsetting: 10160 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 10161 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 10162 * move it to the bottom. Negatives will move to the left and top. 10163 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 10164 * should adapt this value if you use a custom icon. 10165 */ 10166 10167 10168 // @namespace Tooltip 10169 var Tooltip = DivOverlay.extend({ 10170 10171 // @section 10172 // @aka Tooltip options 10173 options: { 10174 // @option pane: String = 'tooltipPane' 10175 // `Map pane` where the tooltip will be added. 10176 pane: 'tooltipPane', 10177 10178 // @option offset: Point = Point(0, 0) 10179 // Optional offset of the tooltip position. 10180 offset: [0, 0], 10181 10182 // @option direction: String = 'auto' 10183 // Direction where to open the tooltip. Possible values are: `right`, `left`, 10184 // `top`, `bottom`, `center`, `auto`. 10185 // `auto` will dynamically switch between `right` and `left` according to the tooltip 10186 // position on the map. 10187 direction: 'auto', 10188 10189 // @option permanent: Boolean = false 10190 // Whether to open the tooltip permanently or only on mouseover. 10191 permanent: false, 10192 10193 // @option sticky: Boolean = false 10194 // If true, the tooltip will follow the mouse instead of being fixed at the feature center. 10195 sticky: false, 10196 10197 // @option interactive: Boolean = false 10198 // If true, the tooltip will listen to the feature events. 10199 interactive: false, 10200 10201 // @option opacity: Number = 0.9 10202 // Tooltip container opacity. 10203 opacity: 0.9 10204 }, 10205 10206 onAdd: function (map) { 10207 DivOverlay.prototype.onAdd.call(this, map); 10208 this.setOpacity(this.options.opacity); 10209 10210 if (this.options.interactive) { 10211 addClass(this._container, 'leaflet-interactive'); 10212 if (this._source) { 10213 this._source.addInteractiveTarget(this._container); 10214 } 10215 } 10216 10217 // @namespace Map 10218 // @section Tooltip events 10219 // @event tooltipopen: TooltipEvent 10220 // Fired when a tooltip is opened in the map. 10221 map.fire('tooltipopen', {tooltip: this}); 10222 10223 if (this._source) { 10224 // @namespace Layer 10225 // @section Tooltip events 10226 // @event tooltipopen: TooltipEvent 10227 // Fired when a tooltip bound to this layer is opened. 10228 this._source.fire('tooltipopen', {tooltip: this}, true); 10229 } 10230 }, 10231 10232 onRemove: function (map) { 10233 DivOverlay.prototype.onRemove.call(this, map); 10234 10235 if (this.options.interactive) { 10236 removeClass(this._container, 'leaflet-interactive'); 10237 if (this._source) { 10238 this._source.removeInteractiveTarget(this._container); 10239 } 10240 } 10241 10242 // @namespace Map 10243 // @section Tooltip events 10244 // @event tooltipclose: TooltipEvent 10245 // Fired when a tooltip in the map is closed. 10246 map.fire('tooltipclose', {tooltip: this}); 10247 10248 if (this._source) { 10249 // @namespace Layer 10250 // @section Tooltip events 10251 // @event tooltipclose: TooltipEvent 10252 // Fired when a tooltip bound to this layer is closed. 10253 this._source.fire('tooltipclose', {tooltip: this}, true); 10254 } 10255 }, 10256 10257 getEvents: function () { 10258 var events = DivOverlay.prototype.getEvents.call(this); 10259 10260 if (!this.options.permanent) { 10261 events.preclick = this._close; 10262 } 10263 10264 return events; 10265 }, 10266 10267 _close: function () { 10268 if (this._map) { 10269 this._map.closeTooltip(this); 10270 } 10271 }, 10272 10273 _initLayout: function () { 10274 var prefix = 'leaflet-tooltip', 10275 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 10276 10277 this._contentNode = this._container = create$1('div', className); 10278 }, 10279 10280 _updateLayout: function () {}, 10281 10282 _adjustPan: function () {}, 10283 10284 _setPosition: function (pos) { 10285 var subX, subY, 10286 map = this._map, 10287 container = this._container, 10288 centerPoint = map.latLngToContainerPoint(map.getCenter()), 10289 tooltipPoint = map.layerPointToContainerPoint(pos), 10290 direction = this.options.direction, 10291 tooltipWidth = container.offsetWidth, 10292 tooltipHeight = container.offsetHeight, 10293 offset = toPoint(this.options.offset), 10294 anchor = this._getAnchor(); 10295 10296 if (direction === 'top') { 10297 subX = tooltipWidth / 2; 10298 subY = tooltipHeight; 10299 } else if (direction === 'bottom') { 10300 subX = tooltipWidth / 2; 10301 subY = 0; 10302 } else if (direction === 'center') { 10303 subX = tooltipWidth / 2; 10304 subY = tooltipHeight / 2; 10305 } else if (direction === 'right') { 10306 subX = 0; 10307 subY = tooltipHeight / 2; 10308 } else if (direction === 'left') { 10309 subX = tooltipWidth; 10310 subY = tooltipHeight / 2; 10311 } else if (tooltipPoint.x < centerPoint.x) { 10312 direction = 'right'; 10313 subX = 0; 10314 subY = tooltipHeight / 2; 10315 } else { 10316 direction = 'left'; 10317 subX = tooltipWidth + (offset.x + anchor.x) * 2; 10318 subY = tooltipHeight / 2; 10319 } 10320 10321 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor); 10322 10323 removeClass(container, 'leaflet-tooltip-right'); 10324 removeClass(container, 'leaflet-tooltip-left'); 10325 removeClass(container, 'leaflet-tooltip-top'); 10326 removeClass(container, 'leaflet-tooltip-bottom'); 10327 addClass(container, 'leaflet-tooltip-' + direction); 10328 setPosition(container, pos); 10329 }, 10330 10331 _updatePosition: function () { 10332 var pos = this._map.latLngToLayerPoint(this._latlng); 10333 this._setPosition(pos); 10334 }, 10335 10336 setOpacity: function (opacity) { 10337 this.options.opacity = opacity; 10338 10339 if (this._container) { 10340 setOpacity(this._container, opacity); 10341 } 10342 }, 10343 10344 _animateZoom: function (e) { 10345 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 10346 this._setPosition(pos); 10347 }, 10348 10349 _getAnchor: function () { 10350 // Where should we anchor the tooltip on the source layer? 10351 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 10352 } 10353 10354 }); 10355 10356 // @namespace Tooltip 10357 // @factory L.tooltip(options?: Tooltip options, source?: Layer) 10358 // 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. 10359 var tooltip = function (options, source) { 10360 return new Tooltip(options, source); 10361 }; 10362 10363 // @namespace Map 10364 // @section Methods for Layers and Controls 10365 Map.include({ 10366 10367 // @method openTooltip(tooltip: Tooltip): this 10368 // Opens the specified tooltip. 10369 // @alternative 10370 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 10371 // Creates a tooltip with the specified content and options and open it. 10372 openTooltip: function (tooltip, latlng, options) { 10373 if (!(tooltip instanceof Tooltip)) { 10374 tooltip = new Tooltip(options).setContent(tooltip); 10375 } 10376 10377 if (latlng) { 10378 tooltip.setLatLng(latlng); 10379 } 10380 10381 if (this.hasLayer(tooltip)) { 10382 return this; 10383 } 10384 10385 return this.addLayer(tooltip); 10386 }, 10387 10388 // @method closeTooltip(tooltip: Tooltip): this 10389 // Closes the tooltip given as parameter. 10390 closeTooltip: function (tooltip) { 10391 return this.removeLayer(tooltip); 10392 } 10393 10394 }); 10395 10396 /* 10397 * @namespace Layer 10398 * @section Tooltip methods example 10399 * 10400 * All layers share a set of methods convenient for binding tooltips to it. 10401 * 10402 * ```js 10403 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 10404 * layer.openTooltip(); 10405 * layer.closeTooltip(); 10406 * ``` 10407 */ 10408 10409 // @section Tooltip methods 10410 Layer.include({ 10411 10412 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 10413 // Binds a tooltip to the layer with the passed `content` and sets up the 10414 // necessary event listeners. If a `Function` is passed it will receive 10415 // the layer as the first argument and should return a `String` or `HTMLElement`. 10416 bindTooltip: function (content, options) { 10417 10418 if (content instanceof Tooltip) { 10419 setOptions(content, options); 10420 this._tooltip = content; 10421 content._source = this; 10422 } else { 10423 if (!this._tooltip || options) { 10424 this._tooltip = new Tooltip(options, this); 10425 } 10426 this._tooltip.setContent(content); 10427 10428 } 10429 10430 this._initTooltipInteractions(); 10431 10432 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 10433 this.openTooltip(); 10434 } 10435 10436 return this; 10437 }, 10438 10439 // @method unbindTooltip(): this 10440 // Removes the tooltip previously bound with `bindTooltip`. 10441 unbindTooltip: function () { 10442 if (this._tooltip) { 10443 this._initTooltipInteractions(true); 10444 this.closeTooltip(); 10445 this._tooltip = null; 10446 } 10447 return this; 10448 }, 10449 10450 _initTooltipInteractions: function (remove) { 10451 if (!remove && this._tooltipHandlersAdded) { return; } 10452 var onOff = remove ? 'off' : 'on', 10453 events = { 10454 remove: this.closeTooltip, 10455 move: this._moveTooltip 10456 }; 10457 if (!this._tooltip.options.permanent) { 10458 events.mouseover = this._openTooltip; 10459 events.mouseout = this.closeTooltip; 10460 if (this._tooltip.options.sticky) { 10461 events.mousemove = this._moveTooltip; 10462 } 10463 events.click = this._openTooltip; 10464 } else { 10465 events.add = this._openTooltip; 10466 } 10467 this[onOff](events); 10468 this._tooltipHandlersAdded = !remove; 10469 }, 10470 10471 // @method openTooltip(latlng?: LatLng): this 10472 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 10473 openTooltip: function (latlng) { 10474 if (this._tooltip && this._tooltip._prepareOpen(latlng)) { 10475 // open the tooltip on the map 10476 this._map.openTooltip(this._tooltip, latlng); 10477 } 10478 10479 return this; 10480 }, 10481 10482 // @method closeTooltip(): this 10483 // Closes the tooltip bound to this layer if it is open. 10484 closeTooltip: function () { 10485 if (this._tooltip) { 10486 this._tooltip._close(); 10487 } 10488 return this; 10489 }, 10490 10491 // @method toggleTooltip(): this 10492 // Opens or closes the tooltip bound to this layer depending on its current state. 10493 toggleTooltip: function () { 10494 if (this._tooltip) { 10495 if (this._tooltip._map) { 10496 this.closeTooltip(); 10497 } else { 10498 this.openTooltip(); 10499 } 10500 } 10501 return this; 10502 }, 10503 10504 // @method isTooltipOpen(): boolean 10505 // Returns `true` if the tooltip bound to this layer is currently open. 10506 isTooltipOpen: function () { 10507 return this._tooltip.isOpen(); 10508 }, 10509 10510 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this 10511 // Sets the content of the tooltip bound to this layer. 10512 setTooltipContent: function (content) { 10513 if (this._tooltip) { 10514 this._tooltip.setContent(content); 10515 } 10516 return this; 10517 }, 10518 10519 // @method getTooltip(): Tooltip 10520 // Returns the tooltip bound to this layer. 10521 getTooltip: function () { 10522 return this._tooltip; 10523 }, 10524 10525 _openTooltip: function (e) { 10526 if (!this._tooltip || !this._map) { 10527 return; 10528 } 10529 this._tooltip._source = e.layer || e.target; 10530 10531 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined); 10532 }, 10533 10534 _moveTooltip: function (e) { 10535 var latlng = e.latlng, containerPoint, layerPoint; 10536 if (this._tooltip.options.sticky && e.originalEvent) { 10537 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 10538 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 10539 latlng = this._map.layerPointToLatLng(layerPoint); 10540 } 10541 this._tooltip.setLatLng(latlng); 10542 } 10543 }); 10544 10545 /* 10546 * @class DivIcon 10547 * @aka L.DivIcon 10548 * @inherits Icon 10549 * 10550 * Represents a lightweight icon for markers that uses a simple `<div>` 10551 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 10552 * 10553 * @example 10554 * ```js 10555 * var myIcon = L.divIcon({className: 'my-div-icon'}); 10556 * // you can set .my-div-icon styles in CSS 10557 * 10558 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 10559 * ``` 10560 * 10561 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 10562 */ 10563 10564 var DivIcon = Icon.extend({ 10565 options: { 10566 // @section 10567 // @aka DivIcon options 10568 iconSize: [12, 12], // also can be set through CSS 10569 10570 // iconAnchor: (Point), 10571 // popupAnchor: (Point), 10572 10573 // @option html: String|HTMLElement = '' 10574 // Custom HTML code to put inside the div element, empty by default. Alternatively, 10575 // an instance of `HTMLElement`. 10576 html: false, 10577 10578 // @option bgPos: Point = [0, 0] 10579 // Optional relative position of the background, in pixels 10580 bgPos: null, 10581 10582 className: 'leaflet-div-icon' 10583 }, 10584 10585 createIcon: function (oldIcon) { 10586 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 10587 options = this.options; 10588 10589 if (options.html instanceof Element) { 10590 empty(div); 10591 div.appendChild(options.html); 10592 } else { 10593 div.innerHTML = options.html !== false ? options.html : ''; 10594 } 10595 10596 if (options.bgPos) { 10597 var bgPos = toPoint(options.bgPos); 10598 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 10599 } 10600 this._setIconStyles(div, 'icon'); 10601 10602 return div; 10603 }, 10604 10605 createShadow: function () { 10606 return null; 10607 } 10608 }); 10609 10610 // @factory L.divIcon(options: DivIcon options) 10611 // Creates a `DivIcon` instance with the given options. 10612 function divIcon(options) { 10613 return new DivIcon(options); 10614 } 10615 10616 Icon.Default = IconDefault; 10617 10618 /* 10619 * @class GridLayer 10620 * @inherits Layer 10621 * @aka L.GridLayer 10622 * 10623 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 10624 * 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. 10625 * 10626 * 10627 * @section Synchronous usage 10628 * @example 10629 * 10630 * 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. 10631 * 10632 * ```js 10633 * var CanvasLayer = L.GridLayer.extend({ 10634 * createTile: function(coords){ 10635 * // create a <canvas> element for drawing 10636 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10637 * 10638 * // setup tile width and height according to the options 10639 * var size = this.getTileSize(); 10640 * tile.width = size.x; 10641 * tile.height = size.y; 10642 * 10643 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 10644 * var ctx = tile.getContext('2d'); 10645 * 10646 * // return the tile so it can be rendered on screen 10647 * return tile; 10648 * } 10649 * }); 10650 * ``` 10651 * 10652 * @section Asynchronous usage 10653 * @example 10654 * 10655 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. O
10655nce the tile is finished drawing it can be passed to the `done()` callback. 10656 * 10657 * ```js 10658 * var CanvasLayer = L.GridLayer.extend({ 10659 * createTile: function(coords, done){ 10660 * var error; 10661 * 10662 * // create a <canvas> element for drawing 10663 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10664 * 10665 * // setup tile width and height according to the options 10666 * var size = this.getTileSize(); 10667 * tile.width = size.x; 10668 * tile.height = size.y; 10669 * 10670 * // draw something asynchronously and pass the tile to the done() callback 10671 * setTimeout(function() { 10672 * done(error, tile); 10673 * }, 1000); 10674 * 10675 * return tile; 10676 * } 10677 * }); 10678 * ``` 10679 * 10680 * @section 10681 */ 10682 10683 10684 var GridLayer = Layer.extend({ 10685 10686 // @section 10687 // @aka GridLayer options 10688 options: { 10689 // @option tileSize: Number|Point = 256 10690 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 10691 tileSize: 256, 10692 10693 // @option opacity: Number = 1.0 10694 // Opacity of the tiles. Can be used in the `createTile()` function. 10695 opacity: 1, 10696 10697 // @option updateWhenIdle: Boolean = (depends) 10698 // Load new tiles only when panning ends. 10699 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 10700 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 10701 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 10702 updateWhenIdle: mobile, 10703 10704 // @option updateWhenZooming: Boolean = true 10705 // 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. 10706 updateWhenZooming: true, 10707 10708 // @option updateInterval: Number = 200 10709 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 10710 updateInterval: 200, 10711 10712 // @option zIndex: Number = 1 10713 // The explicit zIndex of the tile layer. 10714 zIndex: 1, 10715 10716 // @option bounds: LatLngBounds = undefined 10717 // If set, tiles will only be loaded inside the set `LatLngBounds`. 10718 bounds: null, 10719 10720 // @option minZoom: Number = 0 10721 // The minimum zoom level down to which this layer will be displayed (inclusive). 10722 minZoom: 0, 10723 10724 // @option maxZoom: Number = undefined 10725 // The maximum zoom level up to which this layer will be displayed (inclusive). 10726 maxZoom: undefined, 10727 10728 // @option maxNativeZoom: Number = undefined 10729 // Maximum zoom number the tile source has available. If it is specified, 10730 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 10731 // from `maxNativeZoom` level and auto-scaled. 10732 maxNativeZoom: undefined, 10733 10734 // @option minNativeZoom: Number = undefined 10735 // Minimum zoom number the tile source has available. If it is specified, 10736 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 10737 // from `minNativeZoom` level and auto-scaled. 10738 minNativeZoom: undefined, 10739 10740 // @option noWrap: Boolean = false 10741 // Whether the layer is wrapped around the antimeridian. If `true`, the 10742 // GridLayer will only be displayed once at low zoom levels. Has no 10743 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 10744 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 10745 // tiles outside the CRS limits. 10746 noWrap: false, 10747 10748 // @option pane: String = 'tilePane' 10749 // `Map pane` where the grid layer will be added. 10750 pane: 'tilePane', 10751 10752 // @option className: String = '' 10753 // A custom class name to assign to the tile layer. Empty by default. 10754 className: '', 10755 10756 // @option keepBuffer: Number = 2 10757 // When panning the map, keep this many rows and columns of tiles before unloading them. 10758 keepBuffer: 2 10759 }, 10760 10761 initialize: function (options) { 10762 setOptions(this, options); 10763 }, 10764 10765 onAdd: function () { 10766 this._initContainer(); 10767 10768 this._levels = {}; 10769 this._tiles = {}; 10770 10771 this._resetView(); // implicit _update() call 10772 }, 10773 10774 beforeAdd: function (map) {
vendor: 4,257 bytes, lines 10775-10923
10775 map._addZoomLimit(this); 10776 }, 10777 10778 onRemove: function (map) { 10779 this._removeAllTiles(); 10780 remove(this._container); 10781 map._removeZoomLimit(this); 10782 this._container = null; 10783 this._tileZoom = undefined; 10784 }, 10785 10786 // @method bringToFront: this 10787 // Brings the tile layer to the top of all tile layers. 10788 bringToFront: function () { 10789 if (this._map) { 10790 toFront(this._container); 10791 this._setAutoZIndex(Math.max); 10792 } 10793 return this; 10794 }, 10795 10796 // @method bringToBack: this 10797 // Brings the tile layer to the bottom of all tile layers. 10798 bringToBack: function () { 10799 if (this._map) { 10800 toBack(this._container); 10801 this._setAutoZIndex(Math.min); 10802 } 10803 return this; 10804 }, 10805 10806 // @method getContainer: HTMLElement 10807 // Returns the HTML element that contains the tiles for this layer. 10808 getContainer: function () { 10809 return this._container; 10810 }, 10811 10812 // @method setOpacity(opacity: Number): this 10813 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 10814 setOpacity: function (opacity) { 10815 this.options.opacity = opacity; 10816 this._updateOpacity(); 10817 return this; 10818 }, 10819 10820 // @method setZIndex(zIndex: Number): this 10821 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 10822 setZIndex: function (zIndex) { 10823 this.options.zIndex = zIndex; 10824 this._updateZIndex(); 10825 10826 return this; 10827 }, 10828 10829 // @method isLoading: Boolean 10830 // Returns `true` if any tile in the grid layer has not finished loading. 10831 isLoading: function () { 10832 return this._loading; 10833 }, 10834 10835 // @method redraw: this 10836 // Causes the layer to clear all the tiles and request them again. 10837 redraw: function () { 10838 if (this._map) { 10839 this._removeAllTiles(); 10840 var tileZoom = this._clampZoom(this._map.getZoom()); 10841 if (tileZoom !== this._tileZoom) { 10842 this._tileZoom = tileZoom; 10843 this._updateLevels(); 10844 } 10845 this._update(); 10846 } 10847 return this; 10848 }, 10849 10850 getEvents: function () { 10851 var events = { 10852 viewprereset: this._invalidateAll, 10853 viewreset: this._resetView, 10854 zoom: this._resetView, 10855 moveend: this._onMoveEnd 10856 }; 10857 10858 if (!this.options.updateWhenIdle) { 10859 // update tiles on move, but not more often than once per given interval 10860 if (!this._onMove) { 10861 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 10862 } 10863 10864 events.move = this._onMove; 10865 } 10866 10867 if (this._zoomAnimated) { 10868 events.zoomanim = this._animateZoom; 10869 } 10870 10871 return events; 10872 }, 10873 10874 // @section Extension methods 10875 // Layers extending `GridLayer` shall reimplement the following method. 10876 // @method createTile(coords: Object, done?: Function): HTMLElement 10877 // Called only internally, must be overridden by classes extending `GridLayer`. 10878 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 10879 // is specified, it must be called when the tile has finished loading and drawing. 10880 createTile: function () { 10881 return document.createElement('div'); 10882 }, 10883 10884 // @section 10885 // @method getTileSize: Point 10886 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 10887 getTileSize: function () { 10888 var s = this.options.tileSize; 10889 return s instanceof Point ? s : new Point(s, s); 10890 }, 10891 10892 _updateZIndex: function () { 10893 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 10894 this._container.style.zIndex = this.options.zIndex; 10895 } 10896 }, 10897 10898 _setAutoZIndex: function (compare) { 10899 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 10900 10901 var layers = this.getPane().children, 10902 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 10903 10904 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 10905 10906 zIndex = layers[i].style.zIndex; 10907 10908 if (layers[i] !== this._container && zIndex) { 10909 edgeZIndex = compare(edgeZIndex, +zIndex); 10910 } 10911 } 10912 10913 if (isFinite(edgeZIndex)) { 10914 this.options.zIndex = edgeZIndex + compare(-1, 1); 10915 this._updateZIndex(); 10916 } 10917 }, 10918 10919 _updateOpacity: function () { 10920 if (!this._map) { return; } 10921 10922 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 10923 if (
vendor: 6,720 bytes, lines 10923-11209
10923ielt9) { return; } 10924 10925 setOpacity(this._container, this.options.opacity); 10926 10927 var now = +new Date(), 10928 nextFrame = false, 10929 willPrune = false; 10930 10931 for (var key in this._tiles) { 10932 var tile = this._tiles[key]; 10933 if (!tile.current || !tile.loaded) { continue; } 10934 10935 var fade = Math.min(1, (now - tile.loaded) / 200); 10936 10937 setOpacity(tile.el, fade); 10938 if (fade < 1) { 10939 nextFrame = true; 10940 } else { 10941 if (tile.active) { 10942 willPrune = true; 10943 } else { 10944 this._onOpaqueTile(tile); 10945 } 10946 tile.active = true; 10947 } 10948 } 10949 10950 if (willPrune && !this._noPrune) { this._pruneTiles(); } 10951 10952 if (nextFrame) { 10953 cancelAnimFrame(this._fadeFrame); 10954 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 10955 } 10956 }, 10957 10958 _onOpaqueTile: falseFn, 10959 10960 _initContainer: function () { 10961 if (this._container) { return; } 10962 10963 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || '')); 10964 this._updateZIndex(); 10965 10966 if (this.options.opacity < 1) { 10967 this._updateOpacity(); 10968 } 10969 10970 this.getPane().appendChild(this._container); 10971 }, 10972 10973 _updateLevels: function () { 10974 10975 var zoom = this._tileZoom, 10976 maxZoom = this.options.maxZoom; 10977 10978 if (zoom === undefined) { return undefined; } 10979 10980 for (var z in this._levels) { 10981 z = Number(z); 10982 if (this._levels[z].el.children.length || z === zoom) { 10983 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 10984 this._onUpdateLevel(z); 10985 } else { 10986 remove(this._levels[z].el); 10987 this._removeTilesAtZoom(z); 10988 this._onRemoveLevel(z); 10989 delete this._levels[z]; 10990 } 10991 } 10992 10993 var level = this._levels[zoom], 10994 map = this._map; 10995 10996 if (!level) { 10997 level = this._levels[zoom] = {}; 10998 10999 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 11000 level.el.style.zIndex = maxZoom; 11001 11002 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 11003 level.zoom = zoom; 11004 11005 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 11006 11007 // force the browser to consider the newly added element for transition 11008 falseFn(level.el.offsetWidth); 11009 11010 this._onCreateLevel(level); 11011 } 11012 11013 this._level = level; 11014 11015 return level; 11016 }, 11017 11018 _onUpdateLevel: falseFn, 11019 11020 _onRemoveLevel: falseFn, 11021 11022 _onCreateLevel: falseFn, 11023 11024 _pruneTiles: function () { 11025 if (!this._map) { 11026 return; 11027 } 11028 11029 var key, tile; 11030 11031 var zoom = this._map.getZoom(); 11032 if (zoom > this.options.maxZoom || 11033 zoom < this.options.minZoom) { 11034 this._removeAllTiles(); 11035 return; 11036 } 11037 11038 for (key in this._tiles) { 11039 tile = this._tiles[key]; 11040 tile.retain = tile.current; 11041 } 11042 11043 for (key in this._tiles) { 11044 tile = this._tiles[key]; 11045 if (tile.current && !tile.active) { 11046 var coords = tile.coords; 11047 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 11048 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 11049 } 11050 } 11051 } 11052 11053 for (key in this._tiles) { 11054 if (!this._tiles[key].retain) { 11055 this._removeTile(key); 11056 } 11057 } 11058 }, 11059 11060 _removeTilesAtZoom: function (zoom) { 11061 for (var key in this._tiles) { 11062 if (this._tiles[key].coords.z !== zoom) { 11063 continue; 11064 } 11065 this._removeTile(key); 11066 } 11067 }, 11068 11069 _removeAllTiles: function () { 11070 for (var key in this._tiles) { 11071 this._removeTile(key); 11072 } 11073 }, 11074 11075 _invalidateAll: function () { 11076 for (var z in this._levels) { 11077 remove(this._levels[z].el); 11078 this._onRemoveLevel(Number(z)); 11079 delete this._levels[z]; 11080 } 11081 this._removeAllTiles(); 11082 11083 this._tileZoom = undefined; 11084 }, 11085 11086 _retainParent: function (x, y, z, minZoom) { 11087 var x2 = Math.floor(x / 2), 11088 y2 = Math.floor(y / 2), 11089 z2 = z - 1, 11090 coords2 = new Point(+x2, +y2); 11091 coords2.z = +z2; 11092 11093 var key = this._tileCoordsToKey(coords2), 11094 tile = this._tiles[key]; 11095 11096 if (tile && tile.active) { 11097 tile.retain = true; 11098 return true; 11099 11100 } else if (tile && tile.loaded) { 11101 tile.retain = true; 11102 } 11103 11104 if (z2 > minZoom) { 11105 return this._retainParent(x2, y2, z2, minZoom); 11106 } 11107 11108 return false; 11109 }, 11110 11111 _retainChildren: function (x, y, z, maxZoom) { 11112 11113 for (var i = 2 * x; i < 2 * x + 2; i++) { 11114 for (var j = 2 * y; j < 2 * y + 2; j++) { 11115 11116 var coords = new Point(i, j); 11117 coords.z = z + 1; 11118 11119 var key = this._tileCoordsToKey(coords), 11120 tile = this._tiles[key]; 11121 11122 if (tile && tile.active) { 11123 tile.retain = true; 11124 continue; 11125 11126 } else if (tile && tile.loaded) { 11127 tile.retain = true; 11128 } 11129 11130 if (z + 1 < maxZoom) { 11131 this._retainChildren(i, j, z + 1, maxZoom); 11132 } 11133 } 11134 } 11135 }, 11136 11137 _resetView: function (e) { 11138 var animating = e && (e.pinch || e.flyTo); 11139 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 11140 }, 11141 11142 _animateZoom: function (e) { 11143 this._setView(e.center, e.zoom, true, e.noUpdate); 11144 }, 11145 11146 _clampZoom: function (zoom) { 11147 var options = this.options; 11148 11149 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) { 11150 return options.minNativeZoom; 11151 } 11152 11153 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) { 11154 return options.maxNativeZoom; 11155 } 11156 11157 return zoom; 11158 }, 11159 11160 _setView: function (center, zoom, noPrune, noUpdate) { 11161 var tileZoom = Math.round(zoom); 11162 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 11163 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 11164 tileZoom = undefined; 11165 } else { 11166 tileZoom = this._clampZoom(tileZoom); 11167 } 11168 11169 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 11170 11171 if (!noUpdate || tileZoomChanged) { 11172 11173 this._tileZoom = tileZoom; 11174 11175 if (this._abortLoading) { 11176 this._abortLoading(); 11177 } 11178 11179 this._updateLevels(); 11180 this._resetGrid(); 11181 11182 if (tileZoom !== undefined) { 11183 this._update(center); 11184 } 11185 11186 if (!noPrune) { 11187 this._pruneTiles(); 11188 } 11189 11190 // Flag to prevent _updateOpacity from pruning tiles during 11191 // a zoom anim or a pinch gesture 11192 this._noPrune = !!noPrune; 11193 } 11194 11195 this._setZoomTransforms(center, zoom); 11196 }, 11197 11198 _setZoomTransforms: function (center, zoom) { 11199 for (var i in this._levels) { 11200 this._setZoomTransform(this._levels[i], center, zoom); 11201 } 11202 }, 11203 11204 _setZoomTransform: function (level, center, zoom) { 11205 var scale = this._map.getZoomScale(zoom, level.zoom), 11206 translate = level.origin.multiplyBy(scale) 11207 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 11208 11209 if (
vendor: 11,680 bytes, lines 11209-11579
11209any3d) { 11210 setTransform(level.el, translate, scale); 11211 } else { 11212 setPosition(level.el, translate); 11213 } 11214 }, 11215 11216 _resetGrid: function () { 11217 var map = this._map, 11218 crs = map.options.crs, 11219 tileSize = this._tileSize = this.getTileSize(), 11220 tileZoom = this._tileZoom; 11221 11222 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 11223 if (bounds) { 11224 this._globalTileRange = this._pxBoundsToTileRange(bounds); 11225 } 11226 11227 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 11228 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 11229 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 11230 ]; 11231 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 11232 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 11233 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 11234 ]; 11235 }, 11236 11237 _onMoveEnd: function () { 11238 if (!this._map || this._map._animatingZoom) { return; } 11239 11240 this._update(); 11241 }, 11242 11243 _getTiledPixelBounds: function (center) { 11244 var map = this._map, 11245 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 11246 scale = map.getZoomScale(mapZoom, this._tileZoom), 11247 pixelCenter = map.project(center, this._tileZoom).floor(), 11248 halfSize = map.getSize().divideBy(scale * 2); 11249 11250 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 11251 }, 11252 11253 // Private method to load tiles in the grid's active zoom level according to map bounds 11254 _update: function (center) { 11255 var map = this._map; 11256 if (!map) { return; } 11257 var zoom = this._clampZoom(map.getZoom()); 11258 11259 if (center === undefined) { center = map.getCenter(); } 11260 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 11261 11262 var pixelBounds = this._getTiledPixelBounds(center), 11263 tileRange = this._pxBoundsToTileRange(pixelBounds), 11264 tileCenter = tileRange.getCenter(), 11265 queue = [], 11266 margin = this.options.keepBuffer, 11267 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 11268 tileRange.getTopRight().add([margin, -margin])); 11269 11270 // Sanity check: panic if the tile range contains Infinity somewhere. 11271 if (!(isFinite(tileRange.min.x) && 11272 isFinite(tileRange.min.y) && 11273 isFinite(tileRange.max.x) && 11274 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 11275 11276 for (var key in this._tiles) { 11277 var c = this._tiles[key].coords; 11278 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 11279 this._tiles[key].current = false; 11280 } 11281 } 11282 11283 // _update just loads more tiles. If the tile zoom level differs too much 11284 // from the map's, let _setView reset levels and prune old tiles. 11285 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 11286 11287 // create a queue of coordinates to load tiles from 11288 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 11289 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 11290 var coords = new Point(i, j); 11291 coords.z = this._tileZoom; 11292 11293 if (!this._isValidTile(coords)) { continue; } 11294 11295 var tile = this._tiles[this._tileCoordsToKey(coords)]; 11296 if (tile) { 11297 tile.current = true; 11298 } else { 11299 queue.push(coords); 11300 } 11301 } 11302 } 11303 11304 // sort tile queue to load tiles in order of their distance to center 11305 queue.sort(function (a, b) { 11306 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 11307 }); 11308 11309 if (queue.length !== 0) { 11310 // if it's the first batch of tiles to load 11311 if (!this._loading) { 11312 this._loading = true; 11313 // @event loading: Event 11314 // Fired when the grid layer starts loading tiles. 11315 this.fire('loading'); 11316 } 11317 11318 // create DOM fragment to append tiles in one batch 11319 var fragment = document.createDocumentFragment(); 11320 11321 for (i = 0; i < queue.length; i++) { 11322 this._addTile(queue[i], fragment); 11323 } 11324 11325 this._level.el.appendChild(fragment); 11326 } 11327 }, 11328 11329 _isValidTile: function (coords) { 11330 var crs = this._map.options.crs; 11331 11332 if (!crs.infinite) { 11333 // don't load tile if it's out of bounds and not wrapped 11334 var bounds = this._globalTileRange; 11335 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 11336 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 11337 } 11338 11339 if (!this.options.bounds) { return true; } 11340 11341 // don't load tile if it doesn't intersect the bounds in options 11342 var tileBounds = this._tileCoordsToBounds(coords); 11343 return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 11344 }, 11345 11346 _keyToBounds: function (key) { 11347 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 11348 }, 11349 11350 _tileCoordsToNwSe: function (coords) { 11351 var map = this._map, 11352 tileSize = this.getTileSize(), 11353 nwPoint = coords.scaleBy(tileSize), 11354 sePoint = nwPoint.add(tileSize), 11355 nw = map.unproject(nwPoint, coords.z), 11356 se = map.unproject(sePoint, coords.z); 11357 return [nw, se]; 11358 }, 11359 11360 // converts tile coordinates to its geographical bounds 11361 _tileCoordsToBounds: function (coords) { 11362 var bp = this._tileCoordsToNwSe(coords), 11363 bounds = new LatLngBounds(bp[0], bp[1]); 11364 11365 if (!this.options.noWrap) { 11366 bounds = this._map.wrapLatLngBounds(bounds); 11367 } 11368 return bounds; 11369 }, 11370 // converts tile coordinates to key for the tile cache 11371 _tileCoordsToKey: function (coords) { 11372 return coords.x + ':' + coords.y + ':' + coords.z; 11373 }, 11374 11375 // converts tile cache key to coordinates 11376 _keyToTileCoords: function (key) { 11377 var k = key.split(':'), 11378 coords = new Point(+k[0], +k[1]); 11379 coords.z = +k[2]; 11380 return coords; 11381 }, 11382 11383 _removeTile: function (key) { 11384 var tile = this._tiles[key]; 11385 if (!tile) { return; } 11386 11387 remove(tile.el); 11388 11389 delete this._tiles[key]; 11390 11391 // @event tileunload: TileEvent 11392 // Fired when a tile is removed (e.g. when a tile goes off the screen). 11393 this.fire('tileunload', { 11394 tile: tile.el, 11395 coords: this._keyToTileCoords(key) 11396 }); 11397 }, 11398 11399 _initTile: function (tile) { 11400 addClass(tile, 'leaflet-tile'); 11401 11402 var tileSize = this.getTileSize(); 11403 tile.style.width = tileSize.x + 'px'; 11404 tile.style.height = tileSize.y + 'px'; 11405 11406 tile.onselectstart = falseFn; 11407 tile.onmousemove = falseFn; 11408 11409 // update opacity on tiles in IE7-8 because of filter inheritance problems 11410 if (ielt9 && this.options.opacity < 1) { 11411 setOpacity(tile, this.options.opacity); 11412 } 11413 }, 11414 11415 _addTile: function (coords, container) { 11416 var tilePos = this._getTilePos(coords), 11417 key = this._tileCoordsToKey(coords); 11418 11419 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 11420 11421 this._initTile(tile); 11422 11423 // if createTile is defined with a second argument ("done" callback), 11424 // we know that tile is async and will be ready later; otherwise 11425 if (this.createTile.length < 2) { 11426 // mark tile as ready, but delay one frame for opacity animation to happen 11427 requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 11428 } 11429 11430 setPosition(tile, tilePos); 11431 11432 // save tile in cache 11433 this._tiles[key] = { 11434 el: tile, 11435 coords: coords, 11436 current: true 11437 }; 11438 11439 container.appendChild(tile); 11440 // @event tileloadstart: TileEvent 11441 // Fired when a tile is requested and starts loading. 11442 this.fire('tileloadstart', { 11443 tile: tile, 11444 coords: coords 11445 }); 11446 }, 11447 11448 _tileReady: function (coords, err, tile) { 11449 if (err) { 11450 // @event tileerror: TileErrorEvent 11451 // Fired when there is an error loading a tile. 11452 this.fire('tileerror', { 11453 error: err, 11454 tile: tile, 11455 coords: coords 11456 }); 11457 } 11458 11459 var key = this._tileCoordsToKey(coords); 11460 11461 tile = this._tiles[key]; 11462 if (!tile) { return; } 11463 11464 tile.loaded = +new Date(); 11465 if (this._map._fadeAnimated) { 11466 setOpacity(tile.el, 0); 11467 cancelAnimFrame(this._fadeFrame); 11468 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11469 } else { 11470 tile.active = true; 11471 this._pruneTiles(); 11472 } 11473 11474 if (!err) { 11475 addClass(tile.el, 'leaflet-tile-loaded'); 11476 11477 // @event tileload: TileEvent 11478 // Fired when a tile loads. 11479 this.fire('tileload', { 11480 tile: tile.el, 11481 coords: coords 11482 }); 11483 } 11484 11485 if (this._noTilesToLoad()) { 11486 this._loading = false; 11487 // @event load: Event 11488 // Fired when the grid layer loaded all visible tiles. 11489 this.fire('load'); 11490 11491 if (ielt9 || !this._map._fadeAnimated) { 11492 requestAnimFrame(this._pruneTiles, this); 11493 } else { 11494 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 11495 // to trigger a pruning. 11496 setTimeout(bind(this._pruneTiles, this), 250); 11497 } 11498 } 11499 }, 11500 11501 _getTilePos: function (coords) { 11502 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 11503 }, 11504 11505 _wrapCoords: function (coords) { 11506 var newCoords = new Point( 11507 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 11508 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y); 11509 newCoords.z = coords.z; 11510 return newCoords; 11511 }, 11512 11513 _pxBoundsToTileRange: function (bounds) { 11514 var tileSize = this.getTileSize(); 11515 return new Bounds( 11516 bounds.min.unscaleBy(tileSize).floor(), 11517 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 11518 }, 11519 11520 _noTilesToLoad: function () { 11521 for (var key in this._tiles) { 11522 if (!this._tiles[key].loaded) { return false; } 11523 } 11524 return true; 11525 } 11526 }); 11527 11528 // @factory L.gridLayer(options?: GridLayer options) 11529 // Creates a new instance of GridLayer with the supplied options. 11530 function gridLayer(options) { 11531 return new GridLayer(options); 11532 } 11533 11534 /* 11535 * @class TileLayer 11536 * @inherits GridLayer 11537 * @aka L.TileLayer 11538 * 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`. 11539 * 11540 * @example 11541 * 11542 * ```js 11543 * 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); 11544 * ``` 11545 * 11546 * @section URL template 11547 * @example 11548 * 11549 * A string of the following form: 11550 * 11551 * ``` 11552 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 11553 * ``` 11554 * 11555 * `{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. 11556 * 11557 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 11558 * 11559 * ``` 11560 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 11561 * ``` 11562 */ 11563 11564 11565 var TileLayer = GridLayer.extend({ 11566 11567 // @section 11568 // @aka TileLayer options 11569 options: { 11570 // @option minZoom: Number = 0 11571 // The minimum zoom level down to which this layer will be displayed (inclusive). 11572 minZoom: 0, 11573 11574 // @option maxZoom: Number = 18 11575 // The maximum zoom level up to which this layer will be displayed (inclusive). 11576 maxZoom: 18, 11577 11578 // @option subdomains: String|String[] = 'abc' 11579 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of str
11579ings. 11580 subdomains: 'abc', 11581 11582 // @option errorTileUrl: String = '' 11583 // URL to the tile image to show in place of the tile that failed to load. 11584 errorTileUrl: '', 11585 11586 // @option zoomOffset: Number = 0 11587 // The zoom number used in tile URLs will be offset with this value. 11588 zoomOffset: 0, 11589 11590 // @option tms: Boolean = false 11591 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 11592 tms: false, 11593 11594 // @option zoomReverse: Boolean = false 11595 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 11596 zoomReverse: false, 11597 11598 // @option detectRetina: Boolean = false 11599 // 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. 11600 detectRetina: false, 11601 11602 // @option crossOrigin: Boolean|String = false 11603 // Whether the crossOrigin attribute will be added to the tiles. 11604 // 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. 11605 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 11606 crossOrigin: false 11607 }, 11608 11609 initialize: function (url, options) { 11610 11611 this._url = url; 11612 11613 options = setOptions(this, options); 11614 11615 // detecting retina displays, adjusting tileSize and zoom levels 11616 if (options.detectRetina && retina && options.maxZoom > 0) { 11617 11618 options.tileSize = Math.floor(options.tileSize / 2); 11619 11620 if (!options.zoomReverse) { 11621 options.zoomOffset++; 11622 options.maxZoom--; 11623 } else { 11624 options.zoomOffset--; 11625 options.minZoom++; 11626 } 11627 11628 options.minZoom = Math.max(0, options.minZoom); 11629 } 11630 11631 if (typeof options.subdomains === 'string') { 11632 options.subdomains = options.subdomains.split(''); 11633 } 11634 11635 this.on('tileunload', this._onTileRemove); 11636 }, 11637 11638 // @method setUrl(url: String, noRedraw?: Boolean): this 11639 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 11640 // If the URL does not change, the layer will not be redrawn unless 11641 // the noRedraw parameter is set to false. 11642 setUrl: function (url, noRedraw) { 11643 if (this._url === url && noRedraw === undefined) { 11644 noRedraw = true; 11645 } 11646 11647 this._url = url; 11648 11649 if (!noRedraw) { 11650 this.redraw(); 11651 } 11652 return this; 11653 }, 11654 11655 // @method createTile(coords: Object, done?: Function): HTMLElement 11656 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 11657 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 11658 // callback is called when the tile has been loaded. 11659 createTile: function (coords, done) { 11660 var tile = document.createElement('img'); 11661 11662 on(tile, 'load', bind(this._tileOnLoad, this, done, tile)); 11663 on(tile, 'error', bind(this._tileOnError, this, done, tile)); 11664 11665 if (this.options.crossOrigin || this.options.crossOrigin === '') { 11666 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 11667 } 11668 11669 /* 11670 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons 11671 http://www.w3.org/TR/WCAG20-TECHS/H67 11672 */ 11673 tile.alt = ''; 11674 11675 /* 11676 Set role="presentation" to force screen readers to ignore this 11677 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation 11678 */ 11679 tile.setAttribute('role', 'presentation'); 11680 11681 tile.src = this.getTileUrl(coords); 11682 11683 return tile; 11684 }, 11685 11686 // @section Extension methods 11687 // @uninheritable 11688 // Layers extending `TileLayer` might reimplement the following method. 11689 // @method getTileUrl(coords: Object): String 11690 // Called only internally, returns the URL for a tile given its coordinates. 11691 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 11692 getTileUrl: function (coords) { 11693 var data = { 11694 r: retina ? '@2x' : '', 11695 s: this._getSubdomain(coords), 11696 x: coords.x, 11697 y: coords.y, 11698 z: this._getZoomForUrl() 11699 }; 11700 if (this._map && !this._map.options.crs.infinite) { 11701 var invertedY = this._globalTileRange.max.y - coords.y; 11702 if (this.options.tms) { 11703 data['y'] = invertedY; 11704 }
vendor: 13,334 bytes, lines 11705-12177
11705 data['-y'] = invertedY; 11706 } 11707 11708 return template(this._url, extend(data, this.options)); 11709 }, 11710 11711 _tileOnLoad: function (done, tile) { 11712 // For https://github.com/Leaflet/Leaflet/issues/3332 11713 if (ielt9) { 11714 setTimeout(bind(done, this, null, tile), 0); 11715 } else { 11716 done(null, tile); 11717 } 11718 }, 11719 11720 _tileOnError: function (done, tile, e) { 11721 var errorUrl = this.options.errorTileUrl; 11722 if (errorUrl && tile.getAttribute('src') !== errorUrl) { 11723 tile.src = errorUrl; 11724 } 11725 done(e, tile); 11726 }, 11727 11728 _onTileRemove: function (e) { 11729 e.tile.onload = null; 11730 }, 11731 11732 _getZoomForUrl: function () { 11733 var zoom = this._tileZoom, 11734 maxZoom = this.options.maxZoom, 11735 zoomReverse = this.options.zoomReverse, 11736 zoomOffset = this.options.zoomOffset; 11737 11738 if (zoomReverse) { 11739 zoom = maxZoom - zoom; 11740 } 11741 11742 return zoom + zoomOffset; 11743 }, 11744 11745 _getSubdomain: function (tilePoint) { 11746 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 11747 return this.options.subdomains[index]; 11748 }, 11749 11750 // stops loading all tiles in the background layer 11751 _abortLoading: function () { 11752 var i, tile; 11753 for (i in this._tiles) { 11754 if (this._tiles[i].coords.z !== this._tileZoom) { 11755 tile = this._tiles[i].el; 11756 11757 tile.onload = falseFn; 11758 tile.onerror = falseFn; 11759 11760 if (!tile.complete) { 11761 tile.src = emptyImageUrl; 11762 var coords = this._tiles[i].coords; 11763 remove(tile); 11764 delete this._tiles[i]; 11765 // @event tileabort: TileEvent 11766 // Fired when a tile was loading but is now not wanted. 11767 this.fire('tileabort', { 11768 tile: tile, 11769 coords: coords 11770 }); 11771 } 11772 } 11773 } 11774 }, 11775 11776 _removeTile: function (key) { 11777 var tile = this._tiles[key]; 11778 if (!tile) { return; } 11779 11780 // Cancels any pending http requests associated with the tile 11781 tile.el.setAttribute('src', emptyImageUrl); 11782 11783 return GridLayer.prototype._removeTile.call(this, key); 11784 }, 11785 11786 _tileReady: function (coords, err, tile) { 11787 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) { 11788 return; 11789 } 11790 11791 return GridLayer.prototype._tileReady.call(this, coords, err, tile); 11792 } 11793 }); 11794 11795 11796 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 11797 // Instantiates a tile layer object given a `URL template` and optionally an options object. 11798 11799 function tileLayer(url, options) { 11800 return new TileLayer(url, options); 11801 } 11802 11803 /* 11804 * @class TileLayer.WMS 11805 * @inherits TileLayer 11806 * @aka L.TileLayer.WMS 11807 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 11808 * 11809 * @example 11810 * 11811 * ```js 11812 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 11813 * layers: 'nexrad-n0r-900913', 11814 * format: 'image/png', 11815 * transparent: true, 11816 * attribution: "Weather data © 2012 IEM Nexrad" 11817 * }); 11818 * ``` 11819 */ 11820 11821 var TileLayerWMS = TileLayer.extend({ 11822 11823 // @section 11824 // @aka TileLayer.WMS options 11825 // If any custom options not documented here are used, they will be sent to the 11826 // WMS server as extra parameters in each request URL. This can be useful for 11827 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 11828 defaultWmsParams: { 11829 service: 'WMS', 11830 request: 'GetMap', 11831 11832 // @option layers: String = '' 11833 // **(required)** Comma-separated list of WMS layers to show. 11834 layers: '', 11835 11836 // @option styles: String = '' 11837 // Comma-separated list of WMS styles. 11838 styles: '', 11839 11840 // @option format: String = 'image/jpeg' 11841 // WMS image format (use `'image/png'` for layers with transparency). 11842 format: 'image/jpeg', 11843 11844 // @option transparent: Boolean = false 11845 // If `true`, the WMS service will return images with transparency. 11846 transparent: false, 11847 11848 // @option version: String = '1.1.1' 11849 // Version of the WMS service to use 11850 version: '1.1.1' 11851 }, 11852 11853 options: { 11854 // @option crs: CRS = null 11855 // Coordinate Reference System to use for the WMS requests, defaults to 11856 // map CRS. Don't change this if you're not sure what it means. 11857 crs: null, 11858 11859 // @option uppercase: Boolean = false 11860 // If `true`, WMS request parameter keys will be uppercase. 11861 uppercase: false 11862 }, 11863 11864 initialize: function (url, options) { 11865 11866 this._url = url; 11867 11868 var wmsParams = extend({}, this.defaultWmsParams); 11869 11870 // all keys that are not TileLayer options go to WMS params 11871 for (var i in options) { 11872 if (!(i in this.options)) { 11873 wmsParams[i] = options[i]; 11874 } 11875 } 11876 11877 options = setOptions(this, options); 11878 11879 var realRetina = options.detectRetina && retina ? 2 : 1; 11880 var tileSize = this.getTileSize(); 11881 wmsParams.width = tileSize.x * realRetina; 11882 wmsParams.height = tileSize.y * realRetina; 11883 11884 this.wmsParams = wmsParams; 11885 }, 11886 11887 onAdd: function (map) { 11888 11889 this._crs = this.options.crs || map.options.crs; 11890 this._wmsVersion = parseFloat(this.wmsParams.version); 11891 11892 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 11893 this.wmsParams[projectionKey] = this._crs.code; 11894 11895 TileLayer.prototype.onAdd.call(this, map); 11896 }, 11897 11898 getTileUrl: function (coords) { 11899 11900 var tileBounds = this._tileCoordsToNwSe(coords), 11901 crs = this._crs, 11902 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), 11903 min = bounds.min, 11904 max = bounds.max, 11905 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? 11906 [min.y, min.x, max.y, max.x] : 11907 [min.x, min.y, max.x, max.y]).join(','), 11908 url = TileLayer.prototype.getTileUrl.call(this, coords); 11909 return url + 11910 getParamString(this.wmsParams, url, this.options.uppercase) + 11911 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 11912 }, 11913 11914 // @method setParams(params: Object, noRedraw?: Boolean): this 11915 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 11916 setParams: function (params, noRedraw) { 11917 11918 extend(this.wmsParams, params); 11919 11920 if (!noRedraw) { 11921 this.redraw(); 11922 } 11923 11924 return this; 11925 } 11926 }); 11927 11928 11929 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 11930 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 11931 function tileLayerWMS(url, options) { 11932 return new TileLayerWMS(url, options); 11933 } 11934 11935 TileLayer.WMS = TileLayerWMS; 11936 tileLayer.wms = tileLayerWMS; 11937 11938 /* 11939 * @class Renderer 11940 * @inherits Layer 11941 * @aka L.Renderer 11942 * 11943 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 11944 * DOM container of the renderer, its bounds, and its zoom animation. 11945 * 11946 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 11947 * itself can be added or removed to the map. All paths use a renderer, which can 11948 * be implicit (the map will decide the type of renderer and use it automatically) 11949 * or explicit (using the [`renderer`](#path-renderer) option of the path). 11950 * 11951 * Do not use this class directly, use `SVG` and `Canvas` instead. 11952 * 11953 * @event update: Event 11954 * Fired when the renderer updates its bounds, center and zoom, for example when 11955 * its map has moved 11956 */ 11957 11958 var Renderer = Layer.extend({ 11959 11960 // @section 11961 // @aka Renderer options 11962 options: { 11963 // @option padding: Number = 0.1 11964 // How much to extend the clip area around the map view (relative to its size) 11965 // e.g. 0.1 would be 10% of map view in each direction 11966 padding: 0.1 11967 }, 11968 11969 initialize: function (options) { 11970 setOptions(this, options); 11971 stamp(this); 11972 this._layers = this._layers || {}; 11973 }, 11974 11975 onAdd: function () { 11976 if (!this._container) { 11977 this._initContainer(); // defined by renderer implementations 11978 11979 if (this._zoomAnimated) { 11980 addClass(this._container, 'leaflet-zoom-animated'); 11981 } 11982 } 11983 11984 this.getPane().appendChild(this._container); 11985 this._update(); 11986 this.on('update', this._updatePaths, this); 11987 }, 11988 11989 onRemove: function () { 11990 this.off('update', this._updatePaths, this); 11991 this._destroyContainer(); 11992 }, 11993 11994 getEvents: function () { 11995 var events = { 11996 viewreset: this._reset, 11997 zoom: this._onZoom, 11998 moveend: this._update, 11999 zoomend: this._onZoomEnd 12000 }; 12001 if (this._zoomAnimated) { 12002 events.zoomanim = this._onAnimZoom; 12003 } 12004 return events; 12005 }, 12006 12007 _onAnimZoom: function (ev) { 12008 this._updateTransform(ev.center, ev.zoom); 12009 }, 12010 12011 _onZoom: function () { 12012 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 12013 }, 12014 12015 _updateTransform: function (center, zoom) { 12016 var scale = this._map.getZoomScale(zoom, this._zoom), 12017 position = getPosition(this._container), 12018 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 12019 currentCenterPoint = this._map.project(this._center, zoom), 12020 destCenterPoint = this._map.project(center, zoom), 12021 centerOffset = destCenterPoint.subtract(currentCenterPoint), 12022 12023 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset); 12024 12025 if (any3d) { 12026 setTransform(this._container, topLeftOffset, scale); 12027 } else { 12028 setPosition(this._container, topLeftOffset); 12029 } 12030 }, 12031 12032 _reset: function () { 12033 this._update(); 12034 this._updateTransform(this._center, this._zoom); 12035 12036 for (var id in this._layers) { 12037 this._layers[id]._reset(); 12038 } 12039 }, 12040 12041 _onZoomEnd: function () { 12042 for (var id in this._layers) { 12043 this._layers[id]._project(); 12044 } 12045 }, 12046 12047 _updatePaths: function () { 12048 for (var id in this._layers) { 12049 this._layers[id]._update(); 12050 } 12051 }, 12052 12053 _update: function () { 12054 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 12055 // Subclasses are responsible of firing the 'update' event. 12056 var p = this.options.padding, 12057 size = this._map.getSize(), 12058 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 12059 12060 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 12061 12062 this._center = this._map.getCenter(); 12063 this._zoom = this._map.getZoom(); 12064 } 12065 }); 12066 12067 /* 12068 * @class Canvas 12069 * @inherits Renderer 12070 * @aka L.Canvas 12071 * 12072 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 12073 * Inherits `Renderer`. 12074 * 12075 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not 12076 * available in all web browsers, notably IE8, and overlapping geometries might 12077 * not display properly in some edge cases. 12078 * 12079 * @example 12080 * 12081 * Use Canvas by default for all paths in the map: 12082 * 12083 * ```js 12084 * var map = L.map('map', { 12085 * renderer: L.canvas() 12086 * }); 12087 * ``` 12088 * 12089 * Use a Canvas renderer with extra padding for specific vector geometries: 12090 * 12091 * ```js 12092 * var map = L.map('map'); 12093 * var myRenderer = L.canvas({ padding: 0.5 }); 12094 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12095 * var circle = L.circle( center, { renderer: myRenderer } ); 12096 * ``` 12097 */ 12098 12099 var Canvas = Renderer.extend({ 12100 12101 // @section 12102 // @aka Canvas options 12103 options: { 12104 // @option tolerance: Number = 0 12105 // How much to extend the click tolerance around a path/object on the map. 12106 tolerance: 0 12107 }, 12108 12109 getEvents: function () { 12110 var events = Renderer.prototype.getEvents.call(this); 12111 events.viewprereset = this._onViewPreReset; 12112 return events; 12113 }, 12114 12115 _onViewPreReset: function () { 12116 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 12117 this._postponeUpdatePaths = true; 12118 }, 12119 12120 onAdd: function () { 12121 Renderer.prototype.onAdd.call(this); 12122 12123 // Redraw vectors since canvas is cleared upon removal, 12124 // in case of removing the renderer itself from the map. 12125 this._draw(); 12126 }, 12127 12128 _initContainer: function () { 12129 var container = this._container = document.createElement('canvas'); 12130 12131 on(container, 'mousemove', this._onMouseMove, this); 12132 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); 12133 on(container, 'mouseout', this._handleMouseOut, this); 12134 container['_leaflet_disable_events'] = true; 12135 12136 this._ctx = container.getContext('2d'); 12137 }, 12138 12139 _destroyContainer: function () { 12140 cancelAnimFrame(this._redrawRequest); 12141 delete this._ctx; 12142 remove(this._container); 12143 off(this._container); 12144 delete this._container; 12145 }, 12146 12147 _updatePaths: function () { 12148 if (this._postponeUpdatePaths) { return; } 12149 12150 var layer; 12151 this._redrawBounds = null; 12152 for (var id in this._layers) { 12153 layer = this._layers[id]; 12154 layer._update(); 12155 } 12156 this._redraw(); 12157 }, 12158 12159 _update: function () { 12160 if (this._map._animatingZoom && this._bounds) { return; } 12161 12162 Renderer.prototype._update.call(this); 12163 12164 var b = this._bounds, 12165 container = this._container, 12166 size = b.getSize(), 12167 m = retina ? 2 : 1; 12168 12169 setPosition(container, b.min); 12170 12171 // set canvas size (also clearing it); use double size on retina 12172 container.width = m * size.x; 12173 container.height = m * size.y; 12174 container.style.width = size.x + 'px'; 12175 container.style.height = size.y + 'px'; 12176 12177 if (
vendor: 9,280 bytes, lines 12177-12550
12177retina) { 12178 this._ctx.scale(2, 2); 12179 } 12180 12181 // translate so we use the same path coordinates after canvas element moves 12182 this._ctx.translate(-b.min.x, -b.min.y); 12183 12184 // Tell paths to redraw themselves 12185 this.fire('update'); 12186 }, 12187 12188 _reset: function () { 12189 Renderer.prototype._reset.call(this); 12190 12191 if (this._postponeUpdatePaths) { 12192 this._postponeUpdatePaths = false; 12193 this._updatePaths(); 12194 } 12195 }, 12196 12197 _initPath: function (layer) { 12198 this._updateDashArray(layer); 12199 this._layers[stamp(layer)] = layer; 12200 12201 var order = layer._order = { 12202 layer: layer, 12203 prev: this._drawLast, 12204 next: null 12205 }; 12206 if (this._drawLast) { this._drawLast.next = order; } 12207 this._drawLast = order; 12208 this._drawFirst = this._drawFirst || this._drawLast; 12209 }, 12210 12211 _addPath: function (layer) { 12212 this._requestRedraw(layer); 12213 }, 12214 12215 _removePath: function (layer) { 12216 var order = layer._order; 12217 var next = order.next; 12218 var prev = order.prev; 12219 12220 if (next) { 12221 next.prev = prev; 12222 } else { 12223 this._drawLast = prev; 12224 } 12225 if (prev) { 12226 prev.next = next; 12227 } else { 12228 this._drawFirst = next; 12229 } 12230 12231 delete layer._order; 12232 12233 delete this._layers[stamp(layer)]; 12234 12235 this._requestRedraw(layer); 12236 }, 12237 12238 _updatePath: function (layer) { 12239 // Redraw the union of the layer's old pixel 12240 // bounds and the new pixel bounds. 12241 this._extendRedrawBounds(layer); 12242 layer._project(); 12243 layer._update(); 12244 // The redraw will extend the redraw bounds 12245 // with the new pixel bounds. 12246 this._requestRedraw(layer); 12247 }, 12248 12249 _updateStyle: function (layer) { 12250 this._updateDashArray(layer); 12251 this._requestRedraw(layer); 12252 }, 12253 12254 _updateDashArray: function (layer) { 12255 if (typeof layer.options.dashArray === 'string') { 12256 var parts = layer.options.dashArray.split(/[, ]+/), 12257 dashArray = [], 12258 dashValue, 12259 i; 12260 for (i = 0; i < parts.length; i++) { 12261 dashValue = Number(parts[i]); 12262 // Ignore dash array containing invalid lengths 12263 if (isNaN(dashValue)) { return; } 12264 dashArray.push(dashValue); 12265 } 12266 layer.options._dashArray = dashArray; 12267 } else { 12268 layer.options._dashArray = layer.options.dashArray; 12269 } 12270 }, 12271 12272 _requestRedraw: function (layer) { 12273 if (!this._map) { return; } 12274 12275 this._extendRedrawBounds(layer); 12276 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 12277 }, 12278 12279 _extendRedrawBounds: function (layer) { 12280 if (layer._pxBounds) { 12281 var padding = (layer.options.weight || 0) + 1; 12282 this._redrawBounds = this._redrawBounds || new Bounds(); 12283 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 12284 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 12285 } 12286 }, 12287 12288 _redraw: function () { 12289 this._redrawRequest = null; 12290 12291 if (this._redrawBounds) { 12292 this._redrawBounds.min._floor(); 12293 this._redrawBounds.max._ceil(); 12294 } 12295 12296 this._clear(); // clear layers in redraw bounds 12297 this._draw(); // draw layers 12298 12299 this._redrawBounds = null; 12300 }, 12301 12302 _clear: function () { 12303 var bounds = this._redrawBounds; 12304 if (bounds) { 12305 var size = bounds.getSize(); 12306 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 12307 } else { 12308 this._ctx.save(); 12309 this._ctx.setTransform(1, 0, 0, 1, 0, 0); 12310 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 12311 this._ctx.restore(); 12312 } 12313 }, 12314 12315 _draw: function () { 12316 var layer, bounds = this._redrawBounds; 12317 this._ctx.save(); 12318 if (bounds) { 12319 var size = bounds.getSize(); 12320 this._ctx.beginPath(); 12321 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 12322 this._ctx.clip(); 12323 } 12324 12325 this._drawing = true; 12326 12327 for (var order = this._drawFirst; order; order = order.next) { 12328 layer = order.layer; 12329 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 12330 layer._updatePath(); 12331 } 12332 } 12333 12334 this._drawing = false; 12335 12336 this._ctx.restore(); // Restore state before clipping. 12337 }, 12338 12339 _updatePoly: function (layer, closed) { 12340 if (!this._drawing) { return; } 12341 12342 var i, j, len2, p, 12343 parts = layer._parts, 12344 len = parts.length, 12345 ctx = this._ctx; 12346 12347 if (!len) { return; } 12348 12349 ctx.beginPath(); 12350 12351 for (i = 0; i < len; i++) { 12352 for (j = 0, len2 = parts[i].length; j < len2; j++) { 12353 p = parts[i][j]; 12354 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 12355 } 12356 if (closed) { 12357 ctx.closePath(); 12358 } 12359 } 12360 12361 this._fillStroke(ctx, layer); 12362 12363 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 12364 }, 12365 12366 _updateCircle: function (layer) { 12367 12368 if (!this._drawing || layer._empty()) { return; } 12369 12370 var p = layer._point, 12371 ctx = this._ctx, 12372 r = Math.max(Math.round(layer._radius), 1), 12373 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 12374 12375 if (s !== 1) { 12376 ctx.save(); 12377 ctx.scale(1, s); 12378 } 12379 12380 ctx.beginPath(); 12381 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 12382 12383 if (s !== 1) { 12384 ctx.restore(); 12385 } 12386 12387 this._fillStroke(ctx, layer); 12388 }, 12389 12390 _fillStroke: function (ctx, layer) { 12391 var options = layer.options; 12392 12393 if (options.fill) { 12394 ctx.globalAlpha = options.fillOpacity; 12395 ctx.fillStyle = options.fillColor || options.color; 12396 ctx.fill(options.fillRule || 'evenodd'); 12397 } 12398 12399 if (options.stroke && options.weight !== 0) { 12400 if (ctx.setLineDash) { 12401 ctx.setLineDash(layer.options && layer.options._dashArray || []); 12402 } 12403 ctx.globalAlpha = options.opacity; 12404 ctx.lineWidth = options.weight; 12405 ctx.strokeStyle = options.color; 12406 ctx.lineCap = options.lineCap; 12407 ctx.lineJoin = options.lineJoin; 12408 ctx.stroke(); 12409 } 12410 }, 12411 12412 // Canvas obviously doesn't have mouse events for individual drawn objects, 12413 // so we emulate that by calculating what's under the mouse on mousemove/click manually 12414 12415 _onClick: function (e) { 12416 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 12417 12418 for (var order = this._drawFirst; order; order = order.next) { 12419 layer = order.layer; 12420 if (layer.options.interactive && layer._containsPoint(point)) { 12421 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) { 12422 clickedLayer = layer; 12423 } 12424 } 12425 } 12426 this._fireEvent(clickedLayer ? [clickedLayer] : false, e); 12427 }, 12428 12429 _onMouseMove: function (e) { 12430 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 12431 12432 var point = this._map.mouseEventToLayerPoint(e); 12433 this._handleMouseHover(e, point); 12434 }, 12435 12436 12437 _handleMouseOut: function (e) { 12438 var layer = this._hoveredLayer; 12439 if (layer) { 12440 // if we're leaving the layer, fire mouseout 12441 removeClass(this._container, 'leaflet-interactive'); 12442 this._fireEvent([layer], e, 'mouseout'); 12443 this._hoveredLayer = null; 12444 this._mouseHoverThrottled = false; 12445 } 12446 }, 12447 12448 _handleMouseHover: function (e, point) { 12449 if (this._mouseHoverThrottled) { 12450 return; 12451 } 12452 12453 var layer, candidateHoveredLayer; 12454 12455 for (var order = this._drawFirst; order; order = order.next) { 12456 layer = order.layer; 12457 if (layer.options.interactive && layer._containsPoint(point)) { 12458 candidateHoveredLayer = layer; 12459 } 12460 } 12461 12462 if (candidateHoveredLayer !== this._hoveredLayer) { 12463 this._handleMouseOut(e); 12464 12465 if (candidateHoveredLayer) { 12466 addClass(this._container, 'leaflet-interactive'); // change cursor 12467 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 12468 this._hoveredLayer = candidateHoveredLayer; 12469 } 12470 } 12471 12472 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e); 12473 12474 this._mouseHoverThrottled = true; 12475 setTimeout(bind(function () { 12476 this._mouseHoverThrottled = false; 12477 }, this), 32); 12478 }, 12479 12480 _fireEvent: function (layers, e, type) { 12481 this._map._fireDOMEvent(e, type || e.type, layers); 12482 }, 12483 12484 _bringToFront: function (layer) { 12485 var order = layer._order; 12486 12487 if (!order) { return; } 12488 12489 var next = order.next; 12490 var prev = order.prev; 12491 12492 if (next) { 12493 next.prev = prev; 12494 } else { 12495 // Already last 12496 return; 12497 } 12498 if (prev) { 12499 prev.next = next; 12500 } else if (next) { 12501 // Update first entry unless this is the 12502 // single entry 12503 this._drawFirst = next; 12504 } 12505 12506 order.prev = this._drawLast; 12507 this._drawLast.next = order; 12508 12509 order.next = null; 12510 this._drawLast = order; 12511 12512 this._requestRedraw(layer); 12513 }, 12514 12515 _bringToBack: function (layer) { 12516 var order = layer._order; 12517 12518 if (!order) { return; } 12519 12520 var next = order.next; 12521 var prev = order.prev; 12522 12523 if (prev) { 12524 prev.next = next; 12525 } else { 12526 // Already first 12527 return; 12528 } 12529 if (next) { 12530 next.prev = prev; 12531 } else if (prev) { 12532 // Update last entry unless this is the 12533 // single entry 12534 this._drawLast = prev; 12535 } 12536 12537 order.prev = null; 12538 12539 order.next = this._drawFirst; 12540 this._drawFirst.prev = order; 12541 this._drawFirst = order; 12542 12543 this._requestRedraw(layer); 12544 } 12545 }); 12546 12547 // @factory L.canvas(options?: Renderer options) 12548 // Creates a Canvas renderer with the given options. 12549 function canvas(options) { 12550 return
vendor: 9,649 bytes, lines 12550-12903
12550canvas$1 ? new Canvas(options) : null; 12551 } 12552 12553 /* 12554 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 12555 */ 12556 12557 12558 var vmlCreate = (function () { 12559 try { 12560 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 12561 return function (name) { 12562 return document.createElement('<lvml:' + name + ' class="lvml">'); 12563 }; 12564 } catch (e) { 12565 // Do not return fn from catch block so `e` can be garbage collected 12566 // See https://github.com/Leaflet/Leaflet/pull/7279 12567 } 12568 return function (name) { 12569 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 12570 }; 12571 })(); 12572 12573 12574 /* 12575 * @class SVG 12576 * 12577 * 12578 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 12579 * with old versions of Internet Explorer. 12580 */ 12581 12582 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences 12583 var vmlMixin = { 12584 12585 _initContainer: function () { 12586 this._container = create$1('div', 'leaflet-vml-container'); 12587 }, 12588 12589 _update: function () { 12590 if (this._map._animatingZoom) { return; } 12591 Renderer.prototype._update.call(this); 12592 this.fire('update'); 12593 }, 12594 12595 _initPath: function (layer) { 12596 var container = layer._container = vmlCreate('shape'); 12597 12598 addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 12599 12600 container.coordsize = '1 1'; 12601 12602 layer._path = vmlCreate('path'); 12603 container.appendChild(layer._path); 12604 12605 this._updateStyle(layer); 12606 this._layers[stamp(layer)] = layer; 12607 }, 12608 12609 _addPath: function (layer) { 12610 var container = layer._container; 12611 this._container.appendChild(container); 12612 12613 if (layer.options.interactive) { 12614 layer.addInteractiveTarget(container); 12615 } 12616 }, 12617 12618 _removePath: function (layer) { 12619 var container = layer._container; 12620 remove(container); 12621 layer.removeInteractiveTarget(container); 12622 delete this._layers[stamp(layer)]; 12623 }, 12624 12625 _updateStyle: function (layer) { 12626 var stroke = layer._stroke, 12627 fill = layer._fill, 12628 options = layer.options, 12629 container = layer._container; 12630 12631 container.stroked = !!options.stroke; 12632 container.filled = !!options.fill; 12633 12634 if (options.stroke) { 12635 if (!stroke) { 12636 stroke = layer._stroke = vmlCreate('stroke'); 12637 } 12638 container.appendChild(stroke); 12639 stroke.weight = options.weight + 'px'; 12640 stroke.color = options.color; 12641 stroke.opacity = options.opacity; 12642 12643 if (options.dashArray) { 12644 stroke.dashStyle = isArray(options.dashArray) ? 12645 options.dashArray.join(' ') : 12646 options.dashArray.replace(/( *, *)/g, ' '); 12647 } else { 12648 stroke.dashStyle = ''; 12649 } 12650 stroke.endcap = options.lineCap.replace('butt', 'flat'); 12651 stroke.joinstyle = options.lineJoin; 12652 12653 } else if (stroke) { 12654 container.removeChild(stroke); 12655 layer._stroke = null; 12656 } 12657 12658 if (options.fill) { 12659 if (!fill) { 12660 fill = layer._fill = vmlCreate('fill'); 12661 } 12662 container.appendChild(fill); 12663 fill.color = options.fillColor || options.color; 12664 fill.opacity = options.fillOpacity; 12665 12666 } else if (fill) { 12667 container.removeChild(fill); 12668 layer._fill = null; 12669 } 12670 }, 12671 12672 _updateCircle: function (layer) { 12673 var p = layer._point.round(), 12674 r = Math.round(layer._radius), 12675 r2 = Math.round(layer._radiusY || r); 12676 12677 this._setPath(layer, layer._empty() ? 'M0 0' : 12678 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 12679 }, 12680 12681 _setPath: function (layer, path) { 12682 layer._path.v = path; 12683 }, 12684 12685 _bringToFront: function (layer) { 12686 toFront(layer._container); 12687 }, 12688 12689 _bringToBack: function (layer) { 12690 toBack(layer._container); 12691 } 12692 }; 12693 12694 var create = vml ? vmlCreate : svgCreate; 12695 12696 /* 12697 * @class SVG 12698 * @inherits Renderer 12699 * @aka L.SVG 12700 * 12701 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 12702 * Inherits `Renderer`. 12703 * 12704 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not 12705 * available in all web browsers, notably Android 2.x and 3.x. 12706 * 12707 * Although SVG is not available on IE7 and IE8, these browsers support 12708 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 12709 * (a now deprecated technology), and the SVG renderer will fall back to VML in 12710 * this case. 12711 * 12712 * @example 12713 * 12714 * Use SVG by default for all paths in the map: 12715 * 12716 * ```js 12717 * var map = L.map('map', { 12718 * renderer: L.svg() 12719 * }); 12720 * ``` 12721 * 12722 * Use a SVG renderer with extra padding for specific vector geometries: 12723 * 12724 * ```js 12725 * var map = L.map('map'); 12726 * var myRenderer = L.svg({ padding: 0.5 }); 12727 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12728 * var circle = L.circle( center, { renderer: myRenderer } ); 12729 * ``` 12730 */ 12731 12732 var SVG = Renderer.extend({ 12733 12734 getEvents: function () { 12735 var events = Renderer.prototype.getEvents.call(this); 12736 events.zoomstart = this._onZoomStart; 12737 return events; 12738 }, 12739 12740 _initContainer: function () { 12741 this._container = create('svg'); 12742 12743 // makes it possible to click through svg root; we'll reset it back in individual paths 12744 this._container.setAttribute('pointer-events', 'none'); 12745 12746 this._rootGroup = create('g'); 12747 this._container.appendChild(this._rootGroup); 12748 }, 12749 12750 _destroyContainer: function () { 12751 remove(this._container); 12752 off(this._container); 12753 delete this._container; 12754 delete this._rootGroup; 12755 delete this._svgSize; 12756 }, 12757 12758 _onZoomStart: function () { 12759 // Drag-then-pinch interactions might mess up the center and zoom. 12760 // In this case, the easiest way to prevent this is re-do the renderer 12761 // bounds and padding when the zooming starts. 12762 this._update(); 12763 }, 12764 12765 _update: function () { 12766 if (this._map._animatingZoom && this._bounds) { return; } 12767 12768 Renderer.prototype._update.call(this); 12769 12770 var b = this._bounds, 12771 size = b.getSize(), 12772 container = this._container; 12773 12774 // set size of svg-container if changed 12775 if (!this._svgSize || !this._svgSize.equals(size)) { 12776 this._svgSize = size; 12777 container.setAttribute('width', size.x); 12778 container.setAttribute('height', size.y); 12779 } 12780 12781 // movement: update container viewBox so that we don't have to change coordinates of individual layers 12782 setPosition(container, b.min); 12783 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 12784 12785 this.fire('update'); 12786 }, 12787 12788 // methods below are called by vector layers implementations 12789 12790 _initPath: function (layer) { 12791 var path = layer._path = create('path'); 12792 12793 // @namespace Path 12794 // @option className: String = null 12795 // Custom class name set on an element. Only for SVG renderer. 12796 if (layer.options.className) { 12797 addClass(path, layer.options.className); 12798 } 12799 12800 if (layer.options.interactive) { 12801 addClass(path, 'leaflet-interactive'); 12802 } 12803 12804 this._updateStyle(layer); 12805 this._layers[stamp(layer)] = layer; 12806 }, 12807 12808 _addPath: function (layer) { 12809 if (!this._rootGroup) { this._initContainer(); } 12810 this._rootGroup.appendChild(layer._path); 12811 layer.addInteractiveTarget(layer._path); 12812 }, 12813 12814 _removePath: function (layer) { 12815 remove(layer._path); 12816 layer.removeInteractiveTarget(layer._path); 12817 delete this._layers[stamp(layer)]; 12818 }, 12819 12820 _updatePath: function (layer) { 12821 layer._project(); 12822 layer._update(); 12823 }, 12824 12825 _updateStyle: function (layer) { 12826 var path = layer._path, 12827 options = layer.options; 12828 12829 if (!path) { return; } 12830 12831 if (options.stroke) { 12832 path.setAttribute('stroke', options.color); 12833 path.setAttribute('stroke-opacity', options.opacity); 12834 path.setAttribute('stroke-width', options.weight); 12835 path.setAttribute('stroke-linecap', options.lineCap); 12836 path.setAttribute('stroke-linejoin', options.lineJoin); 12837 12838 if (options.dashArray) { 12839 path.setAttribute('stroke-dasharray', options.dashArray); 12840 } else { 12841 path.removeAttribute('stroke-dasharray'); 12842 } 12843 12844 if (options.dashOffset) { 12845 path.setAttribute('stroke-dashoffset', options.dashOffset); 12846 } else { 12847 path.removeAttribute('stroke-dashoffset'); 12848 } 12849 } else { 12850 path.setAttribute('stroke', 'none'); 12851 } 12852 12853 if (options.fill) { 12854 path.setAttribute('fill', options.fillColor || options.color); 12855 path.setAttribute('fill-opacity', options.fillOpacity); 12856 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 12857 } else { 12858 path.setAttribute('fill', 'none'); 12859 } 12860 }, 12861 12862 _updatePoly: function (layer, closed) { 12863 this._setPath(layer, pointsToPath(layer._parts, closed)); 12864 }, 12865 12866 _updateCircle: function (layer) { 12867 var p = layer._point, 12868 r = Math.max(Math.round(layer._radius), 1), 12869 r2 = Math.max(Math.round(layer._radiusY), 1) || r, 12870 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 12871 12872 // drawing a circle with two half-arcs 12873 var d = layer._empty() ? 'M0 0' : 12874 'M' + (p.x - r) + ',' + p.y + 12875 arc + (r * 2) + ',0 ' + 12876 arc + (-r * 2) + ',0 '; 12877 12878 this._setPath(layer, d); 12879 }, 12880 12881 _setPath: function (layer, path) { 12882 layer._path.setAttribute('d', path); 12883 }, 12884 12885 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 12886 _bringToFront: function (layer) { 12887 toFront(layer._path); 12888 }, 12889 12890 _bringToBack: function (layer) { 12891 toBack(layer._path); 12892 } 12893 }); 12894 12895 if (vml) { 12896 SVG.include(vmlMixin); 12897 } 12898 12899 // @namespace SVG 12900 // @factory L.svg(options?: Renderer options) 12901 // Creates a SVG renderer with the given options. 12902 function svg(options) { 12903 return svg$1 ||
vendor: 8,853 bytes, lines 12903-13220
12903vml ? new SVG(options) : null; 12904 } 12905 12906 Map.include({ 12907 // @namespace Map; @method getRenderer(layer: Path): Renderer 12908 // Returns the instance of `Renderer` that should be used to render the given 12909 // `Path`. It will ensure that the `renderer` options of the map and paths 12910 // are respected, and that the renderers do exist on the map. 12911 getRenderer: function (layer) { 12912 // @namespace Path; @option renderer: Renderer 12913 // Use this specific instance of `Renderer` for this path. Takes 12914 // precedence over the map's [default renderer](#map-renderer). 12915 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 12916 12917 if (!renderer) { 12918 renderer = this._renderer = this._createRenderer(); 12919 } 12920 12921 if (!this.hasLayer(renderer)) { 12922 this.addLayer(renderer); 12923 } 12924 return renderer; 12925 }, 12926 12927 _getPaneRenderer: function (name) { 12928 if (name === 'overlayPane' || name === undefined) { 12929 return false; 12930 } 12931 12932 var renderer = this._paneRenderers[name]; 12933 if (renderer === undefined) { 12934 renderer = this._createRenderer({pane: name}); 12935 this._paneRenderers[name] = renderer; 12936 } 12937 return renderer; 12938 }, 12939 12940 _createRenderer: function (options) { 12941 // @namespace Map; @option preferCanvas: Boolean = false 12942 // Whether `Path`s should be rendered on a `Canvas` renderer. 12943 // By default, all `Path`s are rendered in a `SVG` renderer. 12944 return (this.options.preferCanvas && canvas(options)) || svg(options); 12945 } 12946 }); 12947 12948 /* 12949 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. 12950 */ 12951 12952 /* 12953 * @class Rectangle 12954 * @aka L.Rectangle 12955 * @inherits Polygon 12956 * 12957 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 12958 * 12959 * @example 12960 * 12961 * ```js 12962 * // define rectangle geographical bounds 12963 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 12964 * 12965 * // create an orange rectangle 12966 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 12967 * 12968 * // zoom the map to the rectangle bounds 12969 * map.fitBounds(bounds); 12970 * ``` 12971 * 12972 */ 12973 12974 12975 var Rectangle = Polygon.extend({ 12976 initialize: function (latLngBounds, options) { 12977 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 12978 }, 12979 12980 // @method setBounds(latLngBounds: LatLngBounds): this 12981 // Redraws the rectangle with the passed bounds. 12982 setBounds: function (latLngBounds) { 12983 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 12984 }, 12985 12986 _boundsToLatLngs: function (latLngBounds) { 12987 latLngBounds = toLatLngBounds(latLngBounds); 12988 return [ 12989 latLngBounds.getSouthWest(), 12990 latLngBounds.getNorthWest(), 12991 latLngBounds.getNorthEast(), 12992 latLngBounds.getSouthEast() 12993 ]; 12994 } 12995 }); 12996 12997 12998 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 12999 function rectangle(latLngBounds, options) { 13000 return new Rectangle(latLngBounds, options); 13001 } 13002 13003 SVG.create = create; 13004 SVG.pointsToPath = pointsToPath; 13005 13006 GeoJSON.geometryToLayer = geometryToLayer; 13007 GeoJSON.coordsToLatLng = coordsToLatLng; 13008 GeoJSON.coordsToLatLngs = coordsToLatLngs; 13009 GeoJSON.latLngToCoords = latLngToCoords; 13010 GeoJSON.latLngsToCoords = latLngsToCoords; 13011 GeoJSON.getFeature = getFeature; 13012 GeoJSON.asFeature = asFeature; 13013 13014 /* 13015 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 13016 * (zoom to a selected bounding box), enabled by default. 13017 */ 13018 13019 // @namespace Map 13020 // @section Interaction Options 13021 Map.mergeOptions({ 13022 // @option boxZoom: Boolean = true 13023 // Whether the map can be zoomed to a rectangular area specified by 13024 // dragging the mouse while pressing the shift key. 13025 boxZoom: true 13026 }); 13027 13028 var BoxZoom = Handler.extend({ 13029 initialize: function (map) { 13030 this._map = map; 13031 this._container = map._container; 13032 this._pane = map._panes.overlayPane; 13033 this._resetStateTimeout = 0; 13034 map.on('unload', this._destroy, this); 13035 }, 13036 13037 addHooks: function () { 13038 on(this._container, 'mousedown', this._onMouseDown, this); 13039 }, 13040 13041 removeHooks: function () { 13042 off(this._container, 'mousedown', this._onMouseDown, this); 13043 }, 13044 13045 moved: function () { 13046 return this._moved; 13047 }, 13048 13049 _destroy: function () { 13050 remove(this._pane); 13051 delete this._pane; 13052 }, 13053 13054 _resetState: function () { 13055 this._resetStateTimeout = 0; 13056 this._moved = false; 13057 }, 13058 13059 _clearDeferredResetState: function () { 13060 if (this._resetStateTimeout !== 0) { 13061 clearTimeout(this._resetStateTimeout); 13062 this._resetStateTimeout = 0; 13063 } 13064 }, 13065 13066 _onMouseDown: function (e) { 13067 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 13068 13069 // Clear the deferred resetState if it hasn't executed yet, otherwise it 13070 // will interrupt the interaction and orphan a box element in the container. 13071 this._clearDeferredResetState(); 13072 this._resetState(); 13073 13074 disableTextSelection(); 13075 disableImageDrag(); 13076 13077 this._startPoint = this._map.mouseEventToContainerPoint(e); 13078 13079 on(document, { 13080 contextmenu: stop, 13081 mousemove: this._onMouseMove, 13082 mouseup: this._onMouseUp, 13083 keydown: this._onKeyDown 13084 }, this); 13085 }, 13086 13087 _onMouseMove: function (e) { 13088 if (!this._moved) { 13089 this._moved = true; 13090 13091 this._box = create$1('div', 'leaflet-zoom-box', this._container); 13092 addClass(this._container, 'leaflet-crosshair'); 13093 13094 this._map.fire('boxzoomstart'); 13095 } 13096 13097 this._point = this._map.mouseEventToContainerPoint(e); 13098 13099 var bounds = new Bounds(this._point, this._startPoint), 13100 size = bounds.getSize(); 13101 13102 setPosition(this._box, bounds.min); 13103 13104 this._box.style.width = size.x + 'px'; 13105 this._box.style.height = size.y + 'px'; 13106 }, 13107 13108 _finish: function () { 13109 if (this._moved) { 13110 remove(this._box); 13111 removeClass(this._container, 'leaflet-crosshair'); 13112 } 13113 13114 enableTextSelection(); 13115 enableImageDrag(); 13116 13117 off(document, { 13118 contextmenu: stop, 13119 mousemove: this._onMouseMove, 13120 mouseup: this._onMouseUp, 13121 keydown: this._onKeyDown 13122 }, this); 13123 }, 13124 13125 _onMouseUp: function (e) { 13126 if ((e.which !== 1) && (e.button !== 1)) { return; } 13127 13128 this._finish(); 13129 13130 if (!this._moved) { return; } 13131 // Postpone to next JS tick so internal click event handling 13132 // still see it as "moved". 13133 this._clearDeferredResetState(); 13134 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 13135 13136 var bounds = new LatLngBounds( 13137 this._map.containerPointToLatLng(this._startPoint), 13138 this._map.containerPointToLatLng(this._point)); 13139 13140 this._map 13141 .fitBounds(bounds) 13142 .fire('boxzoomend', {boxZoomBounds: bounds}); 13143 }, 13144 13145 _onKeyDown: function (e) { 13146 if (e.keyCode === 27) { 13147 this._finish(); 13148 this._clearDeferredResetState(); 13149 this._resetState(); 13150 } 13151 } 13152 }); 13153 13154 // @section Handlers 13155 // @property boxZoom: Handler 13156 // Box (shift-drag with mouse) zoom handler. 13157 Map.addInitHook('addHandler', 'boxZoom', BoxZoom); 13158 13159 /* 13160 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 13161 */ 13162 13163 // @namespace Map 13164 // @section Interaction Options 13165 13166 Map.mergeOptions({ 13167 // @option doubleClickZoom: Boolean|String = true 13168 // Whether the map can be zoomed in by double clicking on it and 13169 // zoomed out by double clicking while holding shift. If passed 13170 // `'center'`, double-click zoom will zoom to the center of the 13171 // view regardless of where the mouse was. 13172 doubleClickZoom: true 13173 }); 13174 13175 var DoubleClickZoom = Handler.extend({ 13176 addHooks: function () { 13177 this._map.on('dblclick', this._onDoubleClick, this); 13178 }, 13179 13180 removeHooks: function () { 13181 this._map.off('dblclick', this._onDoubleClick, this); 13182 }, 13183 13184 _onDoubleClick: function (e) { 13185 var map = this._map, 13186 oldZoom = map.getZoom(), 13187 delta = map.options.zoomDelta, 13188 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 13189 13190 if (map.options.doubleClickZoom === 'center') { 13191 map.setZoom(zoom); 13192 } else { 13193 map.setZoomAround(e.containerPoint, zoom); 13194 } 13195 } 13196 }); 13197 13198 // @section Handlers 13199 // 13200 // Map properties include interaction handlers that allow you to control 13201 // interaction behavior in runtime, enabling or disabling certain features such 13202 // as dragging or touch zoom (see `Handler` methods). For example: 13203 // 13204 // ```js 13205 // map.doubleClickZoom.disable(); 13206 // ``` 13207 // 13208 // @property doubleClickZoom: Handler 13209 // Double click zoom handler. 13210 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); 13211 13212 /* 13213 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 13214 */ 13215 13216 // @namespace Map 13217 // @section Interaction Options 13218 Map.mergeOptions({ 13219 // @option dragging: Boolean = true 13220 // Whether the map
13220be draggable with mouse/touch or not. 13221 dragging: true, 13222 13223 // @section Panning Inertia Options 13224 // @option inertia: Boolean = * 13225 // If enabled, panning of the map will have an inertia effect where 13226 // the map builds momentum while dragging and continues moving in 13227 // the same direction for some time. Feels especially nice on touch 13228 // devices. Enabled by default. 13229 inertia: true, 13230 13231 // @option inertiaDeceleration: Number = 3000 13232 // The rate with which the inertial movement slows down, in pixels/second². 13233 inertiaDeceleration: 3400, // px/s^2 13234 13235 // @option inertiaMaxSpeed: Number = Infinity 13236 // Max speed of the inertial movement, in pixels/second. 13237 inertiaMaxSpeed: Infinity, // px/s 13238 13239 // @option easeLinearity: Number = 0.2 13240 easeLinearity: 0.2, 13241 13242 // TODO refactor, move to CRS 13243 // @option worldCopyJump: Boolean = false 13244 // With this option enabled, the map tracks when you pan to another "copy" 13245 // of the world and seamlessly jumps to the original one so that all overlays 13246 // like markers and vector layers are still visible.
vendor: 20,306 bytes, lines 13247-13944
13247 worldCopyJump: false, 13248 13249 // @option maxBoundsViscosity: Number = 0.0 13250 // If `maxBounds` is set, this option will control how solid the bounds 13251 // are when dragging the map around. The default value of `0.0` allows the 13252 // user to drag outside the bounds at normal speed, higher values will 13253 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 13254 // solid, preventing the user from dragging outside the bounds. 13255 maxBoundsViscosity: 0.0 13256 }); 13257 13258 var Drag = Handler.extend({ 13259 addHooks: function () { 13260 if (!this._draggable) { 13261 var map = this._map; 13262 13263 this._draggable = new Draggable(map._mapPane, map._container); 13264 13265 this._draggable.on({ 13266 dragstart: this._onDragStart, 13267 drag: this._onDrag, 13268 dragend: this._onDragEnd 13269 }, this); 13270 13271 this._draggable.on('predrag', this._onPreDragLimit, this); 13272 if (map.options.worldCopyJump) { 13273 this._draggable.on('predrag', this._onPreDragWrap, this); 13274 map.on('zoomend', this._onZoomEnd, this); 13275 13276 map.whenReady(this._onZoomEnd, this); 13277 } 13278 } 13279 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 13280 this._draggable.enable(); 13281 this._positions = []; 13282 this._times = []; 13283 }, 13284 13285 removeHooks: function () { 13286 removeClass(this._map._container, 'leaflet-grab'); 13287 removeClass(this._map._container, 'leaflet-touch-drag'); 13288 this._draggable.disable(); 13289 }, 13290 13291 moved: function () { 13292 return this._draggable && this._draggable._moved; 13293 }, 13294 13295 moving: function () { 13296 return this._draggable && this._draggable._moving; 13297 }, 13298 13299 _onDragStart: function () { 13300 var map = this._map; 13301 13302 map._stop(); 13303 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 13304 var bounds = toLatLngBounds(this._map.options.maxBounds); 13305 13306 this._offsetLimit = toBounds( 13307 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 13308 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 13309 .add(this._map.getSize())); 13310 13311 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 13312 } else { 13313 this._offsetLimit = null; 13314 } 13315 13316 map 13317 .fire('movestart') 13318 .fire('dragstart'); 13319 13320 if (map.options.inertia) { 13321 this._positions = []; 13322 this._times = []; 13323 } 13324 }, 13325 13326 _onDrag: function (e) { 13327 if (this._map.options.inertia) { 13328 var time = this._lastTime = +new Date(), 13329 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 13330 13331 this._positions.push(pos); 13332 this._times.push(time); 13333 13334 this._prunePositions(time); 13335 } 13336 13337 this._map 13338 .fire('move', e) 13339 .fire('drag', e); 13340 }, 13341 13342 _prunePositions: function (time) { 13343 while (this._positions.length > 1 && time - this._times[0] > 50) { 13344 this._positions.shift(); 13345 this._times.shift(); 13346 } 13347 }, 13348 13349 _onZoomEnd: function () { 13350 var pxCenter = this._map.getSize().divideBy(2), 13351 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 13352 13353 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 13354 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 13355 }, 13356 13357 _viscousLimit: function (value, threshold) { 13358 return value - (value - threshold) * this._viscosity; 13359 }, 13360 13361 _onPreDragLimit: function () { 13362 if (!this._viscosity || !this._offsetLimit) { return; } 13363 13364 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 13365 13366 var limit = this._offsetLimit; 13367 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 13368 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 13369 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 13370 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 13371 13372 this._draggable._newPos = this._draggable._startPos.add(offset); 13373 }, 13374 13375 _onPreDragWrap: function () { 13376 // TODO refactor to be able to adjust map pane position after zoom 13377 var worldWidth = this._worldWidth, 13378 halfWidth = Math.round(worldWidth / 2), 13379 dx = this._initialWorldOffset, 13380 x = this._draggable._newPos.x, 13381 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 13382 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 13383 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 13384 13385 this._draggable._absPos = this._draggable._newPos.clone(); 13386 this._draggable._newPos.x = newX; 13387 }, 13388 13389 _onDragEnd: function (e) { 13390 var map = this._map, 13391 options = map.options, 13392 13393 noInertia = !options.inertia || this._times.length < 2; 13394 13395 map.fire('dragend', e); 13396 13397 if (noInertia) { 13398 map.fire('moveend'); 13399 13400 } else { 13401 this._prunePositions(+new Date()); 13402 13403 var direction = this._lastPos.subtract(this._positions[0]), 13404 duration = (this._lastTime - this._times[0]) / 1000, 13405 ease = options.easeLinearity, 13406 13407 speedVector = direction.multiplyBy(ease / duration), 13408 speed = speedVector.distanceTo([0, 0]), 13409 13410 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 13411 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 13412 13413 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 13414 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 13415 13416 if (!offset.x && !offset.y) { 13417 map.fire('moveend'); 13418 13419 } else { 13420 offset = map._limitOffset(offset, map.options.maxBounds); 13421 13422 requestAnimFrame(function () { 13423 map.panBy(offset, { 13424 duration: decelerationDuration, 13425 easeLinearity: ease, 13426 noMoveStart: true, 13427 animate: true 13428 }); 13429 }); 13430 } 13431 } 13432 } 13433 }); 13434 13435 // @section Handlers 13436 // @property dragging: Handler 13437 // Map dragging handler (by both mouse and touch). 13438 Map.addInitHook('addHandler', 'dragging', Drag); 13439 13440 /* 13441 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 13442 */ 13443 13444 // @namespace Map 13445 // @section Keyboard Navigation Options 13446 Map.mergeOptions({ 13447 // @option keyboard: Boolean = true 13448 // Makes the map focusable and allows users to navigate the map with keyboard 13449 // arrows and `+`/`-` keys. 13450 keyboard: true, 13451 13452 // @option keyboardPanDelta: Number = 80 13453 // Amount of pixels to pan when pressing an arrow key. 13454 keyboardPanDelta: 80 13455 }); 13456 13457 var Keyboard = Handler.extend({ 13458 13459 keyCodes: { 13460 left: [37], 13461 right: [39], 13462 down: [40], 13463 up: [38], 13464 zoomIn: [187, 107, 61, 171], 13465 zoomOut: [189, 109, 54, 173] 13466 }, 13467 13468 initialize: function (map) { 13469 this._map = map; 13470 13471 this._setPanDelta(map.options.keyboardPanDelta); 13472 this._setZoomDelta(map.options.zoomDelta); 13473 }, 13474 13475 addHooks: function () { 13476 var container = this._map._container; 13477 13478 // make the container focusable by tabbing 13479 if (container.tabIndex <= 0) { 13480 container.tabIndex = '0'; 13481 } 13482 13483 on(container, { 13484 focus: this._onFocus, 13485 blur: this._onBlur, 13486 mousedown: this._onMouseDown 13487 }, this); 13488 13489 this._map.on({ 13490 focus: this._addHooks, 13491 blur: this._removeHooks 13492 }, this); 13493 }, 13494 13495 removeHooks: function () { 13496 this._removeHooks(); 13497 13498 off(this._map._container, { 13499 focus: this._onFocus, 13500 blur: this._onBlur, 13501 mousedown: this._onMouseDown 13502 }, this); 13503 13504 this._map.off({ 13505 focus: this._addHooks, 13506 blur: this._removeHooks 13507 }, this); 13508 }, 13509 13510 _onMouseDown: function () { 13511 if (this._focused) { return; } 13512 13513 var body = document.body, 13514 docEl = document.documentElement, 13515 top = body.scrollTop || docEl.scrollTop, 13516 left = body.scrollLeft || docEl.scrollLeft; 13517 13518 this._map._container.focus(); 13519 13520 window.scrollTo(left, top); 13521 }, 13522 13523 _onFocus: function () { 13524 this._focused = true; 13525 this._map.fire('focus'); 13526 }, 13527 13528 _onBlur: function () { 13529 this._focused = false; 13530 this._map.fire('blur'); 13531 }, 13532 13533 _setPanDelta: function (panDelta) { 13534 var keys = this._panKeys = {}, 13535 codes = this.keyCodes, 13536 i, len; 13537 13538 for (i = 0, len = codes.left.length; i < len; i++) { 13539 keys[codes.left[i]] = [-1 * panDelta, 0]; 13540 } 13541 for (i = 0, len = codes.right.length; i < len; i++) { 13542 keys[codes.right[i]] = [panDelta, 0]; 13543 } 13544 for (i = 0, len = codes.down.length; i < len; i++) { 13545 keys[codes.down[i]] = [0, panDelta]; 13546 } 13547 for (i = 0, len = codes.up.length; i < len; i++) { 13548 keys[codes.up[i]] = [0, -1 * panDelta]; 13549 } 13550 }, 13551 13552 _setZoomDelta: function (zoomDelta) { 13553 var keys = this._zoomKeys = {}, 13554 codes = this.keyCodes, 13555 i, len; 13556 13557 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 13558 keys[codes.zoomIn[i]] = zoomDelta; 13559 } 13560 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 13561 keys[codes.zoomOut[i]] = -zoomDelta; 13562 } 13563 }, 13564 13565 _addHooks: function () { 13566 on(document, 'keydown', this._onKeyDown, this); 13567 }, 13568 13569 _removeHooks: function () { 13570 off(document, 'keydown', this._onKeyDown, this); 13571 }, 13572 13573 _onKeyDown: function (e) { 13574 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 13575 13576 var key = e.keyCode, 13577 map = this._map, 13578 offset; 13579 13580 if (key in this._panKeys) { 13581 if (!map._panAnim || !map._panAnim._inProgress) { 13582 offset = this._panKeys[key]; 13583 if (e.shiftKey) { 13584 offset = toPoint(offset).multiplyBy(3); 13585 } 13586 13587 map.panBy(offset); 13588 13589 if (map.options.maxBounds) { 13590 map.panInsideBounds(map.options.maxBounds); 13591 } 13592 } 13593 } else if (key in this._zoomKeys) { 13594 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 13595 13596 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) { 13597 map.closePopup(); 13598 13599 } else { 13600 return; 13601 } 13602 13603 stop(e); 13604 } 13605 }); 13606 13607 // @section Handlers 13608 // @section Handlers 13609 // @property keyboard: Handler 13610 // Keyboard navigation handler. 13611 Map.addInitHook('addHandler', 'keyboard', Keyboard); 13612 13613 /* 13614 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 13615 */ 13616 13617 // @namespace Map 13618 // @section Interaction Options 13619 Map.mergeOptions({ 13620 // @section Mouse wheel options 13621 // @option scrollWheelZoom: Boolean|String = true 13622 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 13623 // it will zoom to the center of the view regardless of where the mouse was. 13624 scrollWheelZoom: true, 13625 13626 // @option wheelDebounceTime: Number = 40 13627 // Limits the rate at which a wheel can fire (in milliseconds). By default 13628 // user can't zoom via wheel more often than once per 40 ms. 13629 wheelDebounceTime: 40, 13630 13631 // @option wheelPxPerZoomLevel: Number = 60 13632 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 13633 // mean a change of one full zoom level. Smaller values will make wheel-zooming 13634 // faster (and vice versa). 13635 wheelPxPerZoomLevel: 60 13636 }); 13637 13638 var ScrollWheelZoom = Handler.extend({ 13639 addHooks: function () { 13640 on(this._map._container, 'wheel', this._onWheelScroll, this); 13641 13642 this._delta = 0; 13643 }, 13644 13645 removeHooks: function () { 13646 off(this._map._container, 'wheel', this._onWheelScroll, this); 13647 }, 13648 13649 _onWheelScroll: function (e) { 13650 var delta = getWheelDelta(e); 13651 13652 var debounce = this._map.options.wheelDebounceTime; 13653 13654 this._delta += delta; 13655 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 13656 13657 if (!this._startTime) { 13658 this._startTime = +new Date(); 13659 } 13660 13661 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 13662 13663 clearTimeout(this._timer); 13664 this._timer = setTimeout(bind(this._performZoom, this), left); 13665 13666 stop(e); 13667 }, 13668 13669 _performZoom: function () { 13670 var map = this._map, 13671 zoom = map.getZoom(), 13672 snap = this._map.options.zoomSnap || 0; 13673 13674 map._stop(); // stop panning and fly animations if any 13675 13676 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 13677 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 13678 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 13679 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 13680 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 13681 13682 this._delta = 0; 13683 this._startTime = null; 13684 13685 if (!delta) { return; } 13686 13687 if (map.options.scrollWheelZoom === 'center') { 13688 map.setZoom(zoom + delta); 13689 } else { 13690 map.setZoomAround(this._lastMousePos, zoom + delta); 13691 } 13692 } 13693 }); 13694 13695 // @section Handlers 13696 // @property scrollWheelZoom: Handler 13697 // Scroll wheel zoom handler. 13698 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); 13699 13700 /* 13701 * L.Map.TapHold is used to simulate `contextmenu` event on long hold, 13702 * which otherwise is not fired by mobile Safari. 13703 */ 13704 13705 var tapHoldDelay = 600; 13706 13707 // @namespace Map 13708 // @section Interaction Options 13709 Map.mergeOptions({ 13710 // @section Touch interaction options 13711 // @option tapHold: Boolean 13712 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari. 13713 tapHold: touchNative && safari && mobile, 13714 13715 // @option tapTolerance: Number = 15 13716 // The max number of pixels a user can shift his finger during touch 13717 // for it to be considered a valid tap. 13718 tapTolerance: 15 13719 }); 13720 13721 var TapHold = Handler.extend({ 13722 addHooks: function () { 13723 on(this._map._container, 'touchstart', this._onDown, this); 13724 }, 13725 13726 removeHooks: function () { 13727 off(this._map._container, 'touchstart', this._onDown, this); 13728 }, 13729 13730 _onDown: function (e) { 13731 clearTimeout(this._holdTimeout); 13732 if (e.touches.length !== 1) { return; } 13733 13734 var first = e.touches[0]; 13735 this._startPos = this._newPos = new Point(first.clientX, first.clientY); 13736 13737 this._holdTimeout = setTimeout(bind(function () { 13738 this._cancel(); 13739 if (!this._isTapValid()) { return; } 13740 13741 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events 13742 on(document, 'touchend', preventDefault); 13743 on(document, 'touchend touchcancel', this._cancelClickPrevent); 13744 this._simulateEvent('contextmenu', first); 13745 }, this), tapHoldDelay); 13746 13747 on(document, 'touchend touchcancel contextmenu', this._cancel, this); 13748 on(document, 'touchmove', this._onMove, this); 13749 }, 13750 13751 _cancelClickPrevent: function cancelClickPrevent() { 13752 off(document, 'touchend', preventDefault); 13753 off(document, 'touchend touchcancel', cancelClickPrevent); 13754 }, 13755 13756 _cancel: function () { 13757 clearTimeout(this._holdTimeout); 13758 off(document, 'touchend touchcancel contextmenu', this._cancel, this); 13759 off(document, 'touchmove', this._onMove, this); 13760 }, 13761 13762 _onMove: function (e) { 13763 var first = e.touches[0]; 13764 this._newPos = new Point(first.clientX, first.clientY); 13765 }, 13766 13767 _isTapValid: function () { 13768 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 13769 }, 13770 13771 _simulateEvent: function (type, e) { 13772 var simulatedEvent = new MouseEvent(type, { 13773 bubbles: true, 13774 cancelable: true, 13775 view: window, 13776 // detail: 1, 13777 screenX: e.screenX, 13778 screenY: e.screenY, 13779 clientX: e.clientX, 13780 clientY: e.clientY, 13781 // button: 2, 13782 // buttons: 2 13783 }); 13784 13785 simulatedEvent._simulated = true; 13786 13787 e.target.dispatchEvent(simulatedEvent); 13788 } 13789 }); 13790 13791 // @section Handlers 13792 // @property tapHold: Handler 13793 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari). 13794 Map.addInitHook('addHandler', 'tapHold', TapHold); 13795 13796 /* 13797 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. 13798 */ 13799 13800 // @namespace Map 13801 // @section Interaction Options 13802 Map.mergeOptions({ 13803 // @section Touch interaction options 13804 // @option touchZoom: Boolean|String = * 13805 // Whether the map can be zoomed by touch-dragging with two fingers. If 13806 // passed `'center'`, it will zoom to the center of the view regardless of 13807 // where the touch events (fingers) were. Enabled for touch-capable web 13808 // browsers. 13809 touchZoom: touch, 13810 13811 // @option bounceAtZoomLimits: Boolean = true 13812 // Set it to false if you don't want the map to zoom beyond min/max zoom 13813 // and then bounce back when pinch-zooming. 13814 bounceAtZoomLimits: true 13815 }); 13816 13817 var TouchZoom = Handler.extend({ 13818 addHooks: function () { 13819 addClass(this._map._container, 'leaflet-touch-zoom'); 13820 on(this._map._container, 'touchstart', this._onTouchStart, this); 13821 }, 13822 13823 removeHooks: function () { 13824 removeClass(this._map._container, 'leaflet-touch-zoom'); 13825 off(this._map._container, 'touchstart', this._onTouchStart, this); 13826 }, 13827 13828 _onTouchStart: function (e) { 13829 var map = this._map; 13830 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 13831 13832 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 13833 p2 = map.mouseEventToContainerPoint(e.touches[1]); 13834 13835 this._centerPoint = map.getSize()._divideBy(2); 13836 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 13837 if (map.options.touchZoom !== 'center') { 13838 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 13839 } 13840 13841 this._startDist = p1.distanceTo(p2); 13842 this._startZoom = map.getZoom(); 13843 13844 this._moved = false; 13845 this._zooming = true; 13846 13847 map._stop(); 13848 13849 on(document, 'touchmove', this._onTouchMove, this); 13850 on(document, 'touchend touchcancel', this._onTouchEnd, this); 13851 13852 preventDefault(e); 13853 }, 13854 13855 _onTouchMove: function (e) { 13856 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 13857 13858 var map = this._map, 13859 p1 = map.mouseEventToContainerPoint(e.touches[0]), 13860 p2 = map.mouseEventToContainerPoint(e.touches[1]), 13861 scale = p1.distanceTo(p2) / this._startDist; 13862 13863 this._zoom = map.getScaleZoom(scale, this._startZoom); 13864 13865 if (!map.options.bounceAtZoomLimits && ( 13866 (this._zoom < map.getMinZoom() && scale < 1) || 13867 (this._zoom > map.getMaxZoom() && scale > 1))) { 13868 this._zoom = map._limitZoom(this._zoom); 13869 } 13870 13871 if (map.options.touchZoom === 'center') { 13872 this._center = this._startLatLng; 13873 if (scale === 1) { return; } 13874 } else { 13875 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 13876 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 13877 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 13878 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 13879 } 13880 13881 if (!this._moved) { 13882 map._moveStart(true, false); 13883 this._moved = true; 13884 } 13885 13886 cancelAnimFrame(this._animRequest); 13887 13888 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); 13889 this._animRequest = requestAnimFrame(moveFn, this, true); 13890 13891 preventDefault(e); 13892 }, 13893 13894 _onTouchEnd: function () { 13895 if (!this._moved || !this._zooming) { 13896 this._zooming = false; 13897 return; 13898 } 13899 13900 this._zooming = false; 13901 cancelAnimFrame(this._animRequest); 13902 13903 off(document, 'touchmove', this._onTouchMove, this); 13904 off(document, 'touchend touchcancel', this._onTouchEnd, this); 13905 13906 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 13907 if (this._map.options.zoomAnimation) { 13908 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 13909 } else { 13910 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 13911 } 13912 } 13913 }); 13914 13915 // @section Handlers 13916 // @property touchZoom: Handler 13917 // Touch zoom handler. 13918 Map.addInitHook('addHandler', 'touchZoom', TouchZoom); 13919 13920 Map.BoxZoom = BoxZoom; 13921 Map.DoubleClickZoom = DoubleClickZoom; 13922 Map.Drag = Drag; 13923 Map.Keyboard = Keyboard; 13924 Map.ScrollWheelZoom = ScrollWheelZoom; 13925 Map.TapHold = TapHold; 13926 Map.TouchZoom = TouchZoom; 13927 13928 exports.Bounds = Bounds; 13929 exports.Browser = Browser; 13930 exports.CRS = CRS; 13931 exports.Canvas = Canvas; 13932 exports.Circle = Circle; 13933 exports.CircleMarker = CircleMarker; 13934 exports.Class = Class; 13935 exports.Control = Control; 13936 exports.DivIcon = DivIcon; 13937 exports.DivOverlay = DivOverlay; 13938 exports.DomEvent = DomEvent; 13939 exports.DomUtil = DomUtil; 13940 exports.Draggable = Draggable; 13941 exports.Evented = Evented; 13942 exports.FeatureGroup = FeatureGroup; 13943 exports.GeoJSON = GeoJSON; 13944 exports.GridLayer = GridLayer;
13945 exports.Handler = Handler; 13946 exports.Icon = Icon; 13947 exports.ImageOverlay = ImageOverlay; 13948 exports.LatLng = LatLng; 13949 exports.LatLngBounds = LatLngBounds; 13950 exports.Layer = Layer; 13951 exports.LayerGroup = LayerGroup; 13952 exports.LineUtil = LineUtil; 13953 exports.Map = Map; 13954 exports.Marker = Marker; 13955 exports.Mixin = Mixin; 13956 exports.Path = Path; 13957 exports.Point = Point; 13958 exports.PolyUtil = PolyUtil; 13959 exports.Polygon = Polygon; 13960 exports.Polyline = Polyline; 13961 exports.Popup = Popup; 13962 exports.PosAnimation = PosAnimation; 13963 exports.Projection = index; 13964 exports.Rectangle = Rectangle; 13965 exports.Renderer = Renderer; 13966 exports.SVG = SVG; 13967 exports.SVGOverlay = SVGOverlay; 13968 exports.TileLayer = TileLayer; 13969 exports.Tooltip = Tooltip; 13970 exports.Transformation = Transformation; 13971 exports.Util = Util; 13972 exports.VideoOverlay = VideoOverlay; 13973 exports.bind = bind; 13974 exports.bounds = toBounds; 13975 exports.canvas = canvas; 13976 exports.circle = circle; 13977 exports.circleMarker = circleMarker; 13978 exports.control = control; 13979 exports.divIcon = divIcon; 13980 exports.extend = extend; 13981 exports.featureGroup = featureGroup; 13982 exports.geoJSON = geoJSON; 13983 exports.geoJson = geoJson; 13984 exports.gridLayer = gridLayer; 13985 exports.icon = icon; 13986 exports.imageOverlay = imageOverlay; 13987 exports.latLng = toLatLng; 13988 exports.latLngBounds = toLatLngBounds; 13989 exports.layerGroup = layerGroup; 13990 exports.map = createMap; 13991 exports.marker = marker; 13992 exports.point = toPoint; 13993 exports.polygon = polygon; 13994 exports.polyline = polyline; 13995 exports.popup = popup; 13996 exports.rectangle = rectangle; 13997 exports.setOptions = setOptions; 13998 exports.stamp = stamp; 13999 exports.svg = svg; 14000 exports.svgOverlay = svgOverlay; 14001 exports.tileLayer = tileLayer; 14002 exports.tooltip = tooltip; 14003 exports.transformation = toTransformation; 14004 exports.version = version; 14005 exports.videoOverlay = videoOverlay; 14006 14007 var oldL = window.L; 14008 exports.noConflict = function() { 14009 window.L = oldL; 14010 return this; 14011 } 14012 14013 // Always export us to window global (see #2364) 14014 window.L = exports; 14015 14016})); 14017//# 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.