1/* @preserve 2 * Leaflet 1.6.0, a JS library for interactive maps. http://leafletjs.com 3 * (c) 2010-2019 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 (factory((global.L = {}))); 10}(this, (function (exports) { 'use strict'; 11 12var version = "1.6.0"; 13 14/* 15 * @namespace Util 16 * 17 * Various utility functions, used by Leaflet internally. 18 */ 19 20var freeze = Object.freeze; 21Object.freeze = function (obj) { return obj; }; 22 23// @function extend(dest: Object, src?: Object): Object 24// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 25function extend(dest) { 26 var i, j, len, src; 27 28 for (j = 1, len = arguments.length; j < len; j++) { 29 src = arguments[j]; 30 for (i in src) { 31 dest[i] = src[i]; 32 } 33 } 34 return dest; 35} 36 37// @function create(proto: Object, properties?: Object): Object 38// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 39var create = Object.create || (function () { 40 function F() {} 41 return function (proto) { 42 F.prototype = proto; 43 return new F(); 44 }; 45})(); 46 47// @function bind(fn: Function, â¦): Function 48// 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). 49// Has a `L.bind()` shortcut. 50function bind(fn, obj) { 51 var slice = Array.prototype.slice; 52 53 if (fn.bind) { 54 return fn.bind.apply(fn, slice.call(arguments, 1)); 55 } 56 57 var args = slice.call(arguments, 2); 58 59 return function () { 60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 61 }; 62} 63 64// @property lastId: Number 65// Last unique ID used by [`stamp()`](#util-stamp) 66var lastId = 0; 67 68// @function stamp(obj: Object): Number 69// Returns the unique ID of an object, assigning it one if it doesn't have it. 70function stamp(obj) { 71 /*eslint-disable */ 72 obj._leaflet_id = obj._leaflet_id || ++lastId; 73 return obj._leaflet_id; 74 /* eslint-enable */ 75} 76
vendor: 5,772 bytes, lines 77-233
77// @function throttle(fn: Function, time: Number, context: Object): Function 78// Returns a function which executes function `fn` with the given scope `context` 79// (so that the `this` keyword refers to `context` inside `fn`'s code). The function 80// `fn` will be called no more than one time per given amount of `time`. The arguments 81// received by the bound function will be any arguments passed when binding the 82// function, followed by any arguments passed when invoking the bound function. 83// Has an `L.throttle` shortcut. 84function throttle(fn, time, context) { 85 var lock, args, wrapperFn, later; 86 87 later = function () { 88 // reset lock and call if queued 89 lock = false; 90 if (args) { 91 wrapperFn.apply(context, args); 92 args = false; 93 } 94 }; 95 96 wrapperFn = function () { 97 if (lock) { 98 // called too soon, queue to call later 99 args = arguments; 100 101 } else { 102 // call and lock until later 103 fn.apply(context, arguments); 104 setTimeout(later, time); 105 lock = true; 106 } 107 }; 108 109 return wrapperFn; 110} 111 112// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number 113// Returns the number `num` modulo `range` in such a way so it lies within 114// `range[0]` and `range[1]`. The returned value will be always smaller than 115// `range[1]` unless `includeMax` is set to `true`. 116function wrapNum(x, range, includeMax) { 117 var max = range[1], 118 min = range[0], 119 d = max - min; 120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 121} 122 123// @function falseFn(): Function 124// Returns a function which always returns `false`. 125function falseFn() { return false; } 126 127// @function formatNum(num: Number, digits?: Number): Number 128// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default. 129function formatNum(num, digits) { 130 var pow = Math.pow(10, (digits === undefined ? 6 : digits)); 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) 136function 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. 142function 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. 148function setOptions(obj, options) { 149 if (!obj.hasOwnProperty('options')) { 150 obj.options = obj.options ? create(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'`) 163function 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 171var 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. 178function 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) 194var 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) 200function 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). 211var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; 212 213// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 214 215function getPrefixed(name) { 216 return window['webkit' + name] || window['moz' + name] || window['ms' + name]; 217} 218 219var lastTime = 0; 220 221// fallback for IE 7-8 222function 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 230var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; 231var 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. 240function 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). 250function cancelAnimFrame(id) { 251 if (id) { 252 cancelFn.call(window, id); 253 } 254} 255 256 257var Util = (Object.freeze || Object)({ 258 freeze: freeze, 259 extend: extend, 260 create: create, 261 bind: bind, 262 lastId: lastId, 263 stamp: stamp, 264 throttle: throttle, 265 wrapNum: wrapNum, 266 falseFn: falseFn, 267 formatNum: formatNum, 268 trim: trim, 269 splitWords: splitWords, 270 setOptions: setOptions, 271 getParamString: getParamString, 272 template: template, 273 isArray: isArray, 274 indexOf: indexOf, 275 emptyImageUrl: emptyImageUrl, 276 requestFn: requestFn, 277 cancelFn: cancelFn, 278 requestAnimFrame: requestAnimFrame, 279 cancelAnimFrame: cancelAnimFrame 280}); 281 282// @class Class 283// @aka L.Class 284 285// @section 286// @uninheritable 287 288// Thanks to John Resig and Dean Edwards for inspiration! 289 290function Class() {} 291 292Class.extend = function (props) { 293 294 // @function extend(props: Object): Function 295 // [Extends the current class](#class-inheritance) given the properties to be included. 296 // Returns a Javascript function that is a class constructor (to be called with `new`). 297 var NewClass = function () { 298 299 // call the constructor 300 if (this.initialize) { 301 this.initialize.apply(this, arguments); 302 } 303 304 // call all constructor hooks 305 this.callInitHooks(); 306 }; 307 308 var parentProto = NewClass.__super__ = this.prototype; 309 310 var proto = create(parentProto); 311 proto.constructor = NewClass; 312 313 NewClass.prototype = proto; 314 315 // inherit parent's statics 316 for (var i in this) { 317 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') { 318 NewClass[i] = this[i]; 319 } 320 } 321 322 // mix static properties into the class 323 if (props.statics) { 324 extend(NewClass, props.statics); 325 delete props.statics; 326 } 327 328 // mix includes into the prototype 329 if (props.includes) { 330 checkDeprecatedMixinEvents(props.includes); 331 extend.apply(null, [proto].concat(props.includes)); 332 delete props.includes; 333 } 334 335 // merge options 336 if (proto.options) { 337 props.options = extend(create(proto.options), props.options); 338 } 339 340 // mix given properties into the prototype 341 extend(proto, props); 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. 367Class.include = function (props) { 368 extend(this.prototype, props); 369 return this; 370}; 371 372// @function mergeOptions(options: Object): this 373// [Merges `options`](#class-options) into the defaults of the class. 374Class.mergeOptions = function (options) { 375 extend(this.prototype.options, options); 376 return this; 377}; 378 379// @function addInitHook(fn: Function): this 380// Adds a [constructor hook](#class-constructor-hooks) to the class. 381Class.addInitHook = function (fn) { // (Function) || (String, args...) 382 var args = Array.prototype.slice.call(arguments, 1); 383 384 var init = typeof fn === 'function' ? fn : function () { 385 this[fn].apply(this, args); 386 }; 387 388 this.prototype._initHooks = this.prototype._initHooks || []; 389 this.prototype._initHooks.push(init); 390 return this; 391}; 392 393function checkDeprecatedMixinEvents(includes) { 394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; } 395 396 includes = isArray(includes) ? includes : [includes]; 397 398 for (var i = 0; i < includes.length; i++) { 399 if (includes[i] === L.Mixin.Events) {
400 console.warn('Deprecated include of L.Mixin.Events: ' + 401 'this property will be removed in future releases, ' + 402 'please inherit from L.Evented instead.', new Error().stack); 403 } 404 } 405} 406 407/* 408 * @class Evented 409 * @aka L.Evented 410 * @inherits Class 411 * 412 * 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). 413 * 414 * @example 415 * 416 * ```js 417 * map.on('click', function(e) { 418 * alert(e.latlng); 419 * } ); 420 * ``` 421 * 422 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 423 * 424 * ```js 425 * function onClick(e) { ... } 426 * 427 * map.on('click', onClick); 428 * map.off('click', onClick); 429 * ``` 430 */ 431 432var Events = { 433 /* @method on(type: String, fn: Function, context?: Object): this 434 * 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'`). 435 * 436 * @alternative 437 * @method on(eventMap: Object): this 438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 439 */ 440 on: function (types, fn, context) { 441 442 // types can be a map of types/handlers 443 if (typeof types === 'object') { 444 for (var type in types) { 445 // we don't process space-separated events here for performance; 446 // it's a hot path since Layer uses the on(obj) syntax 447 this._on(type, types[type], fn); 448 } 449 450 } else { 451 // types can be a string of space-separated words 452 types = splitWords(types); 453 454 for (var i = 0, len = types.length; i < len; i++) { 455 this._on(types[i], fn, context); 456 } 457 } 458 459 return this; 460 }, 461 462 /* @method off(type: String, fn?: Function, context?: Object): this 463 * 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. 464 * 465 * @alternative 466 * @method off(eventMap: Object): this 467 * Removes a set of type/listener pairs. 468 * 469 * @alternative 470 * @method off: this 471 * Removes all listeners to all events on the object. This includes implicitly attached events. 472 */ 473 off: function (types, fn, context) { 474 475 if (!types) { 476 // clear all listeners if called without arguments 477 delete this._events; 478 479 } else if (typeof types === 'object') { 480 for (var type in types) { 481 this._off(type, types[type], fn); 482 } 483 484 } else { 485 types = splitWords(types); 486 487 for (var i = 0, len = types.length; i < len; i++) { 488 this._off(types[i], fn, context); 489 } 490 } 491 492 return this; 493 }, 494 495 // attach listener (without syntactic sugar now) 496 _on: function (type, fn, context) { 497 this._events = this._events || {}; 498 499 /* get/init listeners for type */ 500 var typeListeners = this._events[type]; 501 if (!typeListeners) { 502 typeListeners = []; 503 this._events[type] = typeListeners; 504 } 505 506 if (context === this) { 507 // Less memory footprint. 508 context = undefined; 509 } 510 var newListener = {fn: fn, ctx: context}, 511 listeners = typeListeners; 512 513 // check if fn already there 514 for (var i = 0, len = listeners.length; i < len; i++) { 515 if (listeners[i].fn === fn && listeners[i].ctx === context) { 516 return; 517 } 518 } 519 520 listeners.push(newListener); 521 }, 522 523 _off: function (type, fn, context) { 524 var listeners, 525 i, 526 len; 527 528 if (!this._events) { return; } 529 530 listeners = this._events[type]; 531 532 if (!listeners) { 533 return; 534 } 535 536 if (!fn) { 537 // Set all removed listeners to noop so they are not called if remove happens in fire 538 for (i = 0, len = listeners.length; i < len; i++) { 539 listeners[i].fn = falseFn; 540 } 541 // clear all listeners for a type if function isn't specified 542 delete this._events[type]; 543 return; 544 } 545 546 if (context === this) { 547 context = undefined; 548 } 549 550 if (listeners) { 551 552 // find fn and remove it 553 for (i = 0, len = listeners.length; i < len; i++) { 554 var l = listeners[i]; 555 if (l.ctx !== context) { continue; } 556 if (l.fn === fn) { 557 558 // set the removed listener to noop so that's not called if remove happens in fire 559 l.fn = falseFn; 560 561 if (this._firingCount) { 562 /* copy array in case events are being fired */ 563 this._events[type] = listeners = listeners.slice(); 564 } 565 listeners.splice(i, 1); 566 567 return; 568 } 569 } 570 } 571 }, 572 573 // @method fire(type: String, data?: Object, propagate?: Boolean): this 574 // Fires an event of the specified type. You can optionally provide an data 575 // object â the first argument of the listener function will contain its 576 // properties. The event can optionally be propagated to event parents. 577 fire: function (type, data, propagate) {
vendor: 16,671 bytes, lines 578-1195
578 if (!this.listens(type, propagate)) { return this; } 579 580 var event = extend({}, data, { 581 type: type, 582 target: this, 583 sourceTarget: data && data.sourceTarget || this 584 }); 585 586 if (this._events) { 587 var listeners = this._events[type]; 588 589 if (listeners) { 590 this._firingCount = (this._firingCount + 1) || 1; 591 for (var i = 0, len = listeners.length; i < len; i++) { 592 var l = listeners[i]; 593 l.fn.call(l.ctx || this, event); 594 } 595 596 this._firingCount--; 597 } 598 } 599 600 if (propagate) { 601 // propagate the event to parents (set with addEventParent) 602 this._propagateEvent(event); 603 } 604 605 return this; 606 }, 607 608 // @method listens(type: String): Boolean 609 // Returns `true` if a particular event type has any listeners attached to it. 610 listens: function (type, propagate) { 611 var listeners = this._events && this._events[type]; 612 if (listeners && listeners.length) { return true; } 613 614 if (propagate) { 615 // also check parents for listeners if event propagates 616 for (var id in this._eventParents) { 617 if (this._eventParents[id].listens(type, propagate)) { return true; } 618 } 619 } 620 return false; 621 }, 622 623 // @method once(â¦): this 624 // Behaves as [`on(â¦)`](#evented-on), except the listener will only get fired once and then removed. 625 once: function (types, fn, context) { 626 627 if (typeof types === 'object') { 628 for (var type in types) { 629 this.once(type, types[type], fn); 630 } 631 return this; 632 } 633 634 var handler = bind(function () { 635 this 636 .off(types, fn, context) 637 .off(types, handler, context); 638 }, this); 639 640 // add a listener that's executed once and removed after that 641 return this 642 .on(types, fn, context) 643 .on(types, handler, context); 644 }, 645 646 // @method addEventParent(obj: Evented): this 647 // Adds an event parent - an `Evented` that will receive propagated events 648 addEventParent: function (obj) { 649 this._eventParents = this._eventParents || {}; 650 this._eventParents[stamp(obj)] = obj; 651 return this; 652 }, 653 654 // @method removeEventParent(obj: Evented): this 655 // Removes an event parent, so it will stop receiving propagated events 656 removeEventParent: function (obj) { 657 if (this._eventParents) { 658 delete this._eventParents[stamp(obj)]; 659 } 660 return this; 661 }, 662 663 _propagateEvent: function (e) { 664 for (var id in this._eventParents) { 665 this._eventParents[id].fire(e.type, extend({ 666 layer: e.target, 667 propagatedFrom: e.target 668 }, e), true); 669 } 670 } 671}; 672 673// aliases; we should ditch those eventually 674 675// @method addEventListener(â¦): this 676// Alias to [`on(â¦)`](#evented-on) 677Events.addEventListener = Events.on; 678 679// @method removeEventListener(â¦): this 680// Alias to [`off(â¦)`](#evented-off) 681 682// @method clearAllEventListeners(â¦): this 683// Alias to [`off()`](#evented-off) 684Events.removeEventListener = Events.clearAllEventListeners = Events.off; 685 686// @method addOneTimeEventListener(â¦): this 687// Alias to [`once(â¦)`](#evented-once) 688Events.addOneTimeEventListener = Events.once; 689 690// @method fireEvent(â¦): this 691// Alias to [`fire(â¦)`](#evented-fire) 692Events.fireEvent = Events.fire; 693 694// @method hasEventListeners(â¦): Boolean 695// Alias to [`listens(â¦)`](#evented-listens) 696Events.hasEventListeners = Events.listens; 697 698var Evented = Class.extend(Events); 699 700/* 701 * @class Point 702 * @aka L.Point 703 * 704 * Represents a point with `x` and `y` coordinates in pixels. 705 * 706 * @example 707 * 708 * ```js 709 * var point = L.point(200, 300); 710 * ``` 711 * 712 * 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: 713 * 714 * ```js 715 * map.panBy([200, 300]); 716 * map.panBy(L.point(200, 300)); 717 * ``` 718 * 719 * Note that `Point` does not inherit from Leafet's `Class` object, 720 * which means new classes can't inherit from it, and new methods 721 * can't be added to it with the `include` function. 722 */ 723 724function Point(x, y, round) { 725 // @property x: Number; The `x` coordinate of the point 726 this.x = (round ? Math.round(x) : x); 727 // @property y: Number; The `y` coordinate of the point 728 this.y = (round ? Math.round(y) : y); 729} 730 731var trunc = Math.trunc || function (v) { 732 return v > 0 ? Math.floor(v) : Math.ceil(v); 733}; 734 735Point.prototype = { 736 737 // @method clone(): Point 738 // Returns a copy of the current point. 739 clone: function () { 740 return new Point(this.x, this.y); 741 }, 742 743 // @method add(otherPoint: Point): Point 744 // Returns the result of addition of the current and the given points. 745 add: function (point) { 746 // non-destructive, returns a new point 747 return this.clone()._add(toPoint(point)); 748 }, 749 750 _add: function (point) { 751 // destructive, used directly for performance in situations where it's safe to modify existing point 752 this.x += point.x; 753 this.y += point.y; 754 return this; 755 }, 756 757 // @method subtract(otherPoint: Point): Point 758 // Returns the result of subtraction of the given point from the current. 759 subtract: function (point) { 760 return this.clone()._subtract(toPoint(point)); 761 }, 762 763 _subtract: function (point) { 764 this.x -= point.x; 765 this.y -= point.y; 766 return this; 767 }, 768 769 // @method divideBy(num: Number): Point 770 // Returns the result of division of the current point by the given number. 771 divideBy: function (num) { 772 return this.clone()._divideBy(num); 773 }, 774 775 _divideBy: function (num) { 776 this.x /= num; 777 this.y /= num; 778 return this; 779 }, 780 781 // @method multiplyBy(num: Number): Point 782 // Returns the result of multiplication of the current point by the given number. 783 multiplyBy: function (num) { 784 return this.clone()._multiplyBy(num); 785 }, 786 787 _multiplyBy: function (num) { 788 this.x *= num; 789 this.y *= num; 790 return this; 791 }, 792 793 // @method scaleBy(scale: Point): Point 794 // Multiply each coordinate of the current point by each coordinate of 795 // `scale`. In linear algebra terms, multiply the point by the 796 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 797 // defined by `scale`. 798 scaleBy: function (point) { 799 return new Point(this.x * point.x, this.y * point.y); 800 }, 801 802 // @method unscaleBy(scale: Point): Point 803 // Inverse of `scaleBy`. Divide each coordinate of the current point by 804 // each coordinate of `scale`. 805 unscaleBy: function (point) { 806 return new Point(this.x / point.x, this.y / point.y); 807 }, 808 809 // @method round(): Point 810 // Returns a copy of the current point with rounded coordinates. 811 round: function () { 812 return this.clone()._round(); 813 }, 814 815 _round: function () { 816 this.x = Math.round(this.x); 817 this.y = Math.round(this.y); 818 return this; 819 }, 820 821 // @method floor(): Point 822 // Returns a copy of the current point with floored coordinates (rounded down). 823 floor: function () { 824 return this.clone()._floor(); 825 }, 826 827 _floor: function () { 828 this.x = Math.floor(this.x); 829 this.y = Math.floor(this.y); 830 return this; 831 }, 832 833 // @method ceil(): Point 834 // Returns a copy of the current point with ceiled coordinates (rounded up). 835 ceil: function () { 836 return this.clone()._ceil(); 837 }, 838 839 _ceil: function () { 840 this.x = Math.ceil(this.x); 841 this.y = Math.ceil(this.y); 842 return this; 843 }, 844 845 // @method trunc(): Point 846 // Returns a copy of the current point with truncated coordinates (rounded towards zero). 847 trunc: function () { 848 return this.clone()._trunc(); 849 }, 850 851 _trunc: function () { 852 this.x = trunc(this.x); 853 this.y = trunc(this.y); 854 return this; 855 }, 856 857 // @method distanceTo(otherPoint: Point): Number 858 // Returns the cartesian distance between the current and the given points. 859 distanceTo: function (point) { 860 point = toPoint(point); 861 862 var x = point.x - this.x, 863 y = point.y - this.y; 864 865 return Math.sqrt(x * x + y * y); 866 }, 867 868 // @method equals(otherPoint: Point): Boolean 869 // Returns `true` if the given point has the same coordinates. 870 equals: function (point) { 871 point = toPoint(point); 872 873 return point.x === this.x && 874 point.y === this.y; 875 }, 876 877 // @method contains(otherPoint: Point): Boolean 878 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 879 contains: function (point) { 880 point = toPoint(point); 881 882 return Math.abs(point.x) <= Math.abs(this.x) && 883 Math.abs(point.y) <= Math.abs(this.y); 884 }, 885 886 // @method toString(): String 887 // Returns a string representation of the point for debugging purposes. 888 toString: function () { 889 return 'Point(' + 890 formatNum(this.x) + ', ' + 891 formatNum(this.y) + ')'; 892 } 893}; 894 895// @factory L.point(x: Number, y: Number, round?: Boolean) 896// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 897 898// @alternative 899// @factory L.point(coords: Number[]) 900// Expects an array of the form `[x, y]` instead. 901 902// @alternative 903// @factory L.point(coords: Object) 904// Expects a plain object of the form `{x: Number, y: Number}` instead. 905function toPoint(x, y, round) { 906 if (x instanceof Point) { 907 return x; 908 } 909 if (isArray(x)) { 910 return new Point(x[0], x[1]); 911 } 912 if (x === undefined || x === null) { 913 return x; 914 } 915 if (typeof x === 'object' && 'x' in x && 'y' in x) { 916 return new Point(x.x, x.y); 917 } 918 return new Point(x, y, round); 919} 920 921/* 922 * @class Bounds 923 * @aka L.Bounds 924 * 925 * Represents a rectangular area in pixel coordinates. 926 * 927 * @example 928 * 929 * ```js 930 * var p1 = L.point(10, 10), 931 * p2 = L.point(40, 60), 932 * bounds = L.bounds(p1, p2); 933 * ``` 934 * 935 * 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: 936 * 937 * ```js 938 * otherBounds.intersects([[10, 10], [40, 60]]); 939 * ``` 940 * 941 * Note that `Bounds` does not inherit from Leafet's `Class` object, 942 * which means new classes can't inherit from it, and new methods 943 * can't be added to it with the `include` function. 944 */ 945 946function Bounds(a, b) { 947 if (!a) { return; } 948 949 var points = b ? [a, b] : a; 950 951 for (var i = 0, len = points.length; i < len; i++) { 952 this.extend(points[i]); 953 } 954} 955 956Bounds.prototype = { 957 // @method extend(point: Point): this 958 // Extends the bounds to contain the given point. 959 extend: function (point) { // (Point) 960 point = toPoint(point); 961 962 // @property min: Point 963 // The top left corner of the rectangle. 964 // @property max: Point 965 // The bottom right corner of the rectangle. 966 if (!this.min && !this.max) { 967 this.min = point.clone(); 968 this.max = point.clone(); 969 } else { 970 this.min.x = Math.min(point.x, this.min.x); 971 this.max.x = Math.max(point.x, this.max.x); 972 this.min.y = Math.min(point.y, this.min.y); 973 this.max.y = Math.max(point.y, this.max.y); 974 } 975 return this; 976 }, 977 978 // @method getCenter(round?: Boolean): Point 979 // Returns the center point of the bounds. 980 getCenter: function (round) { 981 return new Point( 982 (this.min.x + this.max.x) / 2, 983 (this.min.y + this.max.y) / 2, round); 984 }, 985 986 // @method getBottomLeft(): Point 987 // Returns the bottom-left point of the bounds. 988 getBottomLeft: function () { 989 return new Point(this.min.x, this.max.y); 990 }, 991 992 // @method getTopRight(): Point 993 // Returns the top-right point of the bounds. 994 getTopRight: function () { // -> Point 995 return new Point(this.max.x, this.min.y); 996 }, 997 998 // @method getTopLeft(): Point 999 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 1000 getTopLeft: function () { 1001 return this.min; // left, top 1002 }, 1003 1004 // @method getBottomRight(): Point 1005 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 1006 getBottomRight: function () { 1007 return this.max; // right, bottom 1008 }, 1009 1010 // @method getSize(): Point 1011 // Returns the size of the given bounds 1012 getSize: function () { 1013 return this.max.subtract(this.min); 1014 }, 1015 1016 // @method contains(otherBounds: Bounds): Boolean 1017 // Returns `true` if the rectangle contains the given one. 1018 // @alternative 1019 // @method contains(point: Point): Boolean 1020 // Returns `true` if the rectangle contains the given point. 1021 contains: function (obj) { 1022 var min, max; 1023 1024 if (typeof obj[0] === 'number' || obj instanceof Point) { 1025 obj = toPoint(obj); 1026 } else { 1027 obj = toBounds(obj); 1028 } 1029 1030 if (obj instanceof Bounds) { 1031 min = obj.min; 1032 max = obj.max; 1033 } else { 1034 min = max = obj; 1035 } 1036 1037 return (min.x >= this.min.x) && 1038 (max.x <= this.max.x) && 1039 (min.y >= this.min.y) && 1040 (max.y <= this.max.y); 1041 }, 1042 1043 // @method intersects(otherBounds: Bounds): Boolean 1044 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1045 // intersect if they have at least one point in common. 1046 intersects: function (bounds) { // (Bounds) -> Boolean 1047 bounds = toBounds(bounds); 1048 1049 var min = this.min, 1050 max = this.max, 1051 min2 = bounds.min, 1052 max2 = bounds.max, 1053 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1054 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1055 1056 return xIntersects && yIntersects; 1057 }, 1058 1059 // @method overlaps(otherBounds: Bounds): Boolean 1060 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1061 // overlap if their intersection is an area. 1062 overlaps: function (bounds) { // (Bounds) -> Boolean 1063 bounds = toBounds(bounds); 1064 1065 var min = this.min, 1066 max = this.max, 1067 min2 = bounds.min, 1068 max2 = bounds.max, 1069 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1070 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1071 1072 return xOverlaps && yOverlaps; 1073 }, 1074 1075 isValid: function () { 1076 return !!(this.min && this.max); 1077 } 1078}; 1079 1080 1081// @factory L.bounds(corner1: Point, corner2: Point) 1082// Creates a Bounds object from two corners coordinate pairs. 1083// @alternative 1084// @factory L.bounds(points: Point[]) 1085// Creates a Bounds object from the given array of points. 1086function toBounds(a, b) { 1087 if (!a || a instanceof Bounds) { 1088 return a; 1089 } 1090 return new Bounds(a, b); 1091} 1092 1093/* 1094 * @class LatLngBounds 1095 * @aka L.LatLngBounds 1096 * 1097 * Represents a rectangular geographical area on a map. 1098 * 1099 * @example 1100 * 1101 * ```js 1102 * var corner1 = L.latLng(40.712, -74.227), 1103 * corner2 = L.latLng(40.774, -74.125), 1104 * bounds = L.latLngBounds(corner1, corner2); 1105 * ``` 1106 * 1107 * 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: 1108 * 1109 * ```js 1110 * map.fitBounds([ 1111 * [40.712, -74.227], 1112 * [40.774, -74.125] 1113 * ]); 1114 * ``` 1115 * 1116 * 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. 1117 * 1118 * Note that `LatLngBounds` does not inherit from Leafet's `Class` object, 1119 * which means new classes can't inherit from it, and new methods 1120 * can't be added to it with the `include` function. 1121 */ 1122 1123function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1124 if (!corner1) { return; } 1125 1126 var latlngs = corner2 ? [corner1, corner2] : corner1; 1127 1128 for (var i = 0, len = latlngs.length; i < len; i++) { 1129 this.extend(latlngs[i]); 1130 } 1131} 1132 1133LatLngBounds.prototype = { 1134 1135 // @method extend(latlng: LatLng): this 1136 // Extend the bounds to contain the given point 1137 1138 // @alternative 1139 // @method extend(otherBounds: LatLngBounds): this 1140 // Extend the bounds to contain the given bounds 1141 extend: function (obj) { 1142 var sw = this._southWest, 1143 ne = this._northEast, 1144 sw2, ne2; 1145 1146 if (obj instanceof LatLng) { 1147 sw2 = obj; 1148 ne2 = obj; 1149 1150 } else if (obj instanceof LatLngBounds) { 1151 sw2 = obj._southWest; 1152 ne2 = obj._northEast; 1153 1154 if (!sw2 || !ne2) { return this; } 1155 1156 } else { 1157 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 1158 } 1159 1160 if (!sw && !ne) { 1161 this._southWest = new LatLng(sw2.lat, sw2.lng); 1162 this._northEast = new LatLng(ne2.lat, ne2.lng); 1163 } else { 1164 sw.lat = Math.min(sw2.lat, sw.lat); 1165 sw.lng = Math.min(sw2.lng, sw.lng); 1166 ne.lat = Math.max(ne2.lat, ne.lat); 1167 ne.lng = Math.max(ne2.lng, ne.lng); 1168 } 1169 1170 return this; 1171 }, 1172 1173 // @method pad(bufferRatio: Number): LatLngBounds 1174 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1175 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1176 // Negative values will retract the bounds. 1177 pad: function (bufferRatio) { 1178 var sw = this._southWest, 1179 ne = this._northEast, 1180 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1181 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1182 1183 return new LatLngBounds( 1184 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1185 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1186 }, 1187 1188 // @method getCenter(): LatLng 1189 // Returns the center point of the bounds. 1190 getCenter: function () { 1191 return new LatLng( 1192 (this._southWest.lat + this._northEast.lat) / 2, 1193 (this._southWest.lng + this._northEast.lng) / 2); 1194 }, 1195
1196 // @method getSouthWest(): LatLng 1197 // Returns the south-west point of the bounds. 1198 getSouthWest: function () { 1199 return this._southWest; 1200 }, 1201 1202 // @method getNorthEast(): LatLng 1203 // Returns the north-east point of the bounds. 1204 getNorthEast: function () { 1205 return this._northEast; 1206 }, 1207 1208 // @method getNorthWest(): LatLng 1209 // Returns the north-west point of the bounds. 1210 getNorthWest: function () { 1211 return new LatLng(this.getNorth(), this.getWest()); 1212 }, 1213 1214 // @method getSouthEast(): LatLng 1215 // Returns the south-east point of the bounds. 1216 getSouthEast: function () { 1217 return new LatLng(this.getSouth(), this.getEast()); 1218 }, 1219 1220 // @method getWest(): Number 1221 // Returns the west longitude of the bounds 1222 getWest: function () { 1223 return this._southWest.lng; 1224 }, 1225 1226 // @method getSouth(): Number 1227 // Returns the south latitude of the bounds 1228 getSouth: function () { 1229 return this._southWest.lat; 1230 }, 1231 1232 // @method getEast(): Number 1233 // Returns the east longitude of the bounds 1234 getEast: function () { 1235 return this._northEast.lng; 1236 }, 1237 1238 // @method getNorth(): Number 1239 // Returns the north latitude of the bounds 1240 getNorth: function () { 1241 return this._northEast.lat; 1242 }, 1243 1244 // @method contains(otherBounds: LatLngBounds): Boolean 1245 // Returns `true` if the rectangle contains the given one. 1246 1247 // @alternative 1248 // @method contains (latlng: LatLng): Boolean 1249 // Returns `true` if the rectangle contains the given point. 1250 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1251 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { 1252 obj = toLatLng(obj); 1253 } else { 1254 obj = toLatLngBounds(obj); 1255 } 1256 1257 var sw = this._southWest, 1258 ne = this._northEast, 1259 sw2, ne2; 1260 1261 if (obj instanceof LatLngBounds) {
vendor: 7,059 bytes, lines 1262-1481
1262 sw2 = obj.getSouthWest(); 1263 ne2 = obj.getNorthEast(); 1264 } else { 1265 sw2 = ne2 = obj; 1266 } 1267 1268 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1269 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1270 }, 1271 1272 // @method intersects(otherBounds: LatLngBounds): Boolean 1273 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1274 intersects: function (bounds) { 1275 bounds = toLatLngBounds(bounds); 1276 1277 var sw = this._southWest, 1278 ne = this._northEast, 1279 sw2 = bounds.getSouthWest(), 1280 ne2 = bounds.getNorthEast(), 1281 1282 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1283 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1284 1285 return latIntersects && lngIntersects; 1286 }, 1287 1288 // @method overlaps(otherBounds: Bounds): Boolean 1289 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1290 overlaps: function (bounds) { 1291 bounds = toLatLngBounds(bounds); 1292 1293 var sw = this._southWest, 1294 ne = this._northEast, 1295 sw2 = bounds.getSouthWest(), 1296 ne2 = bounds.getNorthEast(), 1297 1298 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1299 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1300 1301 return latOverlaps && lngOverlaps; 1302 }, 1303 1304 // @method toBBoxString(): String 1305 // 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. 1306 toBBoxString: function () { 1307 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1308 }, 1309 1310 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 1311 // 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. 1312 equals: function (bounds, maxMargin) { 1313 if (!bounds) { return false; } 1314 1315 bounds = toLatLngBounds(bounds); 1316 1317 return this._southWest.equals(bounds.getSouthWest(), maxMargin) && 1318 this._northEast.equals(bounds.getNorthEast(), maxMargin); 1319 }, 1320 1321 // @method isValid(): Boolean 1322 // Returns `true` if the bounds are properly initialized. 1323 isValid: function () { 1324 return !!(this._southWest && this._northEast); 1325 } 1326}; 1327 1328// TODO International date line? 1329 1330// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1331// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1332 1333// @alternative 1334// @factory L.latLngBounds(latlngs: LatLng[]) 1335// 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). 1336function toLatLngBounds(a, b) { 1337 if (a instanceof LatLngBounds) { 1338 return a; 1339 } 1340 return new LatLngBounds(a, b); 1341} 1342 1343/* @class LatLng 1344 * @aka L.LatLng 1345 * 1346 * Represents a geographical point with a certain latitude and longitude. 1347 * 1348 * @example 1349 * 1350 * ``` 1351 * var latlng = L.latLng(50.5, 30.5); 1352 * ``` 1353 * 1354 * 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: 1355 * 1356 * ``` 1357 * map.panTo([50, 30]); 1358 * map.panTo({lon: 30, lat: 50}); 1359 * map.panTo({lat: 50, lng: 30}); 1360 * map.panTo(L.latLng(50, 30)); 1361 * ``` 1362 * 1363 * Note that `LatLng` does not inherit from Leaflet's `Class` object, 1364 * which means new classes can't inherit from it, and new methods 1365 * can't be added to it with the `include` function. 1366 */ 1367 1368function LatLng(lat, lng, alt) { 1369 if (isNaN(lat) || isNaN(lng)) { 1370 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1371 } 1372 1373 // @property lat: Number 1374 // Latitude in degrees 1375 this.lat = +lat; 1376 1377 // @property lng: Number 1378 // Longitude in degrees 1379 this.lng = +lng; 1380 1381 // @property alt: Number 1382 // Altitude in meters (optional) 1383 if (alt !== undefined) { 1384 this.alt = +alt; 1385 } 1386} 1387 1388LatLng.prototype = { 1389 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1390 // 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. 1391 equals: function (obj, maxMargin) { 1392 if (!obj) { return false; } 1393 1394 obj = toLatLng(obj); 1395 1396 var margin = Math.max( 1397 Math.abs(this.lat - obj.lat), 1398 Math.abs(this.lng - obj.lng)); 1399 1400 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1401 }, 1402 1403 // @method toString(): String 1404 // Returns a string representation of the point (for debugging purposes). 1405 toString: function (precision) { 1406 return 'LatLng(' + 1407 formatNum(this.lat, precision) + ', ' + 1408 formatNum(this.lng, precision) + ')'; 1409 }, 1410 1411 // @method distanceTo(otherLatLng: LatLng): Number 1412 // 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). 1413 distanceTo: function (other) { 1414 return Earth.distance(this, toLatLng(other)); 1415 }, 1416 1417 // @method wrap(): LatLng 1418 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1419 wrap: function () { 1420 return Earth.wrapLatLng(this); 1421 }, 1422 1423 // @method toBounds(sizeInMeters: Number): LatLngBounds 1424 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1425 toBounds: function (sizeInMeters) { 1426 var latAccuracy = 180 * sizeInMeters / 40075017, 1427 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1428 1429 return toLatLngBounds( 1430 [this.lat - latAccuracy, this.lng - lngAccuracy], 1431 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1432 }, 1433 1434 clone: function () { 1435 return new LatLng(this.lat, this.lng, this.alt); 1436 } 1437}; 1438 1439 1440 1441// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1442// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1443 1444// @alternative 1445// @factory L.latLng(coords: Array): LatLng 1446// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1447 1448// @alternative 1449// @factory L.latLng(coords: Object): LatLng 1450// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1451 1452function toLatLng(a, b, c) { 1453 if (a instanceof LatLng) { 1454 return a; 1455 } 1456 if (isArray(a) && typeof a[0] !== 'object') { 1457 if (a.length === 3) { 1458 return new LatLng(a[0], a[1], a[2]); 1459 } 1460 if (a.length === 2) { 1461 return new LatLng(a[0], a[1]); 1462 } 1463 return null; 1464 } 1465 if (a === undefined || a === null) { 1466 return a; 1467 } 1468 if (typeof a === 'object' && 'lat' in a) { 1469 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1470 } 1471 if (b === undefined) { 1472 return null; 1473 } 1474 return new LatLng(a, b, c); 1475} 1476 1477/* 1478 * @namespace CRS 1479 * @crs L.CRS.Base 1480 * Object that defines coordinate reference systems for projecting 1481 * geographical points into pixel (screen) coordinates and back (and to
1482 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 1483 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). 1484 * 1485 * Leaflet defines the most usual CRSs by default. If you want to use a 1486 * CRS not defined by default, take a look at the 1487 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 1488 * 1489 * Note that the CRS instances do not inherit from Leafet's `Class` object, 1490 * and can't be instantiated. Also, new classes can't inherit from them, 1491 * and methods can't be added to them with the `include` function. 1492 */ 1493 1494var CRS = { 1495 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1496 // Projects geographical coordinates into pixel coordinates for a given zoom. 1497 latLngToPoint: function (latlng, zoom) { 1498 var projectedPoint = this.projection.project(latlng), 1499 scale = this.scale(zoom); 1500 1501 return this.transformation._transform(projectedPoint, scale); 1502 }, 1503 1504 // @method pointToLatLng(point: Point, zoom: Number): LatLng 1505 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1506 // zoom into geographical coordinates. 1507 pointToLatLng: function (point, zoom) { 1508 var scale = this.scale(zoom), 1509 untransformedPoint = this.transformation.untransform(point, scale); 1510 1511 return this.projection.unproject(untransformedPoint); 1512 }, 1513 1514 // @method project(latlng: LatLng): Point 1515 // Projects geographical coordinates into coordinates in units accepted for 1516 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1517 project: function (latlng) { 1518 return this.projection.project(latlng); 1519 }, 1520 1521 // @method unproject(point: Point): LatLng 1522 // Given a projected coordinate returns the corresponding LatLng. 1523 // The inverse of `project`. 1524 unproject: function (point) { 1525 return this.projection.unproject(point); 1526 }, 1527 1528 // @method scale(zoom: Number): Number 1529 // Returns the scale used when transforming projected coordinates into 1530 // pixel coordinates for a particular zoom. For example, it returns 1531 // `256 * 2^zoom` for Mercator-based CRS. 1532 scale: function (zoom) { 1533 return 256 * Math.pow(2, zoom); 1534 }, 1535 1536 // @method zoom(scale: Number): Number 1537 // Inverse of `scale()`, returns the zoom level corresponding to a scale 1538 // factor of `scale`. 1539 zoom: function (scale) { 1540 return Math.log(scale / 256) / Math.LN2; 1541 }, 1542 1543 // @method getProjectedBounds(zoom: Number): Bounds 1544 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1545 getProjectedBounds: function (zoom) { 1546 if (this.infinite) { return null; } 1547 1548 var b = this.projection.bounds, 1549 s = this.scale(zoom), 1550 min = this.transformation.transform(b.min, s), 1551 max = this.transformation.transform(b.max, s); 1552 1553 return new Bounds(min, max); 1554 }, 1555 1556 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1557 // Returns the distance between two geographical coordinates. 1558 1559 // @property code: String 1560 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1561 // 1562 // @property wrapLng: Number[] 1563 // An array of two numbers defining whether the longitude (horizontal) coordinate 1564 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1565 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1566 // 1567 // @property wrapLat: Number[] 1568 // Like `wrapLng`, but for the latitude (vertical) axis. 1569 1570 // wrapLng: [min, max], 1571 // wrapLat: [min, max], 1572 1573 // @property infinite: Boolean 1574 // If true, the coordinate space will be unbounded (infinite in both axes) 1575 infinite: false, 1576 1577 // @method wrapLatLng(latlng: LatLng): LatLng 1578 // Returns a `LatLng` where lat and lng has been wrapped according to the 1579 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1580 wrapLatLng: function (latlng) { 1581 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 1582 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 1583 alt = latlng.alt; 1584 1585 return new LatLng(lat, lng, alt); 1586 }, 1587 1588 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1589 // Returns a `LatLngBounds` with the same size as the given one, ensuring 1590 // that its center is within the CRS's bounds. 1591 // Only accepts actual `L.LatLngBounds` instances, not arrays. 1592 wrapLatLngBounds: function (bounds) { 1593 var center = bounds.getCenter(), 1594 newCenter = this.wrapLatLng(center), 1595 latShift = center.lat - newCenter.lat, 1596 lngShift = center.lng - newCenter.lng; 1597 1598 if (latShift === 0 && lngShift === 0) { 1599 return bounds; 1600 } 1601
vendor: 8,594 bytes, lines 1602-1880
1602 var sw = bounds.getSouthWest(), 1603 ne = bounds.getNorthEast(), 1604 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), 1605 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1606 1607 return new LatLngBounds(newSw, newNe); 1608 } 1609}; 1610 1611/* 1612 * @namespace CRS 1613 * @crs L.CRS.Earth 1614 * 1615 * Serves as the base for CRS that are global such that they cover the earth. 1616 * Can only be used as the base for other CRS and cannot be used directly, 1617 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 1618 * meters. 1619 */ 1620 1621var Earth = extend({}, CRS, { 1622 wrapLng: [-180, 180], 1623 1624 // Mean Earth Radius, as recommended for use by 1625 // the International Union of Geodesy and Geophysics, 1626 // see http://rosettacode.org/wiki/Haversine_formula 1627 R: 6371000, 1628 1629 // distance between two geographical points using spherical law of cosines approximation 1630 distance: function (latlng1, latlng2) { 1631 var rad = Math.PI / 180, 1632 lat1 = latlng1.lat * rad, 1633 lat2 = latlng2.lat * rad, 1634 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), 1635 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), 1636 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, 1637 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1638 return this.R * c; 1639 } 1640}); 1641 1642/* 1643 * @namespace Projection 1644 * @projection L.Projection.SphericalMercator 1645 * 1646 * Spherical Mercator projection â the most common projection for online maps, 1647 * used by almost all free and commercial tile providers. Assumes that Earth is 1648 * a sphere. Used by the `EPSG:3857` CRS. 1649 */ 1650 1651var earthRadius = 6378137; 1652 1653var SphericalMercator = { 1654 1655 R: earthRadius, 1656 MAX_LATITUDE: 85.0511287798, 1657 1658 project: function (latlng) { 1659 var d = Math.PI / 180, 1660 max = this.MAX_LATITUDE, 1661 lat = Math.max(Math.min(max, latlng.lat), -max), 1662 sin = Math.sin(lat * d); 1663 1664 return new Point( 1665 this.R * latlng.lng * d, 1666 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 1667 }, 1668 1669 unproject: function (point) { 1670 var d = 180 / Math.PI; 1671 1672 return new LatLng( 1673 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 1674 point.x * d / this.R); 1675 }, 1676 1677 bounds: (function () { 1678 var d = earthRadius * Math.PI; 1679 return new Bounds([-d, -d], [d, d]); 1680 })() 1681}; 1682 1683/* 1684 * @class Transformation 1685 * @aka L.Transformation 1686 * 1687 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1688 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1689 * the reverse. Used by Leaflet in its projections code. 1690 * 1691 * @example 1692 * 1693 * ```js 1694 * var transformation = L.transformation(2, 5, -1, 10), 1695 * p = L.point(1, 2), 1696 * p2 = transformation.transform(p), // L.point(7, 8) 1697 * p3 = transformation.untransform(p2); // L.point(1, 2) 1698 * ``` 1699 */ 1700 1701 1702// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1703// Creates a `Transformation` object with the given coefficients. 1704function Transformation(a, b, c, d) { 1705 if (isArray(a)) { 1706 // use array properties 1707 this._a = a[0]; 1708 this._b = a[1]; 1709 this._c = a[2]; 1710 this._d = a[3]; 1711 return; 1712 } 1713 this._a = a; 1714 this._b = b; 1715 this._c = c; 1716 this._d = d; 1717} 1718 1719Transformation.prototype = { 1720 // @method transform(point: Point, scale?: Number): Point 1721 // Returns a transformed point, optionally multiplied by the given scale. 1722 // Only accepts actual `L.Point` instances, not arrays. 1723 transform: function (point, scale) { // (Point, Number) -> Point 1724 return this._transform(point.clone(), scale); 1725 }, 1726 1727 // destructive transform (faster) 1728 _transform: function (point, scale) { 1729 scale = scale || 1; 1730 point.x = scale * (this._a * point.x + this._b); 1731 point.y = scale * (this._c * point.y + this._d); 1732 return point; 1733 }, 1734 1735 // @method untransform(point: Point, scale?: Number): Point 1736 // Returns the reverse transformation of the given point, optionally divided 1737 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1738 untransform: function (point, scale) { 1739 scale = scale || 1; 1740 return new Point( 1741 (point.x / scale - this._b) / this._a, 1742 (point.y / scale - this._d) / this._c); 1743 } 1744}; 1745 1746// factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1747 1748// @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1749// Instantiates a Transformation object with the given coefficients. 1750 1751// @alternative 1752// @factory L.transformation(coefficients: Array): Transformation 1753// Expects an coefficients array of the form 1754// `[a: Number, b: Number, c: Number, d: Number]`. 1755 1756function toTransformation(a, b, c, d) { 1757 return new Transformation(a, b, c, d); 1758} 1759 1760/* 1761 * @namespace CRS 1762 * @crs L.CRS.EPSG3857 1763 * 1764 * The most common CRS for online maps, used by almost all free and commercial 1765 * tile providers. Uses Spherical Mercator projection. Set in by default in 1766 * Map's `crs` option. 1767 */ 1768 1769var EPSG3857 = extend({}, Earth, { 1770 code: 'EPSG:3857', 1771 projection: SphericalMercator, 1772 1773 transformation: (function () { 1774 var scale = 0.5 / (Math.PI * SphericalMercator.R); 1775 return toTransformation(scale, 0.5, -scale, 0.5); 1776 }()) 1777}); 1778 1779var EPSG900913 = extend({}, EPSG3857, { 1780 code: 'EPSG:900913' 1781}); 1782 1783// @namespace SVG; @section 1784// There are several static functions which can be called without instantiating L.SVG: 1785 1786// @function create(name: String): SVGElement 1787// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 1788// corresponding to the class name passed. For example, using 'line' will return 1789// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 1790function svgCreate(name) { 1791 return document.createElementNS('http://www.w3.org/2000/svg', name); 1792} 1793 1794// @function pointsToPath(rings: Point[], closed: Boolean): String 1795// Generates a SVG path string for multiple rings, with each ring turning 1796// into "M..L..L.." instructions 1797function pointsToPath(rings, closed) { 1798 var str = '', 1799 i, j, len, len2, points, p; 1800 1801 for (i = 0, len = rings.length; i < len; i++) { 1802 points = rings[i]; 1803 1804 for (j = 0, len2 = points.length; j < len2; j++) { 1805 p = points[j]; 1806 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 1807 } 1808 1809 // closes the ring for polygons; "x" is VML syntax 1810 str += closed ? (svg ? 'z' : 'x') : ''; 1811 } 1812 1813 // SVG complains about empty path strings 1814 return str || 'M0 0'; 1815} 1816 1817/* 1818 * @namespace Browser 1819 * @aka L.Browser 1820 * 1821 * A namespace with static properties for browser/feature detection used by Leaflet internally. 1822 * 1823 * @example 1824 * 1825 * ```js 1826 * if (L.Browser.ielt9) { 1827 * alert('Upgrade your browser, dude!'); 1828 * } 1829 * ``` 1830 */ 1831 1832var style$1 = document.documentElement.style; 1833 1834// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 1835var ie = 'ActiveXObject' in window; 1836 1837// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 1838var ielt9 = ie && !document.addEventListener; 1839 1840// @property edge: Boolean; `true` for the Edge web browser. 1841var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 1842 1843// @property webkit: Boolean; 1844// `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 1845var webkit = userAgentContains('webkit'); 1846 1847// @property android: Boolean 1848// `true` for any browser running on an Android platform. 1849var android = userAgentContains('android'); 1850 1851// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3. 1852var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 1853 1854/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 1855var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 1856// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome) 1857var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 1858 1859// @property opera: Boolean; `true` for the Opera browser 1860var opera = !!window.opera; 1861 1862// @property chrome: Boolean; `true` for the Chrome browser. 1863var chrome = userAgentContains('chrome'); 1864 1865// @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 1866var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 1867 1868// @property safari: Boolean; `true` for the Safari browser. 1869var safari = !chrome && userAgentContains('safari'); 1870 1871var phantom = userAgentContains('phantom'); 1872 1873// @property opera12: Boolean 1874// `true` for the Opera browser supporting CSS transforms (version 12 or later). 1875var opera12 = 'OTransition' in style$1; 1876 1877// @property win: Boolean; `true` when the browser is running in a Windows platform 1878var win = navigator.platform.indexOf('Win') === 0; 1879 1880// @property ie3d: Boolean;
1880 `true` for all Internet Explorer versions supporting CSS transforms. 1881var ie3d = ie && ('transition' in style$1); 1882 1883// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 1884var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 1885 1886// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 1887var gecko3d = 'MozPerspective' in style$1; 1888 1889// @property any3d: Boolean 1890// `true` for all browsers supporting CSS transforms. 1891var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1892 1893// @property mobile: Boolean; `true` for all browsers running in a mobile device. 1894var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 1895 1896// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 1897var mobileWebkit = mobile && webkit; 1898 1899// @property mobileWebkit3d: Boolean 1900// `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 1901var mobileWebkit3d = mobile && webkit3d; 1902 1903// @property msPointer: Boolean 1904// `true` for browsers implementing the Microsoft touch events model (notably IE10). 1905var msPointer = !window.PointerEvent && window.MSPointerEvent; 1906 1907// @property pointer: Boolean 1908// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 1909var pointer = !webkit && !!(window.PointerEvent || msPointer); 1910 1911// @property touch: Boolean 1912// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 1913// This does not necessarily mean that the browser is running in a computer with 1914// a touchscreen, it only means that the browser is capable of understanding 1915// touch events. 1916var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || 1917 (window.DocumentTouch && document instanceof window.DocumentTouch)); 1918 1919// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 1920var mobileOpera = mobile && opera; 1921 1922// @property mobileGecko: Boolean 1923// `true` for gecko-based browsers running in a mobile device. 1924var mobileGecko = mobile && gecko; 1925 1926// @property retina: Boolean 1927// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 1928var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 1929 1930// @property passiveEvents: Boolean 1931// `true` for browsers that support passive events. 1932var passiveEvents = (function () { 1933 var supportsPassiveOption = false; 1934 try { 1935 var opts = Object.defineProperty({}, 'passive', { 1936 get: function () { 1937 supportsPassiveOption = true; 1938 } 1939 }); 1940 window.addEventListener('testPassiveEventSupport', falseFn, opts); 1941 window.removeEventListener('testPassiveEventSupport', falseFn, opts); 1942 } catch (e) { 1943 // Errors can safely be ignored since this is only a browser support test. 1944 } 1945 return supportsPassiveOption; 1946}); 1947 1948// @property canvas: Boolean 1949// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 1950var canvas = (function () { 1951 return !!document.createElement('canvas').getContext; 1952}()); 1953 1954// @property svg: Boolean 1955// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 1956var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect); 1957 1958// @property vml: Boolean 1959// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 1960var vml = !svg && (function () { 1961 try { 1962 var div = document.createElement('div'); 1963 div.innerHTML = '<v:shape adj="1"/>'; 1964 1965 var shape = div.firstChild; 1966 shape.style.behavior = 'url(#default#VML)'; 1967 1968 return shape && (typeof shape.adj === 'object'); 1969 1970 } catch (e) { 1971 return false; 1972 } 1973}()); 1974 1975 1976function userAgentContains(str) { 1977 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 1978} 1979 1980 1981var Browser = (Object.freeze || Object)({ 1982 ie: ie, 1983 ielt9: ielt9, 1984 edge: edge, 1985 webkit: webkit, 1986 android: android, 1987 android23: android23, 1988 androidStock: androidStock, 1989 opera: opera, 1990 chrome: chrome, 1991 gecko: gecko, 1992 safari: safari, 1993 phantom: phantom, 1994 opera12: opera12, 1995 win: win, 1996 ie3d: ie3d, 1997 webkit3d: webkit3d, 1998 gecko3d: gecko3d, 1999 any3d: any3d, 2000 mobile: mobile, 2001 mobileWebkit: mobileWebkit, 2002 mobileWebkit3d: mobileWebkit3d,
2003 msPointer: msPointer, 2004 pointer: pointer, 2005 touch: touch, 2006 mobileOpera: mobileOpera, 2007 mobileGecko: mobileGecko, 2008 retina: retina, 2009 passiveEvents: passiveEvents, 2010 canvas: canvas, 2011 svg: svg, 2012 vml: vml 2013}); 2014 2015/* 2016 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 2017 */ 2018 2019 2020var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown'; 2021var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'; 2022var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'; 2023var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'; 2024var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION']; 2025 2026var _pointers = {}; 2027var _pointerDocListener = false; 2028 2029// DomEvent.DoubleTap needs to know about this 2030var _pointersCount = 0; 2031 2032// Provides a touch events wrapper for (ms)pointer events. 2033// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2034 2035function addPointerListener(obj, type, handler, id) { 2036 if (type === 'touchstart') { 2037 _addPointerStart(obj, handler, id); 2038 2039 } else if (type === 'touchmove') { 2040 _addPointerMove(obj, handler, id); 2041 2042 } else if (type === 'touchend') { 2043 _addPointerEnd(obj, handler, id); 2044 } 2045 2046 return this; 2047} 2048 2049function removePointerListener(obj, type, id) { 2050 var handler = obj['_leaflet_' + type + id]; 2051 2052 if (type === 'touchstart') { 2053 obj.removeEventListener(POINTER_DOWN, handler, false); 2054 2055 } else if (type === 'touchmove') { 2056 obj.removeEventListener(POINTER_MOVE, handler, false); 2057 2058 } else if (type === 'touchend') { 2059 obj.removeEventListener(POINTER_UP, handler, false); 2060 obj.removeEventListener(POINTER_CANCEL, handler, false); 2061 } 2062 2063 return this; 2064} 2065 2066function _addPointerStart(obj, handler, id) { 2067 var onDown = bind(function (e) { 2068 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { 2069 // In IE11, some touch events needs to fire for form controls, or 2070 // the controls will stop working. We keep a whitelist of tag names that 2071 // need these events. For other target tags, we prevent default on the event. 2072 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { 2073 preventDefault(e); 2074 } else { 2075 return; 2076 } 2077 } 2078 2079 _handlePointer(e, handler); 2080 }); 2081 2082 obj['_leaflet_touchstart' + id] = onDown; 2083 obj.addEventListener(POINTER_DOWN, onDown, false); 2084 2085 // need to keep track of what pointers and how many are active to provide e.touches emulation 2086 if (!_pointerDocListener) { 2087 // we listen documentElement as any drags that end by moving the touch off the screen get fired there 2088 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2089 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2090 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true); 2091 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2092 2093 _pointerDocListener = true; 2094 } 2095} 2096 2097function _globalPointerDown(e) { 2098 _pointers[e.pointerId] = e; 2099 _pointersCount++; 2100} 2101 2102function _globalPointerMove(e) { 2103 if (_pointers[e.pointerId]) { 2104 _pointers[e.pointerId] = e; 2105 } 2106} 2107 2108function _globalPointerUp(e) { 2109 delete _pointers[e.pointerId]; 2110 _pointersCount--; 2111} 2112 2113function _handlePointer(e, handler) { 2114 e.touches = []; 2115 for (var i in _pointers) { 2116 e.touches.push(_pointers[i]); 2117 } 2118 e.changedTouches = [e]; 2119 2120 handler(e); 2121} 2122 2123function _addPointerMove(obj, handler, id) { 2124 var onMove = function (e) { 2125 // don't fire touch moves when mouse isn't down 2126 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } 2127 2128 _handlePointer(e, handler); 2129 }; 2130 2131 obj['_leaflet_touchmove' + id] = onMove; 2132 obj.addEventListener(POINTER_MOVE, onMove, false); 2133} 2134 2135function _addPointerEnd(obj, handler, id) { 2136 var onUp = function (e) { 2137 _handlePointer(e, handler); 2138 }; 2139 2140 obj['_leaflet_touchend' + id] = onUp; 2141 obj.addEventListener(POINTER_UP, onUp, false); 2142 obj.addEventListener(POINTER_CANCEL, onUp, false); 2143} 2144 2145/* 2146 * Extends the event handling code with double tap support for mobile browsers. 2147 */ 2148 2149var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart'; 2150var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend'; 2151var _pre = '_leaflet_'; 2152 2153// inspired by Zepto touch code by Thomas Fuchs 2154function addDoubleTapListener(obj, handler, id) { 2155 var last, touch$$1, 2156 doubleTap = false,
2157 delay = 250; 2158 2159 function onTouchStart(e) { 2160 var count; 2161 2162 if (pointer) { 2163 if ((!edge) || e.pointerType === 'mouse') { return; } 2164 count = _pointersCount; 2165 } else { 2166 count = e.touches.length; 2167 } 2168 2169 if (count > 1) { return; } 2170 2171 var now = Date.now(), 2172 delta = now - (last || now); 2173 2174 touch$$1 = e.touches ? e.touches[0] : e; 2175 doubleTap = (delta > 0 && delta <= delay); 2176 last = now; 2177 } 2178 2179 function onTouchEnd(e) { 2180 if (doubleTap && !touch$$1.cancelBubble) { 2181 if (pointer) { 2182 if ((!edge) || e.pointerType === 'mouse') { return; } 2183 // work around .type being readonly with MSPointer* events 2184 var newTouch = {}, 2185 prop, i; 2186 2187 for (i in touch$$1) { 2188 prop = touch$$1[i]; 2189 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop; 2190 } 2191 touch$$1 = newTouch; 2192 } 2193 touch$$1.type = 'dblclick'; 2194 touch$$1.button = 0; 2195 handler(touch$$1); 2196 last = null; 2197 } 2198 } 2199 2200 obj[_pre + _touchstart + id] = onTouchStart; 2201 obj[_pre + _touchend + id] = onTouchEnd; 2202 obj[_pre + 'dblclick' + id] = handler; 2203 2204 obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false); 2205 obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false); 2206 2207 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), 2208 // the browser doesn't fire touchend/pointerup events but does fire 2209 // native dblclicks. See #4127. 2210 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. 2211 obj.addEventListener('dblclick', handler, false); 2212 2213 return this; 2214} 2215 2216function removeDoubleTapListener(obj, id) { 2217 var touchstart = obj[_pre + _touchstart + id], 2218 touchend = obj[_pre + _touchend + id], 2219 dblclick = obj[_pre + 'dblclick' + id]; 2220 2221 obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false); 2222 obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false); 2223 if (!edge) { 2224 obj.removeEventListener('dblclick', dblclick, false); 2225 } 2226 2227 return this; 2228} 2229 2230/* 2231 * @namespace DomUtil 2232 * 2233 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2234 * tree, used by Leaflet internally. 2235 * 2236 * Most functions expecting or returning a `HTMLElement` also work for 2237 * SVG elements. The only difference is that classes refer to CSS classes 2238 * in HTML and SVG classes in SVG. 2239 */ 2240 2241 2242// @property TRANSFORM: String 2243// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2244var TRANSFORM = testProp( 2245 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2246 2247// webkitTransition comes first because some browser versions that drop vendor prefix don't do 2248// the same for the transitionend event, in particular the Android 4.1 stock browser 2249 2250// @property TRANSITION: String 2251// Vendor-prefixed transition style name. 2252var TRANSITION = testProp( 2253 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2254 2255// @property TRANSITION_END: String 2256// Vendor-prefixed transitionend event name. 2257var TRANSITION_END = 2258 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2259 2260 2261// @function get(id: String|HTMLElement): HTMLElement 2262// Returns an element given its DOM id, or returns the element itself 2263// if it was passed directly. 2264function get(id) { 2265 return typeof id === 'string' ? document.getElementById(id) : id; 2266} 2267 2268// @function getStyle(el: HTMLElement, styleAttrib: String): String 2269// Returns the value for a certain style attribute on an element, 2270// including computed values or values set through CSS. 2271function getStyle(el, style) { 2272 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2273 2274 if ((!value || value === 'auto') && document.defaultView) { 2275 var css = document.defaultView.getComputedStyle(el, null); 2276 value = css ? css[style] : null; 2277 } 2278 return value === 'auto' ? null : value; 2279} 2280 2281// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2282// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2283function create$1(tagName, className, container) { 2284 var el = document.createElement(tagName); 2285 el.className = className || ''; 2286 2287 if (container) { 2288 container.appendChild(el); 2289 } 2290 return el; 2291} 2292 2293// @function remove(el: HTMLElement) 2294// Removes `el` from its parent element 2295function remove(el) { 2296 var parent = el.parentNode; 2297 if (parent) { 2298 parent.removeChild(el); 2299 } 2300} 2301 2302// @function empty(el: HTMLElement) 2303// Removes all of `el`'s children elements from `el` 2304function empty(el) { 2305 while (el.firstChild) { 2306 el.removeChild(el.firstChild); 2307 } 2308} 2309 2310// @function toFront(el: HTMLElement)
vendor: 9,504 bytes, lines 2311-2612
2311// Makes `el` the last child of its parent, so it renders in front of the other children. 2312function toFront(el) { 2313 var parent = el.parentNode; 2314 if (parent && parent.lastChild !== el) { 2315 parent.appendChild(el); 2316 } 2317} 2318 2319// @function toBack(el: HTMLElement) 2320// Makes `el` the first child of its parent, so it renders behind the other children. 2321function toBack(el) { 2322 var parent = el.parentNode; 2323 if (parent && parent.firstChild !== el) { 2324 parent.insertBefore(el, parent.firstChild); 2325 } 2326} 2327 2328// @function hasClass(el: HTMLElement, name: String): Boolean 2329// Returns `true` if the element's class attribute contains `name`. 2330function hasClass(el, name) { 2331 if (el.classList !== undefined) { 2332 return el.classList.contains(name); 2333 } 2334 var className = getClass(el); 2335 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2336} 2337 2338// @function addClass(el: HTMLElement, name: String) 2339// Adds `name` to the element's class attribute. 2340function addClass(el, name) { 2341 if (el.classList !== undefined) { 2342 var classes = splitWords(name); 2343 for (var i = 0, len = classes.length; i < len; i++) { 2344 el.classList.add(classes[i]); 2345 } 2346 } else if (!hasClass(el, name)) { 2347 var className = getClass(el); 2348 setClass(el, (className ? className + ' ' : '') + name); 2349 } 2350} 2351 2352// @function removeClass(el: HTMLElement, name: String) 2353// Removes `name` from the element's class attribute. 2354function removeClass(el, name) { 2355 if (el.classList !== undefined) { 2356 el.classList.remove(name); 2357 } else { 2358 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2359 } 2360} 2361 2362// @function setClass(el: HTMLElement, name: String) 2363// Sets the element's class. 2364function setClass(el, name) { 2365 if (el.className.baseVal === undefined) { 2366 el.className = name; 2367 } else { 2368 // in case of SVG element 2369 el.className.baseVal = name; 2370 } 2371} 2372 2373// @function getClass(el: HTMLElement): String 2374// Returns the element's class. 2375function getClass(el) { 2376 // Check if the element is an SVGElementInstance and use the correspondingElement instead 2377 // (Required for linked SVG elements in IE11.) 2378 if (el.correspondingElement) { 2379 el = el.correspondingElement; 2380 } 2381 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2382} 2383 2384// @function setOpacity(el: HTMLElement, opacity: Number) 2385// Set the opacity of an element (including old IE support). 2386// `opacity` must be a number from `0` to `1`. 2387function setOpacity(el, value) { 2388 if ('opacity' in el.style) { 2389 el.style.opacity = value; 2390 } else if ('filter' in el.style) { 2391 _setOpacityIE(el, value); 2392 } 2393} 2394 2395function _setOpacityIE(el, value) { 2396 var filter = false, 2397 filterName = 'DXImageTransform.Microsoft.Alpha'; 2398 2399 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2400 try { 2401 filter = el.filters.item(filterName); 2402 } catch (e) { 2403 // don't set opacity to 1 if we haven't already set an opacity, 2404 // it isn't needed and breaks transparent pngs. 2405 if (value === 1) { return; } 2406 } 2407 2408 value = Math.round(value * 100); 2409 2410 if (filter) { 2411 filter.Enabled = (value !== 100); 2412 filter.Opacity = value; 2413 } else { 2414 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2415 } 2416} 2417 2418// @function testProp(props: String[]): String|false 2419// Goes through the array of style names and returns the first name 2420// that is a valid style name for an element. If no such name is found, 2421// it returns false. Useful for vendor-prefixed styles like `transform`. 2422function testProp(props) { 2423 var style = document.documentElement.style; 2424 2425 for (var i = 0; i < props.length; i++) { 2426 if (props[i] in style) { 2427 return props[i]; 2428 } 2429 } 2430 return false; 2431} 2432 2433// @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2434// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2435// and optionally scaled by `scale`. Does not have an effect if the 2436// browser doesn't support 3D CSS transforms. 2437function setTransform(el, offset, scale) { 2438 var pos = offset || new Point(0, 0); 2439 2440 el.style[TRANSFORM] = 2441 (ie3d ? 2442 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2443 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2444 (scale ? ' scale(' + scale + ')' : ''); 2445} 2446 2447// @function setPosition(el: HTMLElement, position: Point) 2448// Sets the position of `el` to coordinates specified by `position`, 2449// using CSS translate or top/left positioning depending on the browser 2450// (used by Leaflet internally to position its layers). 2451function setPosition(el, point) { 2452 2453 /*eslint-disable */ 2454 el._leaflet_pos = point; 2455 /* eslint-enable */ 2456 2457 if (any3d) { 2458 setTransform(el, point); 2459 } else { 2460 el.style.left = point.x + 'px'; 2461 el.style.top = point.y + 'px'; 2462 } 2463} 2464 2465// @function getPosition(el: HTMLElement): Point 2466// Returns the coordinates of an element previously positioned with setPosition. 2467function getPosition(el) { 2468 // this method is only used for elements previously positioned using setPosition, 2469 // so it's safe to cache the position for performance 2470 2471 return el._leaflet_pos || new Point(0, 0); 2472} 2473 2474// @function disableTextSelection() 2475// Prevents the user from generating `selectstart` DOM events, usually generated 2476// when the user drags the mouse through a page with text. Used internally 2477// by Leaflet to override the behaviour of any click-and-drag interaction on 2478// the map. Affects drag interactions on the whole document. 2479 2480// @function enableTextSelection() 2481// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2482var disableTextSelection; 2483var enableTextSelection; 2484var _userSelect; 2485if ('onselectstart' in document) { 2486 disableTextSelection = function () { 2487 on(window, 'selectstart', preventDefault); 2488 }; 2489 enableTextSelection = function () { 2490 off(window, 'selectstart', preventDefault); 2491 }; 2492} else { 2493 var userSelectProperty = testProp( 2494 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2495 2496 disableTextSelection = function () { 2497 if (userSelectProperty) { 2498 var style = document.documentElement.style; 2499 _userSelect = style[userSelectProperty]; 2500 style[userSelectProperty] = 'none'; 2501 } 2502 }; 2503 enableTextSelection = function () { 2504 if (userSelectProperty) { 2505 document.documentElement.style[userSelectProperty] = _userSelect; 2506 _userSelect = undefined; 2507 } 2508 }; 2509} 2510 2511// @function disableImageDrag() 2512// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2513// for `dragstart` DOM events, usually generated when the user drags an image. 2514function disableImageDrag() { 2515 on(window, 'dragstart', preventDefault); 2516} 2517 2518// @function enableImageDrag() 2519// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2520function enableImageDrag() { 2521 off(window, 'dragstart', preventDefault); 2522} 2523 2524var _outlineElement; 2525var _outlineStyle; 2526// @function preventOutline(el: HTMLElement) 2527// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2528// of the element `el` invisible. Used internally by Leaflet to prevent 2529// focusable elements from displaying an outline when the user performs a 2530// drag interaction on them. 2531function preventOutline(element) { 2532 while (element.tabIndex === -1) { 2533 element = element.parentNode; 2534 } 2535 if (!element.style) { return; } 2536 restoreOutline(); 2537 _outlineElement = element; 2538 _outlineStyle = element.style.outline; 2539 element.style.outline = 'none'; 2540 on(window, 'keydown', restoreOutline); 2541} 2542 2543// @function restoreOutline() 2544// Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2545function restoreOutline() { 2546 if (!_outlineElement) { return; } 2547 _outlineElement.style.outline = _outlineStyle; 2548 _outlineElement = undefined; 2549 _outlineStyle = undefined; 2550 off(window, 'keydown', restoreOutline); 2551} 2552 2553// @function getSizedParentNode(el: HTMLElement): HTMLElement 2554// Finds the closest parent node which size (width and height) is not null. 2555function getSizedParentNode(element) { 2556 do { 2557 element = element.parentNode; 2558 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2559 return element; 2560} 2561 2562// @function getScale(el: HTMLElement): Object 2563// Computes the CSS scale currently applied on the element. 2564// Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2565// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2566function getScale(element) { 2567 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2568 2569 return { 2570 x: rect.width / element.offsetWidth || 1, 2571 y: rect.height / element.offsetHeight || 1, 2572 boundingClientRect: rect 2573 }; 2574} 2575 2576 2577var DomUtil = (Object.freeze || Object)({ 2578 TRANSFORM: TRANSFORM, 2579 TRANSITION: TRANSITION, 2580 TRANSITION_END: TRANSITION_END, 2581 get: get, 2582 getStyle: getStyle, 2583 create: create$1, 2584 remove: remove, 2585 empty: empty, 2586 toFront: toFront, 2587 toBack: toBack, 2588 hasClass: hasClass, 2589 addClass: addClass, 2590 removeClass: removeClass, 2591 setClass: setClass, 2592 getClass: getClass, 2593 setOpacity: setOpacity, 2594 testProp: testProp, 2595 setTransform: setTransform, 2596 setPosition: setPosition, 2597 getPosition: getPosition, 2598 disableTextSelection: disableTextSelection, 2599 enableTextSelection: enableTextSelection, 2600 disableImageDrag: disableImageDrag, 2601 enableImageDrag: enableImageDrag, 2602 preventOutline: preventOutline, 2603 restoreOutline: restoreOutline, 2604 getSizedParentNode: getSizedParentNode, 2605 getScale: getScale 2606}); 2607 2608/* 2609 * @namespace DomEvent 2610 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2611 */ 2612
2613// Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2614 2615// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2616// Adds a listener function (`fn`) to a particular DOM event type of the 2617// element `el`. You can optionally specify the context of the listener 2618// (object the `this` keyword will point to). You can also pass several 2619// space-separated types (e.g. `'click dblclick'`). 2620 2621// @alternative 2622// @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2623// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2624function on(obj, types, fn, context) { 2625 2626 if (typeof types === 'object') { 2627 for (var type in types) { 2628 addOne(obj, type, types[type], fn); 2629 } 2630 } else { 2631 types = splitWords(types); 2632 2633 for (var i = 0, len = types.length; i < len; i++) { 2634 addOne(obj, types[i], fn, context); 2635 } 2636 } 2637 2638 return this; 2639} 2640 2641var eventsKey = '_leaflet_events'; 2642 2643// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2644// Removes a previously added listener function. 2645// Note that if you passed a custom context to on, you must pass the same 2646// context to `off` in order to remove the listener. 2647 2648// @alternative 2649// @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2650// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2651function off(obj, types, fn, context) { 2652 2653 if (typeof types === 'object') { 2654 for (var type in types) { 2655 removeOne(obj, type, types[type], fn); 2656 } 2657 } else if (types) { 2658 types = splitWords(types); 2659 2660 for (var i = 0, len = types.length; i < len; i++) { 2661 removeOne(obj, types[i], fn, context); 2662 } 2663 } else { 2664 for (var j in obj[eventsKey]) { 2665 removeOne(obj, j, obj[eventsKey][j]); 2666 } 2667 delete obj[eventsKey]; 2668 } 2669 2670 return this; 2671} 2672 2673function addOne(obj, type, fn, context) { 2674 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2675 2676 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 2677 2678 var handler = function (e) { 2679 return fn.call(context || obj, e || window.event); 2680 }; 2681 2682 var originalHandler = handler; 2683 2684 if (pointer && type.indexOf('touch') === 0) { 2685 // Needs DomEvent.Pointer.js 2686 addPointerListener(obj, type, handler, id); 2687 2688 } else if (touch && (type === 'dblclick') && addDoubleTapListener && 2689 !(pointer && chrome)) { 2690 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener 2691 // See #5180 2692 addDoubleTapListener(obj, handler, id); 2693 2694 } else if ('addEventListener' in obj) { 2695 2696 if (type === 'mousewheel') { 2697 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false); 2698 2699 } else if ((type === 'mouseenter') || (type === 'mouseleave')) { 2700 handler = function (e) { 2701 e = e || window.event; 2702 if (isExternalTarget(obj, e)) { 2703 originalHandler(e); 2704 } 2705 }; 2706 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); 2707 2708 } else { 2709 if (type === 'click' && android) { 2710 handler = function (e) { 2711 filterClick(e, originalHandler); 2712 }; 2713 } 2714 obj.addEventListener(type, handler, false); 2715 } 2716 2717 } else if ('attachEvent' in obj) { 2718 obj.attachEvent('on' + type, handler); 2719 } 2720 2721 obj[eventsKey] = obj[eventsKey] || {}; 2722 obj[eventsKey][id] = handler; 2723} 2724 2725function removeOne(obj, type, fn, context) { 2726 2727 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''), 2728 handler = obj[eventsKey] && obj[eventsKey][id]; 2729 2730 if (!handler) { return this; } 2731 2732 if (pointer && type.indexOf('touch') === 0) { 2733 removePointerListener(obj, type, id); 2734 2735 } else if (touch && (type === 'dblclick') && removeDoubleTapListener && 2736 !(pointer && chrome)) { 2737 removeDoubleTapListener(obj, id); 2738 2739 } else if ('removeEventListener' in obj) { 2740 2741 if (type === 'mousewheel') { 2742 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false); 2743 2744 } else { 2745 obj.removeEventListener( 2746 type === 'mouseenter' ? 'mouseover' : 2747 type === 'mouseleave' ? 'mouseout' : type, handler, false); 2748 } 2749 2750 } else if ('detachEvent' in obj) { 2751 obj.detachEvent('on' + type, handler); 2752 } 2753 2754 obj[eventsKey][id] = null; 2755} 2756 2757// @function stopPropagation(ev: DOMEvent): this 2758// Stop the given event from propagation to parent elements. Used inside the listener functions: 2759// ```js 2760// L.DomEvent.on(div, 'click', function (ev) { 2761// L.DomEvent.stopPropagation(ev); 2762// }); 2763// ``` 2764function stopPropagation(e) { 2765 2766 if (e.stopPropagation) { 2767 e.stopPropagation(); 2768 } else if (e.originalEvent) { // In case of Leaflet event. 2769 e.originalEvent._stopped = true; 2770 } else { 2771 e.cancelBubble = true; 2772 } 2773 skipped(e); 2774 2775 return this; 2776} 2777 2778// @function disableScrollPropagation(el: HTMLElement): this 2779// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). 2780function disableScrollPropagation(el) { 2781 addOne(el, 'mousewheel', stopPropagation); 2782 return this; 2783} 2784 2785// @function disableClickPropagation(el: HTMLElement): this 2786// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, 2787// `'mousedown'` and `'touchstart'` events (plus browser variants). 2788function disableClickPropagation(el) { 2789 on(el, 'mousedown touchstart dblclick', stopPropagation); 2790 addOne(el, 'click', fakeStop); 2791 return this; 2792} 2793 2794// @function preventDefault(ev: DOMEvent): this 2795// Prevents the default action of the DOM Event `ev` from happening (such as 2796// following a link in the href of the a element, or doing a POST request 2797// with page reload when a `<form>` is submitted). 2798// Use it inside listener functions. 2799function preventDefault(e) { 2800 if (e.preventDefault) { 2801 e.preventDefault(); 2802 } else { 2803 e.returnValue = false;
2804 } 2805 return this; 2806} 2807 2808// @function stop(ev: DOMEvent): this 2809// Does `stopPropagation` and `preventDefault` at the same time. 2810function stop(e) { 2811 preventDefault(e); 2812 stopPropagation(e); 2813 return this; 2814} 2815 2816// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 2817// Gets normalized mouse position from a DOM event relative to the 2818// `container` (border excluded) or to the whole page if not specified. 2819function getMousePosition(e, container) { 2820 if (!container) { 2821 return new Point(e.clientX, e.clientY); 2822 } 2823 2824 var scale = getScale(container), 2825 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 2826 2827 return new Point( 2828 // offset.left/top values are in page scale (like clientX/Y), 2829 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 2830 (e.clientX - offset.left) / scale.x - container.clientLeft, 2831 (e.clientY - offset.top) / scale.y - container.clientTop 2832 ); 2833} 2834 2835// Chrome on Win scrolls double the pixels as in other platforms (see #4538), 2836// and Firefox scrolls device pixels, not CSS pixels 2837var wheelPxFactor = 2838 (win && chrome) ? 2 * window.devicePixelRatio : 2839 gecko ? window.devicePixelRatio : 1; 2840 2841// @function getWheelDelta(ev: DOMEvent): Number 2842// Gets normalized wheel delta from a mousewheel DOM event, in vertical 2843// pixels scrolled (negative if scrolling down). 2844// Events from pointing devices without precise scrolling are mapped to 2845// a best guess of 60 pixels. 2846function getWheelDelta(e) { 2847 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 2848 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 2849 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 2850 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 2851 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2852 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 2853 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 2854 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 2855 0; 2856} 2857 2858var skipEvents = {}; 2859 2860function fakeStop(e) { 2861 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) 2862 skipEvents[e.type] = true; 2863} 2864 2865function skipped(e) { 2866 var events = skipEvents[e.type]; 2867 // reset when checking, as it's only used in map container and propagates outside of the map 2868 skipEvents[e.type] = false; 2869 return events; 2870} 2871 2872// check if element really left/entered the event target (for mouseenter/mouseleave) 2873function isExternalTarget(el, e) { 2874 2875 var related = e.relatedTarget; 2876 2877 if (!related) { return true; } 2878 2879 try { 2880 while (related && (related !== el)) { 2881 related = related.parentNode; 2882 } 2883 } catch (err) { 2884 return false; 2885 } 2886 return (related !== el); 2887} 2888 2889var lastClick; 2890 2891// this is a horrible workaround for a bug in Android where a single touch triggers two click events 2892function filterClick(e, handler) { 2893 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), 2894 elapsed = lastClick && (timeStamp - lastClick); 2895 2896 // are they closer together than 500ms yet more than 100ms? 2897 // Android typically triggers them ~300ms apart while multiple listeners 2898 // on the same event should be triggered far faster; 2899 // or check if click is simulated on the element, and if it is, reject any non-simulated events 2900 2901 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { 2902 stop(e); 2903 return; 2904 } 2905 lastClick = timeStamp; 2906 2907 handler(e); 2908} 2909 2910 2911 2912 2913var DomEvent = (Object.freeze || Object)({ 2914 on: on, 2915 off: off, 2916 stopPropagation: stopPropagation, 2917 disableScrollPropagation: disableScrollPropagation, 2918 disableClickPropagation: disableClickPropagation, 2919 preventDefault: preventDefault, 2920 stop: stop, 2921 getMousePosition: getMousePosition, 2922 getWheelDelta: getWheelDelta, 2923 fakeStop: fakeStop, 2924 skipped: skipped, 2925 isExternalTarget: isExternalTarget, 2926 addListener: on, 2927 removeListener: off 2928}); 2929 2930/* 2931 * @class PosAnimation 2932 * @aka L.PosAnimation 2933 * @inherits Evented 2934 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 2935 * 2936 * @example 2937 * ```js 2938 * var fx = new L.PosAnimation(); 2939 * fx.run(el, [300, 500], 0.5); 2940 * ``` 2941 * 2942 * @constructor L.PosAnimation() 2943 * Creates a `PosAnimation` object. 2944 * 2945 */ 2946 2947var PosAnimation = Evented.extend({ 2948 2949 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 2950 // Run an animation of a given element to a new position, optionally setting 2951 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 2952 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), 2953 // `0.5` by default). 2954 run: function (el, newPos, duration, easeLinearity) { 2955 this.stop(); 2956 2957 this._el = el; 2958 this._inProgress = true; 2959 this._duration = duration || 0.25; 2960 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 2961 2962 this._startPos = getPosition(el); 2963 this._offset = newPos.subtract(this._startPos); 2964 this._startTime = +new Date(); 2965 2966 // @event start: Event 2967 // Fired when the animation starts 2968 this.fire('start'); 2969 2970 this._animate(); 2971 }, 2972 2973 // @method stop() 2974 // Stops the animation (if currently running). 2975 stop: function () { 2976 if (!this._inProgress) { return; } 2977 2978 this._step(true); 2979 this._complete(); 2980 }, 2981 2982 _animate: function () { 2983 // animation loop 2984 this._animId = requestAnimFrame(this._animate, this); 2985 this._step(); 2986 }, 2987 2988 _step: function (round) { 2989 var elapsed = (+new Date()) - this._startTime, 2990 duration = this._duration * 1000; 2991 2992 if (elapsed < duration) { 2993 this._runFrame(this._easeOut(elapsed / duration), round); 2994 } else { 2995 this._runFrame(1); 2996 this._complete(); 2997 } 2998 }, 2999 3000 _runFrame: function (progress, round) { 3001 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 3002 if (round) { 3003 pos._round(); 3004 } 3005 setPosition(this._el, pos); 3006 3007 // @event step: Event 3008 // Fired continuously during the animation. 3009 this.fire('step'); 3010 }, 3011 3012 _complete: function () {
vendor: 13,656 bytes, lines 3013-3428
3013 cancelAnimFrame(this._animId); 3014 3015 this._inProgress = false; 3016 // @event end: Event 3017 // Fired when the animation ends. 3018 this.fire('end'); 3019 }, 3020 3021 _easeOut: function (t) { 3022 return 1 - Math.pow(1 - t, this._easeOutPower); 3023 } 3024}); 3025 3026/* 3027 * @class Map 3028 * @aka L.Map 3029 * @inherits Evented 3030 * 3031 * The central class of the API â it is used to create a map on a page and manipulate it. 3032 * 3033 * @example 3034 * 3035 * ```js 3036 * // initialize the map on the "map" div with a given center and zoom 3037 * var map = L.map('map', { 3038 * center: [51.505, -0.09], 3039 * zoom: 13 3040 * }); 3041 * ``` 3042 * 3043 */ 3044 3045var Map = Evented.extend({ 3046 3047 options: { 3048 // @section Map State Options 3049 // @option crs: CRS = L.CRS.EPSG3857 3050 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 3051 // sure what it means. 3052 crs: EPSG3857, 3053 3054 // @option center: LatLng = undefined 3055 // Initial geographic center of the map 3056 center: undefined, 3057 3058 // @option zoom: Number = undefined 3059 // Initial map zoom level 3060 zoom: undefined, 3061 3062 // @option minZoom: Number = * 3063 // Minimum zoom level of the map. 3064 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3065 // the lowest of their `minZoom` options will be used instead. 3066 minZoom: undefined, 3067 3068 // @option maxZoom: Number = * 3069 // Maximum zoom level of the map. 3070 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3071 // the highest of their `maxZoom` options will be used instead. 3072 maxZoom: undefined, 3073 3074 // @option layers: Layer[] = [] 3075 // Array of layers that will be added to the map initially 3076 layers: [], 3077 3078 // @option maxBounds: LatLngBounds = null 3079 // When this option is set, the map restricts the view to the given 3080 // geographical bounds, bouncing the user back if the user tries to pan 3081 // outside the view. To set the restriction dynamically, use 3082 // [`setMaxBounds`](#map-setmaxbounds) method. 3083 maxBounds: undefined, 3084 3085 // @option renderer: Renderer = * 3086 // The default method for drawing vector layers on the map. `L.SVG` 3087 // or `L.Canvas` by default depending on browser support. 3088 renderer: undefined, 3089 3090 3091 // @section Animation Options 3092 // @option zoomAnimation: Boolean = true 3093 // Whether the map zoom animation is enabled. By default it's enabled 3094 // in all browsers that support CSS3 Transitions except Android. 3095 zoomAnimation: true, 3096 3097 // @option zoomAnimationThreshold: Number = 4 3098 // Won't animate zoom if the zoom difference exceeds this value. 3099 zoomAnimationThreshold: 4, 3100 3101 // @option fadeAnimation: Boolean = true 3102 // Whether the tile fade animation is enabled. By default it's enabled 3103 // in all browsers that support CSS3 Transitions except Android. 3104 fadeAnimation: true, 3105 3106 // @option markerZoomAnimation: Boolean = true 3107 // Whether markers animate their zoom with the zoom animation, if disabled 3108 // they will disappear for the length of the animation. By default it's 3109 // enabled in all browsers that support CSS3 Transitions except Android. 3110 markerZoomAnimation: true, 3111 3112 // @option transform3DLimit: Number = 2^23 3113 // Defines the maximum size of a CSS translation transform. The default 3114 // value should not be changed unless a web browser positions layers in 3115 // the wrong place after doing a large `panBy`. 3116 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3117 3118 // @section Interaction Options 3119 // @option zoomSnap: Number = 1 3120 // Forces the map's zoom level to always be a multiple of this, particularly 3121 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3122 // By default, the zoom level snaps to the nearest integer; lower values 3123 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3124 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3125 zoomSnap: 1, 3126 3127 // @option zoomDelta: Number = 1 3128 // Controls how much the map's zoom level will change after a 3129 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3130 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3131 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3132 zoomDelta: 1, 3133 3134 // @option trackResize: Boolean = true 3135 // Whether the map automatically handles browser window resize to update itself. 3136 trackResize: true 3137 }, 3138 3139 initialize: function (id, options) { // (HTMLElement or String, Object) 3140 options = setOptions(this, options); 3141 3142 // Make sure to assign internal flags at the beginning, 3143 // to avoid inconsistent state in some edge cases. 3144 this._handlers = []; 3145 this._layers = {}; 3146 this._zoomBoundLayers = {}; 3147 this._sizeChanged = true; 3148 3149 this._initContainer(id); 3150 this._initLayout(); 3151 3152 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3153 this._onResize = bind(this._onResize, this); 3154 3155 this._initEvents(); 3156 3157 if (options.maxBounds) { 3158 this.setMaxBounds(options.maxBounds); 3159 } 3160 3161 if (options.zoom !== undefined) { 3162 this._zoom = this._limitZoom(options.zoom); 3163 } 3164 3165 if (options.center && options.zoom !== undefined) { 3166 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3167 } 3168 3169 this.callInitHooks(); 3170 3171 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3172 this._zoomAnimated = TRANSITION && any3d && !mobileOpera && 3173 this.options.zoomAnimation; 3174 3175 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3176 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3177 if (this._zoomAnimated) { 3178 this._createAnimProxy(); 3179 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3180 } 3181 3182 this._addLayers(this.options.layers); 3183 }, 3184 3185 3186 // @section Methods for modifying map state 3187 3188 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3189 // Sets the view of the map (geographical center and zoom) with the given 3190 // animation options. 3191 setView: function (center, zoom, options) { 3192 3193 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3194 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3195 options = options || {}; 3196 3197 this._stop(); 3198 3199 if (this._loaded && !options.reset && options !== true) { 3200 3201 if (options.animate !== undefined) { 3202 options.zoom = extend({animate: options.animate}, options.zoom); 3203 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3204 } 3205 3206 // try animating pan or zoom 3207 var moved = (this._zoom !== zoom) ? 3208 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3209 this._tryAnimatedPan(center, options.pan); 3210 3211 if (moved) { 3212 // prevent resize handler call, the view will refresh after animation anyway 3213 clearTimeout(this._sizeTimer); 3214 return this; 3215 } 3216 } 3217 3218 // animation didn't start, just reset the map view 3219 this._resetView(center, zoom); 3220 3221 return this; 3222 }, 3223 3224 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3225 // Sets the zoom of the map. 3226 setZoom: function (zoom, options) { 3227 if (!this._loaded) { 3228 this._zoom = zoom; 3229 return this; 3230 } 3231 return this.setView(this.getCenter(), zoom, {zoom: options}); 3232 }, 3233 3234 // @method zoomIn(delta?: Number, options?: Zoom options): this 3235 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3236 zoomIn: function (delta, options) { 3237 delta = delta || (any3d ? this.options.zoomDelta : 1); 3238 return this.setZoom(this._zoom + delta, options); 3239 }, 3240 3241 // @method zoomOut(delta?: Number, options?: Zoom options): this 3242 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3243 zoomOut: function (delta, options) { 3244 delta = delta || (any3d ? this.options.zoomDelta : 1); 3245 return this.setZoom(this._zoom - delta, options); 3246 }, 3247 3248 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3249 // Zooms the map while keeping a specified geographical point on the map 3250 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3251 // @alternative 3252 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3253 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3254 setZoomAround: function (latlng, zoom, options) { 3255 var scale = this.getZoomScale(zoom), 3256 viewHalf = this.getSize().divideBy(2), 3257 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3258 3259 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3260 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3261 3262 return this.setView(newCenter, zoom, {zoom: options}); 3263 }, 3264 3265 _getBoundsCenterZoom: function (bounds, options) { 3266 3267 options = options || {}; 3268 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3269 3270 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3271 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3272 3273 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3274 3275 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3276 3277 if (zoom === Infinity) { 3278 return { 3279 center: bounds.getCenter(), 3280 zoom: zoom 3281 }; 3282 } 3283 3284 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3285 3286 swPoint = this.project(bounds.getSouthWest(), zoom), 3287 nePoint = this.project(bounds.getNorthEast(), zoom), 3288 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3289 3290 return { 3291 center: center, 3292 zoom: zoom 3293 }; 3294 }, 3295 3296 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3297 // Sets a map view that contains the given geographical bounds with the 3298 // maximum zoom level possible. 3299 fitBounds: function (bounds, options) { 3300 3301 bounds = toLatLngBounds(bounds); 3302 3303 if (!bounds.isValid()) { 3304 throw new Error('Bounds are not valid.'); 3305 } 3306 3307 var target = this._getBoundsCenterZoom(bounds, options); 3308 return this.setView(target.center, target.zoom, options); 3309 }, 3310 3311 // @method fitWorld(options?: fitBounds options): this 3312 // Sets a map view that mostly contains the whole world with the maximum 3313 // zoom level possible. 3314 fitWorld: function (options) { 3315 return this.fitBounds([[-90, -180], [90, 180]], options); 3316 }, 3317 3318 // @method panTo(latlng: LatLng, options?: Pan options): this 3319 // Pans the map to a given center. 3320 panTo: function (center, options) { // (LatLng) 3321 return this.setView(center, this._zoom, {pan: options}); 3322 }, 3323 3324 // @method panBy(offset: Point, options?: Pan options): this 3325 // Pans the map by a given number of pixels (animated). 3326 panBy: function (offset, options) { 3327 offset = toPoint(offset).round(); 3328 options = options || {}; 3329 3330 if (!offset.x && !offset.y) { 3331 return this.fire('moveend'); 3332 } 3333 // If we pan too far, Chrome gets issues with tiles 3334 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3335 if (options.animate !== true && !this.getSize().contains(offset)) { 3336 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3337 return this; 3338 } 3339 3340 if (!this._panAnim) { 3341 this._panAnim = new PosAnimation(); 3342 3343 this._panAnim.on({ 3344 'step': this._onPanTransitionStep, 3345 'end': this._onPanTransitionEnd 3346 }, this); 3347 } 3348 3349 // don't fire movestart if animating inertia 3350 if (!options.noMoveStart) { 3351 this.fire('movestart'); 3352 } 3353 3354 // animate pan unless animate: false specified 3355 if (options.animate !== false) { 3356 addClass(this._mapPane, 'leaflet-pan-anim'); 3357 3358 var newPos = this._getMapPanePos().subtract(offset).round(); 3359 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3360 } else { 3361 this._rawPanBy(offset); 3362 this.fire('move').fire('moveend'); 3363 } 3364 3365 return this; 3366 }, 3367 3368 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3369 // Sets the view of the map (geographical center and zoom) performing a smooth 3370 // pan-zoom animation. 3371 flyTo: function (targetCenter, targetZoom, options) { 3372 3373 options = options || {}; 3374 if (options.animate === false || !any3d) { 3375 return this.setView(targetCenter, targetZoom, options); 3376 } 3377 3378 this._stop(); 3379 3380 var from = this.project(this.getCenter()), 3381 to = this.project(targetCenter), 3382 size = this.getSize(), 3383 startZoom = this._zoom; 3384 3385 targetCenter = toLatLng(targetCenter); 3386 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3387 3388 var w0 = Math.max(size.x, size.y), 3389 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3390 u1 = (to.distanceTo(from)) || 1, 3391 rho = 1.42, 3392 rho2 = rho * rho; 3393 3394 function r(i) { 3395 var s1 = i ? -1 : 1, 3396 s2 = i ? w1 : w0, 3397 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3398 b1 = 2 * s2 * rho2 * u1, 3399 b = t1 / b1, 3400 sq = Math.sqrt(b * b + 1) - b; 3401 3402 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3403 // thus triggering an infinite loop in flyTo 3404 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3405 3406 return log; 3407 } 3408 3409 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3410 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3411 function tanh(n) { return sinh(n) / cosh(n); } 3412 3413 var r0 = r(0); 3414 3415 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3416 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3417 3418 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3419 3420 var start = Date.now(), 3421 S = (r(1) - r0) / rho, 3422 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3423 3424 function frame() { 3425 var t = (Date.now() - start) / duration, 3426 s = easeOut(t) * S; 3427 3428 if (t <= 1) {
3429 this._flyToFrame = requestAnimFrame(frame, this); 3430 3431 this._move( 3432 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3433 this.getScaleZoom(w0 / w(s), startZoom), 3434 {flyTo: true}); 3435 3436 } else { 3437 this 3438 ._move(targetCenter, targetZoom) 3439 ._moveEnd(true); 3440 } 3441 } 3442 3443 this._moveStart(true, options.noMoveStart); 3444 3445 frame.call(this); 3446 return this; 3447 }, 3448 3449 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3450 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3451 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3452 flyToBounds: function (bounds, options) { 3453 var target = this._getBoundsCenterZoom(bounds, options); 3454 return this.flyTo(target.center, target.zoom, options); 3455 }, 3456 3457 // @method setMaxBounds(bounds: Bounds): this 3458 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3459 setMaxBounds: function (bounds) { 3460 bounds = toLatLngBounds(bounds); 3461 3462 if (!bounds.isValid()) { 3463 this.options.maxBounds = null; 3464 return this.off('moveend', this._panInsideMaxBounds); 3465 } else if (this.options.maxBounds) { 3466 this.off('moveend', this._panInsideMaxBounds); 3467 } 3468 3469 this.options.maxBounds = bounds; 3470 3471 if (this._loaded) { 3472 this._panInsideMaxBounds(); 3473 } 3474 3475 return this.on('moveend', this._panInsideMaxBounds); 3476 }, 3477 3478 // @method setMinZoom(zoom: Number): this 3479 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3480 setMinZoom: function (zoom) { 3481 var oldZoom = this.options.minZoom; 3482 this.options.minZoom = zoom; 3483 3484 if (this._loaded && oldZoom !== zoom) { 3485 this.fire('zoomlevelschange'); 3486 3487 if (this.getZoom() < this.options.minZoom) { 3488 return this.setZoom(zoom); 3489 } 3490 } 3491 3492 return this; 3493 }, 3494 3495 // @method setMaxZoom(zoom: Number): this 3496 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3497 setMaxZoom: function (zoom) { 3498 var oldZoom = this.options.maxZoom; 3499 this.options.maxZoom = zoom; 3500 3501 if (this._loaded && oldZoom !== zoom) { 3502 this.fire('zoomlevelschange'); 3503 3504 if (this.getZoom() > this.options.maxZoom) { 3505 return this.setZoom(zoom); 3506 } 3507 } 3508 3509 return this; 3510 }, 3511 3512 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3513 // 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. 3514 panInsideBounds: function (bounds, options) { 3515 this._enforcingBounds = true; 3516 var center = this.getCenter(), 3517 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3518 3519 if (!center.equals(newCenter)) { 3520 this.panTo(newCenter, options); 3521 } 3522 3523 this._enforcingBounds = false; 3524 return this; 3525 }, 3526 3527 // @method panInside(latlng: LatLng, options?: options): this 3528 // Pans the map the minimum amount to make the `latlng` visible. Use 3529 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit 3530 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds). 3531 // If `latlng` is already within the (optionally padded) display bounds, 3532 // the map will not be panned. 3533 panInside: function (latlng, options) { 3534 options = options || {}; 3535 3536 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3537 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3538 center = this.getCenter(), 3539 pixelCenter = this.project(center), 3540 pixelPoint = this.project(latlng), 3541 pixelBounds = this.getPixelBounds(), 3542 halfPixelBounds = pixelBounds.getSize().divideBy(2), 3543 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]); 3544 3545 if (!paddedBounds.contains(pixelPoint)) { 3546 this._enforcingBounds = true; 3547 var diff = pixelCenter.subtract(pixelPoint), 3548 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y); 3549 3550 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) { 3551 newCenter.x = pixelCenter.x - diff.x; 3552 if (diff.x > 0) { 3553 newCenter.x += halfPixelBounds.x - paddingTL.x; 3554 } else { 3555 newCenter.x -= halfPixelBounds.x - paddingBR.x; 3556 } 3557 } 3558 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) { 3559 newCenter.y = pixelCenter.y - diff.y; 3560 if (diff.y > 0) { 3561 newCenter.y += halfPixelBounds.y - paddingTL.y; 3562 } else { 3563 newCenter.y -= halfPixelBounds.y - paddingBR.y; 3564 } 3565 } 3566 this.panTo(this.unproject(newCenter), options); 3567 this._enforcingBounds = false;
vendor: 24,536 bytes, lines 3568-4328
3568 } 3569 return this; 3570 }, 3571 3572 // @method invalidateSize(options: Zoom/pan options): this 3573 // Checks if the map container size changed and updates the map if so â 3574 // call it after you've changed the map size dynamically, also animating 3575 // pan by default. If `options.pan` is `false`, panning will not occur. 3576 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3577 // that it doesn't happen often even if the method is called many 3578 // times in a row. 3579 3580 // @alternative 3581 // @method invalidateSize(animate: Boolean): this 3582 // Checks if the map container size changed and updates the map if so â 3583 // call it after you've changed the map size dynamically, also animating 3584 // pan by default. 3585 invalidateSize: function (options) { 3586 if (!this._loaded) { return this; } 3587 3588 options = extend({ 3589 animate: false, 3590 pan: true 3591 }, options === true ? {animate: true} : options); 3592 3593 var oldSize = this.getSize(); 3594 this._sizeChanged = true; 3595 this._lastCenter = null; 3596 3597 var newSize = this.getSize(), 3598 oldCenter = oldSize.divideBy(2).round(), 3599 newCenter = newSize.divideBy(2).round(), 3600 offset = oldCenter.subtract(newCenter); 3601 3602 if (!offset.x && !offset.y) { return this; } 3603 3604 if (options.animate && options.pan) { 3605 this.panBy(offset); 3606 3607 } else { 3608 if (options.pan) { 3609 this._rawPanBy(offset); 3610 } 3611 3612 this.fire('move'); 3613 3614 if (options.debounceMoveend) { 3615 clearTimeout(this._sizeTimer); 3616 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3617 } else { 3618 this.fire('moveend'); 3619 } 3620 } 3621 3622 // @section Map state change events 3623 // @event resize: ResizeEvent 3624 // Fired when the map is resized. 3625 return this.fire('resize', { 3626 oldSize: oldSize, 3627 newSize: newSize 3628 }); 3629 }, 3630 3631 // @section Methods for modifying map state 3632 // @method stop(): this 3633 // Stops the currently running `panTo` or `flyTo` animation, if any. 3634 stop: function () { 3635 this.setZoom(this._limitZoom(this._zoom)); 3636 if (!this.options.zoomSnap) { 3637 this.fire('viewreset'); 3638 } 3639 return this._stop(); 3640 }, 3641 3642 // @section Geolocation methods 3643 // @method locate(options?: Locate options): this 3644 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3645 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3646 // and optionally sets the map view to the user's location with respect to 3647 // detection accuracy (or to the world view if geolocation failed). 3648 // Note that, if your page doesn't use HTTPS, this method will fail in 3649 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3650 // See `Locate options` for more details. 3651 locate: function (options) { 3652 3653 options = this._locateOptions = extend({ 3654 timeout: 10000, 3655 watch: false 3656 // setView: false 3657 // maxZoom: <Number> 3658 // maximumAge: 0 3659 // enableHighAccuracy: false 3660 }, options); 3661 3662 if (!('geolocation' in navigator)) { 3663 this._handleGeolocationError({ 3664 code: 0, 3665 message: 'Geolocation not supported.' 3666 }); 3667 return this; 3668 } 3669 3670 var onResponse = bind(this._handleGeolocationResponse, this), 3671 onError = bind(this._handleGeolocationError, this); 3672 3673 if (options.watch) { 3674 this._locationWatchId = 3675 navigator.geolocation.watchPosition(onResponse, onError, options); 3676 } else { 3677 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 3678 } 3679 return this; 3680 }, 3681 3682 // @method stopLocate(): this 3683 // Stops watching location previously initiated by `map.locate({watch: true})` 3684 // and aborts resetting the map view if map.locate was called with 3685 // `{setView: true}`. 3686 stopLocate: function () { 3687 if (navigator.geolocation && navigator.geolocation.clearWatch) { 3688 navigator.geolocation.clearWatch(this._locationWatchId); 3689 } 3690 if (this._locateOptions) { 3691 this._locateOptions.setView = false; 3692 } 3693 return this; 3694 }, 3695 3696 _handleGeolocationError: function (error) { 3697 var c = error.code, 3698 message = error.message || 3699 (c === 1 ? 'permission denied' : 3700 (c === 2 ? 'position unavailable' : 'timeout')); 3701 3702 if (this._locateOptions.setView && !this._loaded) { 3703 this.fitWorld(); 3704 } 3705 3706 // @section Location events 3707 // @event locationerror: ErrorEvent 3708 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 3709 this.fire('locationerror', { 3710 code: c, 3711 message: 'Geolocation error: ' + message + '.' 3712 }); 3713 }, 3714 3715 _handleGeolocationResponse: function (pos) { 3716 var lat = pos.coords.latitude, 3717 lng = pos.coords.longitude, 3718 latlng = new LatLng(lat, lng), 3719 bounds = latlng.toBounds(pos.coords.accuracy * 2), 3720 options = this._locateOptions; 3721 3722 if (options.setView) { 3723 var zoom = this.getBoundsZoom(bounds); 3724 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 3725 } 3726 3727 var data = { 3728 latlng: latlng, 3729 bounds: bounds, 3730 timestamp: pos.timestamp 3731 }; 3732 3733 for (var i in pos.coords) { 3734 if (typeof pos.coords[i] === 'number') { 3735 data[i] = pos.coords[i]; 3736 } 3737 } 3738 3739 // @event locationfound: LocationEvent 3740 // Fired when geolocation (using the [`locate`](#map-locate) method) 3741 // went successfully. 3742 this.fire('locationfound', data); 3743 }, 3744 3745 // TODO Appropriate docs section? 3746 // @section Other Methods 3747 // @method addHandler(name: String, HandlerClass: Function): this 3748 // Adds a new `Handler` to the map, given its name and constructor function. 3749 addHandler: function (name, HandlerClass) { 3750 if (!HandlerClass) { return this; } 3751 3752 var handler = this[name] = new HandlerClass(this); 3753 3754 this._handlers.push(handler); 3755 3756 if (this.options[name]) { 3757 handler.enable(); 3758 } 3759 3760 return this; 3761 }, 3762 3763 // @method remove(): this 3764 // Destroys the map and clears all related event listeners. 3765 remove: function () { 3766 3767 this._initEvents(true); 3768 3769 if (this._containerId !== this._container._leaflet_id) { 3770 throw new Error('Map container is being reused by another instance'); 3771 } 3772 3773 try { 3774 // throws error in IE6-8 3775 delete this._container._leaflet_id; 3776 delete this._containerId; 3777 } catch (e) { 3778 /*eslint-disable */ 3779 this._container._leaflet_id = undefined; 3780 /* eslint-enable */ 3781 this._containerId = undefined; 3782 } 3783 3784 if (this._locationWatchId !== undefined) { 3785 this.stopLocate(); 3786 } 3787 3788 this._stop(); 3789 3790 remove(this._mapPane); 3791 3792 if (this._clearControlPos) { 3793 this._clearControlPos(); 3794 } 3795 if (this._resizeRequest) { 3796 cancelAnimFrame(this._resizeRequest); 3797 this._resizeRequest = null; 3798 } 3799 3800 this._clearHandlers(); 3801 3802 if (this._loaded) { 3803 // @section Map state change events 3804 // @event unload: Event 3805 // Fired when the map is destroyed with [remove](#map-remove) method. 3806 this.fire('unload'); 3807 } 3808 3809 var i; 3810 for (i in this._layers) { 3811 this._layers[i].remove(); 3812 } 3813 for (i in this._panes) { 3814 remove(this._panes[i]); 3815 } 3816 3817 this._layers = []; 3818 this._panes = []; 3819 delete this._mapPane; 3820 delete this._renderer; 3821 3822 return this; 3823 }, 3824 3825 // @section Other Methods 3826 // @method createPane(name: String, container?: HTMLElement): HTMLElement 3827 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 3828 // then returns it. The pane is created as a child of `container`, or 3829 // as a child of the main map pane if not set. 3830 createPane: function (name, container) { 3831 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3832 pane = create$1('div', className, container || this._mapPane); 3833 3834 if (name) { 3835 this._panes[name] = pane; 3836 } 3837 return pane; 3838 }, 3839 3840 // @section Methods for Getting Map State 3841 3842 // @method getCenter(): LatLng 3843 // Returns the geographical center of the map view 3844 getCenter: function () { 3845 this._checkIfLoaded(); 3846 3847 if (this._lastCenter && !this._moved()) { 3848 return this._lastCenter; 3849 } 3850 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3851 }, 3852 3853 // @method getZoom(): Number 3854 // Returns the current zoom level of the map view 3855 getZoom: function () { 3856 return this._zoom; 3857 }, 3858 3859 // @method getBounds(): LatLngBounds 3860 // Returns the geographical bounds visible in the current map view 3861 getBounds: function () { 3862 var bounds = this.getPixelBounds(), 3863 sw = this.unproject(bounds.getBottomLeft()), 3864 ne = this.unproject(bounds.getTopRight()); 3865 3866 return new LatLngBounds(sw, ne); 3867 }, 3868 3869 // @method getMinZoom(): Number 3870 // 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. 3871 getMinZoom: function () { 3872 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3873 }, 3874 3875 // @method getMaxZoom(): Number 3876 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3877 getMaxZoom: function () { 3878 return this.options.maxZoom === undefined ? 3879 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3880 this.options.maxZoom; 3881 }, 3882 3883 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 3884 // Returns the maximum zoom level on which the given bounds fit to the map 3885 // view in its entirety. If `inside` (optional) is set to `true`, the method 3886 // instead returns the minimum zoom level on which the map view fits into 3887 // the given bounds in its entirety. 3888 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3889 bounds = toLatLngBounds(bounds); 3890 padding = toPoint(padding || [0, 0]); 3891 3892 var zoom = this.getZoom() || 0, 3893 min = this.getMinZoom(), 3894 max = this.getMaxZoom(), 3895 nw = bounds.getNorthWest(), 3896 se = bounds.getSouthEast(), 3897 size = this.getSize().subtract(padding), 3898 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3899 snap = any3d ? this.options.zoomSnap : 1, 3900 scalex = size.x / boundsSize.x, 3901 scaley = size.y / boundsSize.y, 3902 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 3903 3904 zoom = this.getScaleZoom(scale, zoom); 3905 3906 if (snap) { 3907 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3908 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3909 } 3910 3911 return Math.max(min, Math.min(max, zoom)); 3912 }, 3913 3914 // @method getSize(): Point 3915 // Returns the current size of the map container (in pixels). 3916 getSize: function () { 3917 if (!this._size || this._sizeChanged) { 3918 this._size = new Point( 3919 this._container.clientWidth || 0, 3920 this._container.clientHeight || 0); 3921 3922 this._sizeChanged = false; 3923 } 3924 return this._size.clone(); 3925 }, 3926 3927 // @method getPixelBounds(): Bounds 3928 // Returns the bounds of the current map view in projected pixel 3929 // coordinates (sometimes useful in layer and overlay implementations). 3930 getPixelBounds: function (center, zoom) { 3931 var topLeftPoint = this._getTopLeftPoint(center, zoom); 3932 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 3933 }, 3934 3935 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 3936 // the map pane? "left point of the map layer" can be confusing, specially 3937 // since there can be negative offsets. 3938 // @method getPixelOrigin(): Point 3939 // Returns the projected pixel coordinates of the top left point of 3940 // the map layer (useful in custom layer and overlay implementations). 3941 getPixelOrigin: function () { 3942 this._checkIfLoaded(); 3943 return this._pixelOrigin; 3944 }, 3945 3946 // @method getPixelWorldBounds(zoom?: Number): Bounds 3947 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 3948 // If `zoom` is omitted, the map's current zoom level is used. 3949 getPixelWorldBounds: function (zoom) { 3950 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 3951 }, 3952 3953 // @section Other Methods 3954 3955 // @method getPane(pane: String|HTMLElement): HTMLElement 3956 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 3957 getPane: function (pane) { 3958 return typeof pane === 'string' ? this._panes[pane] : pane; 3959 }, 3960 3961 // @method getPanes(): Object 3962 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 3963 // the panes as values. 3964 getPanes: function () { 3965 return this._panes; 3966 }, 3967 3968 // @method getContainer: HTMLElement 3969 // Returns the HTML element that contains the map. 3970 getContainer: function () { 3971 return this._container; 3972 }, 3973 3974 3975 // @section Conversion Methods 3976 3977 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 3978 // Returns the scale factor to be applied to a map transition from zoom level 3979 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 3980 getZoomScale: function (toZoom, fromZoom) { 3981 // TODO replace with universal implementation after refactoring projections 3982 var crs = this.options.crs; 3983 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3984 return crs.scale(toZoom) / crs.scale(fromZoom); 3985 }, 3986 3987 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 3988 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 3989 // level and everything is scaled by a factor of `scale`. Inverse of 3990 // [`getZoomScale`](#map-getZoomScale). 3991 getScaleZoom: function (scale, fromZoom) { 3992 var crs = this.options.crs; 3993 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3994 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 3995 return isNaN(zoom) ? Infinity : zoom; 3996 }, 3997 3998 // @method project(latlng: LatLng, zoom: Number): Point 3999 // Projects a geographical coordinate `LatLng` according to the projection 4000 // of the map's CRS, then scales it according to `zoom` and the CRS's 4001 // `Transformation`. The result is pixel coordinate relative to 4002 // the CRS origin. 4003 project: function (latlng, zoom) { 4004 zoom = zoom === undefined ? this._zoom : zoom; 4005 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 4006 }, 4007 4008 // @method unproject(point: Point, zoom: Number): LatLng 4009 // Inverse of [`project`](#map-project). 4010 unproject: function (point, zoom) { 4011 zoom = zoom === undefined ? this._zoom : zoom; 4012 return this.options.crs.pointToLatLng(toPoint(point), zoom); 4013 }, 4014 4015 // @method layerPointToLatLng(point: Point): LatLng 4016 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4017 // returns the corresponding geographical coordinate (for the current zoom level). 4018 layerPointToLatLng: function (point) { 4019 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 4020 return this.unproject(projectedPoint); 4021 }, 4022 4023 // @method latLngToLayerPoint(latlng: LatLng): Point 4024 // Given a geographical coordinate, returns the corresponding pixel coordinate 4025 // relative to the [origin pixel](#map-getpixelorigin). 4026 latLngToLayerPoint: function (latlng) { 4027 var projectedPoint = this.project(toLatLng(latlng))._round(); 4028 return projectedPoint._subtract(this.getPixelOrigin()); 4029 }, 4030 4031 // @method wrapLatLng(latlng: LatLng): LatLng 4032 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 4033 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 4034 // CRS's bounds. 4035 // By default this means longitude is wrapped around the dateline so its 4036 // value is between -180 and +180 degrees. 4037 wrapLatLng: function (latlng) { 4038 return this.options.crs.wrapLatLng(toLatLng(latlng)); 4039 }, 4040 4041 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 4042 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 4043 // its center is within the CRS's bounds. 4044 // By default this means the center longitude is wrapped around the dateline so its 4045 // value is between -180 and +180 degrees, and the majority of the bounds 4046 // overlaps the CRS's bounds. 4047 wrapLatLngBounds: function (latlng) { 4048 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 4049 }, 4050 4051 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 4052 // Returns the distance between two geographical coordinates according to 4053 // the map's CRS. By default this measures distance in meters. 4054 distance: function (latlng1, latlng2) { 4055 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 4056 }, 4057 4058 // @method containerPointToLayerPoint(point: Point): Point 4059 // Given a pixel coordinate relative to the map container, returns the corresponding 4060 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 4061 containerPointToLayerPoint: function (point) { // (Point) 4062 return toPoint(point).subtract(this._getMapPanePos()); 4063 }, 4064 4065 // @method layerPointToContainerPoint(point: Point): Point 4066 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4067 // returns the corresponding pixel coordinate relative to the map container. 4068 layerPointToContainerPoint: function (point) { // (Point) 4069 return toPoint(point).add(this._getMapPanePos()); 4070 }, 4071 4072 // @method containerPointToLatLng(point: Point): LatLng 4073 // Given a pixel coordinate relative to the map container, returns 4074 // the corresponding geographical coordinate (for the current zoom level). 4075 containerPointToLatLng: function (point) { 4076 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4077 return this.layerPointToLatLng(layerPoint); 4078 }, 4079 4080 // @method latLngToContainerPoint(latlng: LatLng): Point 4081 // Given a geographical coordinate, returns the corresponding pixel coordinate 4082 // relative to the map container. 4083 latLngToContainerPoint: function (latlng) { 4084 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4085 }, 4086 4087 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4088 // Given a MouseEvent object, returns the pixel coordinate relative to the 4089 // map container where the event took place. 4090 mouseEventToContainerPoint: function (e) { 4091 return getMousePosition(e, this._container); 4092 }, 4093 4094 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4095 // Given a MouseEvent object, returns the pixel coordinate relative to 4096 // the [origin pixel](#map-getpixelorigin) where the event took place. 4097 mouseEventToLayerPoint: function (e) { 4098 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4099 }, 4100 4101 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4102 // Given a MouseEvent object, returns geographical coordinate where the 4103 // event took place. 4104 mouseEventToLatLng: function (e) { // (MouseEvent) 4105 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4106 }, 4107 4108 4109 // map initialization methods 4110 4111 _initContainer: function (id) { 4112 var container = this._container = get(id); 4113 4114 if (!container) { 4115 throw new Error('Map container not found.'); 4116 } else if (container._leaflet_id) { 4117 throw new Error('Map container is already initialized.'); 4118 } 4119 4120 on(container, 'scroll', this._onScroll, this); 4121 this._containerId = stamp(container); 4122 }, 4123 4124 _initLayout: function () { 4125 var container = this._container; 4126 4127 this._fadeAnimated = this.options.fadeAnimation && any3d; 4128 4129 addClass(container, 'leaflet-container' + 4130 (touch ? ' leaflet-touch' : '') + 4131 (retina ? ' leaflet-retina' : '') + 4132 (ielt9 ? ' leaflet-oldie' : '') + 4133 (safari ? ' leaflet-safari' : '') + 4134 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4135 4136 var position = getStyle(container, 'position'); 4137 4138 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { 4139 container.style.position = 'relative'; 4140 } 4141 4142 this._initPanes(); 4143 4144 if (this._initControlPos) { 4145 this._initControlPos(); 4146 } 4147 }, 4148 4149 _initPanes: function () { 4150 var panes = this._panes = {}; 4151 this._paneRenderers = {}; 4152 4153 // @section 4154 // 4155 // Panes are DOM elements used to control the ordering of layers on the map. You 4156 // can access panes with [`map.getPane`](#map-getpane) or 4157 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4158 // [`map.createPane`](#map-createpane) method. 4159 // 4160 // Every map has the following default panes that differ only in zIndex. 4161 // 4162 // @pane mapPane: HTMLElement = 'auto' 4163 // Pane that contains all other map panes 4164 4165 this._mapPane = this.createPane('mapPane', this._container); 4166 setPosition(this._mapPane, new Point(0, 0)); 4167 4168 // @pane tilePane: HTMLElement = 200 4169 // Pane for `GridLayer`s and `TileLayer`s 4170 this.createPane('tilePane'); 4171 // @pane overlayPane: HTMLElement = 400 4172 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4173 this.createPane('shadowPane'); 4174 // @pane shadowPane: HTMLElement = 500 4175 // Pane for overlay shadows (e.g. `Marker` shadows) 4176 this.createPane('overlayPane'); 4177 // @pane markerPane: HTMLElement = 600 4178 // Pane for `Icon`s of `Marker`s 4179 this.createPane('markerPane'); 4180 // @pane tooltipPane: HTMLElement = 650 4181 // Pane for `Tooltip`s. 4182 this.createPane('tooltipPane'); 4183 // @pane popupPane: HTMLElement = 700 4184 // Pane for `Popup`s. 4185 this.createPane('popupPane'); 4186 4187 if (!this.options.markerZoomAnimation) { 4188 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4189 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4190 } 4191 }, 4192 4193 4194 // private methods that modify map state 4195 4196 // @section Map state change events 4197 _resetView: function (center, zoom) { 4198 setPosition(this._mapPane, new Point(0, 0)); 4199 4200 var loading = !this._loaded; 4201 this._loaded = true; 4202 zoom = this._limitZoom(zoom); 4203 4204 this.fire('viewprereset'); 4205 4206 var zoomChanged = this._zoom !== zoom; 4207 this 4208 ._moveStart(zoomChanged, false) 4209 ._move(center, zoom) 4210 ._moveEnd(zoomChanged); 4211 4212 // @event viewreset: Event 4213 // Fired when the map needs to redraw its content (this usually happens 4214 // on map zoom or load). Very useful for creating custom overlays. 4215 this.fire('viewreset'); 4216 4217 // @event load: Event 4218 // Fired when the map is initialized (when its center and zoom are set 4219 // for the first time). 4220 if (loading) { 4221 this.fire('load'); 4222 } 4223 }, 4224 4225 _moveStart: function (zoomChanged, noMoveStart) { 4226 // @event zoomstart: Event 4227 // Fired when the map zoom is about to change (e.g. before zoom animation). 4228 // @event movestart: Event 4229 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4230 if (zoomChanged) { 4231 this.fire('zoomstart'); 4232 } 4233 if (!noMoveStart) { 4234 this.fire('movestart'); 4235 } 4236 return this; 4237 }, 4238 4239 _move: function (center, zoom, data) { 4240 if (zoom === undefined) { 4241 zoom = this._zoom; 4242 } 4243 var zoomChanged = this._zoom !== zoom; 4244 4245 this._zoom = zoom; 4246 this._lastCenter = center; 4247 this._pixelOrigin = this._getNewPixelOrigin(center); 4248 4249 // @event zoom: Event 4250 // Fired repeatedly during any change in zoom level, including zoom 4251 // and fly animations. 4252 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4253 this.fire('zoom', data); 4254 } 4255 4256 // @event move: Event 4257 // Fired repeatedly during any movement of the map, including pan and 4258 // fly animations. 4259 return this.fire('move', data); 4260 }, 4261 4262 _moveEnd: function (zoomChanged) { 4263 // @event zoomend: Event 4264 // Fired when the map has changed, after any animations. 4265 if (zoomChanged) { 4266 this.fire('zoomend'); 4267 } 4268 4269 // @event moveend: Event 4270 // Fired when the center of the map stops changing (e.g. user stopped 4271 // dragging the map). 4272 return this.fire('moveend'); 4273 }, 4274 4275 _stop: function () { 4276 cancelAnimFrame(this._flyToFrame); 4277 if (this._panAnim) { 4278 this._panAnim.stop(); 4279 } 4280 return this; 4281 }, 4282 4283 _rawPanBy: function (offset) { 4284 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4285 }, 4286 4287 _getZoomSpan: function () { 4288 return this.getMaxZoom() - this.getMinZoom(); 4289 }, 4290 4291 _panInsideMaxBounds: function () { 4292 if (!this._enforcingBounds) { 4293 this.panInsideBounds(this.options.maxBounds); 4294 } 4295 }, 4296 4297 _checkIfLoaded: function () { 4298 if (!this._loaded) { 4299 throw new Error('Set map center and zoom first.'); 4300 } 4301 }, 4302 4303 // DOM event handling 4304 4305 // @section Interaction events 4306 _initEvents: function (remove$$1) { 4307 this._targets = {}; 4308 this._targets[stamp(this._container)] = this; 4309 4310 var onOff = remove$$1 ? off : on; 4311 4312 // @event click: MouseEvent 4313 // Fired when the user clicks (or taps) the map. 4314 // @event dblclick: MouseEvent 4315 // Fired when the user double-clicks (or double-taps) the map. 4316 // @event mousedown: MouseEvent 4317 // Fired when the user pushes the mouse button on the map. 4318 // @event mouseup: MouseEvent 4319 // Fired when the user releases the mouse button on the map. 4320 // @event mouseover: MouseEvent 4321 // Fired when the mouse enters the map. 4322 // @event mouseout: MouseEvent 4323 // Fired when the mouse leaves the map. 4324 // @event mousemove: MouseEvent 4325 // Fired while the mouse moves over the map. 4326 // @event contextmenu: MouseEvent 4327 // Fired when the user pushes the right mouse button on the map, prevents 4328 // default browser context menu from showing if there are listeners on
4329 // this event. Also fired on mobile when the user holds a single touch 4330 // for a second (also called long press). 4331 // @event keypress: KeyboardEvent 4332 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused. 4333 // @event keydown: KeyboardEvent 4334 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event, 4335 // the `keydown` event is fired for keys that produce a character value and for keys 4336 // that do not produce a character value. 4337 // @event keyup: KeyboardEvent 4338 // Fired when the user releases a key from the keyboard while the map is focused. 4339 onOff(this._container, 'click dblclick mousedown mouseup ' + 4340 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this); 4341 4342 if (this.options.trackResize) { 4343 onOff(window, 'resize', this._onResize, this); 4344 } 4345 4346 if (any3d && this.options.transform3DLimit) { 4347 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4348 } 4349 }, 4350 4351 _onResize: function () { 4352 cancelAnimFrame(this._resizeRequest); 4353 this._resizeRequest = requestAnimFrame( 4354 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4355 }, 4356 4357 _onScroll: function () { 4358 this._container.scrollTop = 0; 4359 this._container.scrollLeft = 0; 4360 }, 4361 4362 _onMoveEnd: function () { 4363 var pos = this._getMapPanePos(); 4364 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4365 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4366 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ 4367 this._resetView(this.getCenter(), this.getZoom()); 4368 } 4369 }, 4370 4371 _findEventTargets: function (e, type) { 4372 var targets = [], 4373 target, 4374 isHover = type === 'mouseout' || type === 'mouseover', 4375 src = e.target || e.srcElement, 4376 dragging = false; 4377 4378 while (src) { 4379 target = this._targets[stamp(src)]; 4380 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { 4381 // Prevent firing click after you just dragged an object. 4382 dragging = true; 4383 break; 4384 } 4385 if (target && target.listens(type, true)) { 4386 if (isHover && !isExternalTarget(src, e)) { break; } 4387 targets.push(target); 4388 if (isHover) { break; } 4389 } 4390 if (src === this._container) { break; } 4391 src = src.parentNode; 4392 } 4393 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) { 4394 targets = [this]; 4395 } 4396 return targets; 4397 }, 4398 4399 _handleDOMEvent: function (e) { 4400 if (!this._loaded || skipped(e)) { return; } 4401 4402 var type = e.type; 4403 4404 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') { 4405 // prevents outline when clicking on keyboard-focusable element 4406 preventOutline(e.target || e.srcElement); 4407 } 4408 4409 this._fireDOMEvent(e, type); 4410 }, 4411 4412 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4413 4414 _fireDOMEvent: function (e, type, targets) { 4415 4416 if (e.type === 'click') { 4417 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4418 // @event preclick: MouseEvent 4419 // Fired before mouse click on the map (sometimes useful when you 4420 // want something to happen on click before any existing click 4421 // handlers start running). 4422 var synth = extend({}, e); 4423 synth.type = 'preclick'; 4424 this._fireDOMEvent(synth, synth.type, targets); 4425 } 4426 4427 if (e._stopped) { return; } 4428 4429 // Find the layer the event is propagating from and its parents. 4430 targets = (targets || []).concat(this._findEventTargets(e, type)); 4431 4432 if (!targets.length) { return; } 4433 4434 var target = targets[0]; 4435 if (type === 'contextmenu' && target.listens(type, true)) { 4436 preventDefault(e); 4437 } 4438 4439 var data = { 4440 originalEvent: e 4441 }; 4442 4443 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') { 4444 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4445 data.containerPoint = isMarker ? 4446 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4447 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4448 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4449 } 4450 4451 for (var i = 0; i < targets.length; i++) { 4452 targets[i].fire(type, data, true); 4453 if (data.originalEvent._stopped || 4454 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4455 } 4456 }, 4457 4458 _draggableMoved: function (obj) { 4459 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4460 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4461 }, 4462 4463 _clearHandlers: function () { 4464 for (var i = 0, len = this._handlers.length; i < len; i++) { 4465 this._handlers[i].disable(); 4466 } 4467 }, 4468 4469 // @section Other Methods 4470 4471 // @method whenReady(fn: Function, context?: Object): this 4472 // Runs the given function `fn` when the map gets initialized with 4473 // a view (center and zoom) and at least one layer, or immediately 4474 // if it's already initialized, optionally passing a function context. 4475 whenReady: function (callback, context) { 4476 if (this._loaded) { 4477 callback.call(context || this, {target: this}); 4478 } else { 4479 this.on('load', callback, context); 4480 } 4481 return this; 4482 }, 4483 4484 4485 // private methods for getting map state 4486 4487 _getMapPanePos: function () { 4488 return getPosition(this._mapPane) || new Point(0, 0); 4489 }, 4490 4491 _moved: function () { 4492 var pos = this._getMapPanePos(); 4493 return pos && !pos.equals([0, 0]); 4494 }, 4495 4496 _getTopLeftPoint: function (center, zoom) { 4497 var pixelOrigin = center && zoom !== undefined ? 4498 this._getNewPixelOrigin(center, zoom) : 4499 this.getPixelOrigin(); 4500 return pixelOrigin.subtract(this._getMapPanePos()); 4501 }, 4502 4503 _getNewPixelOrigin: function (center, zoom) {
vendor: 34,571 bytes, lines 4504-5765
4504 var viewHalf = this.getSize()._divideBy(2); 4505 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4506 }, 4507 4508 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4509 var topLeft = this._getNewPixelOrigin(center, zoom); 4510 return this.project(latlng, zoom)._subtract(topLeft); 4511 }, 4512 4513 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4514 var topLeft = this._getNewPixelOrigin(center, zoom); 4515 return toBounds([ 4516 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4517 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4518 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4519 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4520 ]); 4521 }, 4522 4523 // layer point of the current center 4524 _getCenterLayerPoint: function () { 4525 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4526 }, 4527 4528 // offset of the specified place to the current center in pixels 4529 _getCenterOffset: function (latlng) { 4530 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4531 }, 4532 4533 // adjust center for view to get inside bounds 4534 _limitCenter: function (center, zoom, bounds) { 4535 4536 if (!bounds) { return center; } 4537 4538 var centerPoint = this.project(center, zoom), 4539 viewHalf = this.getSize().divideBy(2), 4540 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4541 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4542 4543 // If offset is less than a pixel, ignore. 4544 // This prevents unstable projections from getting into 4545 // an infinite loop of tiny offsets. 4546 if (offset.round().equals([0, 0])) { 4547 return center; 4548 } 4549 4550 return this.unproject(centerPoint.add(offset), zoom); 4551 }, 4552 4553 // adjust offset for view to get inside bounds 4554 _limitOffset: function (offset, bounds) { 4555 if (!bounds) { return offset; } 4556 4557 var viewBounds = this.getPixelBounds(), 4558 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4559 4560 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4561 }, 4562 4563 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4564 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4565 var projectedMaxBounds = toBounds( 4566 this.project(maxBounds.getNorthEast(), zoom), 4567 this.project(maxBounds.getSouthWest(), zoom) 4568 ), 4569 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4570 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4571 4572 dx = this._rebound(minOffset.x, -maxOffset.x), 4573 dy = this._rebound(minOffset.y, -maxOffset.y); 4574 4575 return new Point(dx, dy); 4576 }, 4577 4578 _rebound: function (left, right) { 4579 return left + right > 0 ? 4580 Math.round(left - right) / 2 : 4581 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4582 }, 4583 4584 _limitZoom: function (zoom) { 4585 var min = this.getMinZoom(), 4586 max = this.getMaxZoom(), 4587 snap = any3d ? this.options.zoomSnap : 1; 4588 if (snap) { 4589 zoom = Math.round(zoom / snap) * snap; 4590 } 4591 return Math.max(min, Math.min(max, zoom)); 4592 }, 4593 4594 _onPanTransitionStep: function () { 4595 this.fire('move'); 4596 }, 4597 4598 _onPanTransitionEnd: function () { 4599 removeClass(this._mapPane, 'leaflet-pan-anim'); 4600 this.fire('moveend'); 4601 }, 4602 4603 _tryAnimatedPan: function (center, options) { 4604 // difference between the new and current centers in pixels 4605 var offset = this._getCenterOffset(center)._trunc(); 4606 4607 // don't animate too far unless animate: true specified in options 4608 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4609 4610 this.panBy(offset, options); 4611 4612 return true; 4613 }, 4614 4615 _createAnimProxy: function () { 4616 4617 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4618 this._panes.mapPane.appendChild(proxy); 4619 4620 this.on('zoomanim', function (e) { 4621 var prop = TRANSFORM, 4622 transform = this._proxy.style[prop]; 4623 4624 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4625 4626 // workaround for case when transform is the same and so transitionend event is not fired 4627 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4628 this._onZoomTransitionEnd(); 4629 } 4630 }, this); 4631 4632 this.on('load moveend', this._animMoveEnd, this); 4633 4634 this._on('unload', this._destroyAnimProxy, this); 4635 }, 4636 4637 _destroyAnimProxy: function () { 4638 remove(this._proxy); 4639 this.off('load moveend', this._animMoveEnd, this); 4640 delete this._proxy; 4641 }, 4642 4643 _animMoveEnd: function () { 4644 var c = this.getCenter(), 4645 z = this.getZoom(); 4646 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 4647 }, 4648 4649 _catchTransitionEnd: function (e) { 4650 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 4651 this._onZoomTransitionEnd(); 4652 } 4653 }, 4654 4655 _nothingToAnimate: function () { 4656 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 4657 }, 4658 4659 _tryAnimatedZoom: function (center, zoom, options) { 4660 4661 if (this._animatingZoom) { return true; } 4662 4663 options = options || {}; 4664 4665 // don't animate if disabled, not supported or zoom difference is too large 4666 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 4667 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 4668 4669 // offset is the pixel coords of the zoom origin relative to the current center 4670 var scale = this.getZoomScale(zoom), 4671 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 4672 4673 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 4674 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 4675 4676 requestAnimFrame(function () { 4677 this 4678 ._moveStart(true, false) 4679 ._animateZoom(center, zoom, true); 4680 }, this); 4681 4682 return true; 4683 }, 4684 4685 _animateZoom: function (center, zoom, startAnim, noUpdate) { 4686 if (!this._mapPane) { return; } 4687 4688 if (startAnim) { 4689 this._animatingZoom = true; 4690 4691 // remember what center/zoom to set after animation 4692 this._animateToCenter = center; 4693 this._animateToZoom = zoom; 4694 4695 addClass(this._mapPane, 'leaflet-zoom-anim'); 4696 } 4697 4698 // @section Other Events 4699 // @event zoomanim: ZoomAnimEvent 4700 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom. 4701 this.fire('zoomanim', { 4702 center: center, 4703 zoom: zoom, 4704 noUpdate: noUpdate 4705 }); 4706 4707 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 4708 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 4709 }, 4710 4711 _onZoomTransitionEnd: function () { 4712 if (!this._animatingZoom) { return; } 4713 4714 if (this._mapPane) { 4715 removeClass(this._mapPane, 'leaflet-zoom-anim'); 4716 } 4717 4718 this._animatingZoom = false; 4719 4720 this._move(this._animateToCenter, this._animateToZoom); 4721 4722 // This anim frame should prevent an obscure iOS webkit tile loading race condition. 4723 requestAnimFrame(function () { 4724 this._moveEnd(true); 4725 }, this); 4726 } 4727}); 4728 4729// @section 4730 4731// @factory L.map(id: String, options?: Map options) 4732// Instantiates a map object given the DOM ID of a `<div>` element 4733// and optionally an object literal with `Map options`. 4734// 4735// @alternative 4736// @factory L.map(el: HTMLElement, options?: Map options) 4737// Instantiates a map object given an instance of a `<div>` HTML element 4738// and optionally an object literal with `Map options`. 4739function createMap(id, options) { 4740 return new Map(id, options); 4741} 4742 4743/* 4744 * @class Control 4745 * @aka L.Control 4746 * @inherits Class 4747 * 4748 * L.Control is a base class for implementing map controls. Handles positioning. 4749 * All other controls extend from this class. 4750 */ 4751 4752var Control = Class.extend({ 4753 // @section 4754 // @aka Control options 4755 options: { 4756 // @option position: String = 'topright' 4757 // The position of the control (one of the map corners). Possible values are `'topleft'`, 4758 // `'topright'`, `'bottomleft'` or `'bottomright'` 4759 position: 'topright' 4760 }, 4761 4762 initialize: function (options) { 4763 setOptions(this, options); 4764 }, 4765 4766 /* @section 4767 * Classes extending L.Control will inherit the following methods: 4768 * 4769 * @method getPosition: string 4770 * Returns the position of the control. 4771 */ 4772 getPosition: function () { 4773 return this.options.position; 4774 }, 4775 4776 // @method setPosition(position: string): this 4777 // Sets the position of the control. 4778 setPosition: function (position) { 4779 var map = this._map; 4780 4781 if (map) { 4782 map.removeControl(this); 4783 } 4784 4785 this.options.position = position; 4786 4787 if (map) { 4788 map.addControl(this); 4789 } 4790 4791 return this; 4792 }, 4793 4794 // @method getContainer: HTMLElement 4795 // Returns the HTMLElement that contains the control. 4796 getContainer: function () { 4797 return this._container; 4798 }, 4799 4800 // @method addTo(map: Map): this 4801 // Adds the control to the given map. 4802 addTo: function (map) { 4803 this.remove(); 4804 this._map = map; 4805 4806 var container = this._container = this.onAdd(map), 4807 pos = this.getPosition(), 4808 corner = map._controlCorners[pos]; 4809 4810 addClass(container, 'leaflet-control'); 4811 4812 if (pos.indexOf('bottom') !== -1) { 4813 corner.insertBefore(container, corner.firstChild); 4814 } else { 4815 corner.appendChild(container); 4816 } 4817 4818 this._map.on('unload', this.remove, this); 4819 4820 return this; 4821 }, 4822 4823 // @method remove: this 4824 // Removes the control from the map it is currently active on. 4825 remove: function () { 4826 if (!this._map) { 4827 return this; 4828 } 4829 4830 remove(this._container); 4831 4832 if (this.onRemove) { 4833 this.onRemove(this._map); 4834 } 4835 4836 this._map.off('unload', this.remove, this); 4837 this._map = null; 4838 4839 return this; 4840 }, 4841 4842 _refocusOnMap: function (e) { 4843 // if map exists and event is not a keyboard event 4844 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 4845 this._map.getContainer().focus(); 4846 } 4847 } 4848}); 4849 4850var control = function (options) { 4851 return new Control(options); 4852}; 4853 4854/* @section Extension methods 4855 * @uninheritable 4856 * 4857 * Every control should extend from `L.Control` and (re-)implement the following methods. 4858 * 4859 * @method onAdd(map: Map): HTMLElement 4860 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 4861 * 4862 * @method onRemove(map: Map) 4863 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 4864 */ 4865 4866/* @namespace Map 4867 * @section Methods for Layers and Controls 4868 */ 4869Map.include({ 4870 // @method addControl(control: Control): this 4871 // Adds the given control to the map 4872 addControl: function (control) { 4873 control.addTo(this); 4874 return this; 4875 }, 4876 4877 // @method removeControl(control: Control): this 4878 // Removes the given control from the map 4879 removeControl: function (control) { 4880 control.remove(); 4881 return this; 4882 }, 4883 4884 _initControlPos: function () { 4885 var corners = this._controlCorners = {}, 4886 l = 'leaflet-', 4887 container = this._controlContainer = 4888 create$1('div', l + 'control-container', this._container); 4889 4890 function createCorner(vSide, hSide) { 4891 var className = l + vSide + ' ' + l + hSide; 4892 4893 corners[vSide + hSide] = create$1('div', className, container); 4894 } 4895 4896 createCorner('top', 'left'); 4897 createCorner('top', 'right'); 4898 createCorner('bottom', 'left'); 4899 createCorner('bottom', 'right'); 4900 }, 4901 4902 _clearControlPos: function () { 4903 for (var i in this._controlCorners) { 4904 remove(this._controlCorners[i]); 4905 } 4906 remove(this._controlContainer); 4907 delete this._controlCorners; 4908 delete this._controlContainer; 4909 } 4910}); 4911 4912/* 4913 * @class Control.Layers 4914 * @aka L.Control.Layers 4915 * @inherits Control 4916 * 4917 * 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`. 4918 * 4919 * @example 4920 * 4921 * ```js 4922 * var baseLayers = { 4923 * "Mapbox": mapbox, 4924 * "OpenStreetMap": osm 4925 * }; 4926 * 4927 * var overlays = { 4928 * "Marker": marker, 4929 * "Roads": roadsLayer 4930 * }; 4931 * 4932 * L.control.layers(baseLayers, overlays).addTo(map); 4933 * ``` 4934 * 4935 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 4936 * 4937 * ```js 4938 * { 4939 * "<someName1>": layer1, 4940 * "<someName2>": layer2 4941 * } 4942 * ``` 4943 * 4944 * The layer names can contain HTML, which allows you to add additional styling to the items: 4945 * 4946 * ```js 4947 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 4948 * ``` 4949 */ 4950 4951var Layers = Control.extend({ 4952 // @section 4953 // @aka Control.Layers options 4954 options: { 4955 // @option collapsed: Boolean = true 4956 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. 4957 collapsed: true, 4958 position: 'topright', 4959 4960 // @option autoZIndex: Boolean = true 4961 // 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. 4962 autoZIndex: true, 4963 4964 // @option hideSingleBase: Boolean = false 4965 // If `true`, the base layers in the control will be hidden when there is only one. 4966 hideSingleBase: false, 4967 4968 // @option sortLayers: Boolean = false 4969 // Whether to sort the layers. When `false`, layers will keep the order 4970 // in which they were added to the control. 4971 sortLayers: false, 4972 4973 // @option sortFunction: Function = * 4974 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 4975 // that will be used for sorting the layers, when `sortLayers` is `true`. 4976 // The function receives both the `L.Layer` instances and their names, as in 4977 // `sortFunction(layerA, layerB, nameA, nameB)`. 4978 // By default, it sorts layers alphabetically by their name. 4979 sortFunction: function (layerA, layerB, nameA, nameB) { 4980 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 4981 } 4982 }, 4983 4984 initialize: function (baseLayers, overlays, options) { 4985 setOptions(this, options); 4986 4987 this._layerControlInputs = []; 4988 this._layers = []; 4989 this._lastZIndex = 0; 4990 this._handlingClick = false; 4991 4992 for (var i in baseLayers) { 4993 this._addLayer(baseLayers[i], i); 4994 } 4995 4996 for (i in overlays) { 4997 this._addLayer(overlays[i], i, true); 4998 } 4999 }, 5000 5001 onAdd: function (map) { 5002 this._initLayout(); 5003 this._update(); 5004 5005 this._map = map; 5006 map.on('zoomend', this._checkDisabledLayers, this); 5007 5008 for (var i = 0; i < this._layers.length; i++) { 5009 this._layers[i].layer.on('add remove', this._onLayerChange, this); 5010 } 5011 5012 return this._container; 5013 }, 5014 5015 addTo: function (map) { 5016 Control.prototype.addTo.call(this, map); 5017 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 5018 return this._expandIfNotCollapsed(); 5019 }, 5020 5021 onRemove: function () { 5022 this._map.off('zoomend', this._checkDisabledLayers, this); 5023 5024 for (var i = 0; i < this._layers.length; i++) { 5025 this._layers[i].layer.off('add remove', this._onLayerChange, this); 5026 } 5027 }, 5028 5029 // @method addBaseLayer(layer: Layer, name: String): this 5030 // Adds a base layer (radio button entry) with the given name to the control. 5031 addBaseLayer: function (layer, name) { 5032 this._addLayer(layer, name); 5033 return (this._map) ? this._update() : this; 5034 }, 5035 5036 // @method addOverlay(layer: Layer, name: String): this 5037 // Adds an overlay (checkbox entry) with the given name to the control. 5038 addOverlay: function (layer, name) { 5039 this._addLayer(layer, name, true); 5040 return (this._map) ? this._update() : this; 5041 }, 5042 5043 // @method removeLayer(layer: Layer): this 5044 // Remove the given layer from the control. 5045 removeLayer: function (layer) { 5046 layer.off('add remove', this._onLayerChange, this); 5047 5048 var obj = this._getLayer(stamp(layer)); 5049 if (obj) { 5050 this._layers.splice(this._layers.indexOf(obj), 1); 5051 } 5052 return (this._map) ? this._update() : this; 5053 }, 5054 5055 // @method expand(): this 5056 // Expand the control container if collapsed. 5057 expand: function () { 5058 addClass(this._container, 'leaflet-control-layers-expanded'); 5059 this._section.style.height = null; 5060 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 5061 if (acceptableHeight < this._section.clientHeight) { 5062 addClass(this._section, 'leaflet-control-layers-scrollbar'); 5063 this._section.style.height = acceptableHeight + 'px'; 5064 } else { 5065 removeClass(this._section, 'leaflet-control-layers-scrollbar'); 5066 } 5067 this._checkDisabledLayers(); 5068 return this; 5069 }, 5070 5071 // @method collapse(): this 5072 // Collapse the control container if expanded. 5073 collapse: function () { 5074 removeClass(this._container, 'leaflet-control-layers-expanded'); 5075 return this; 5076 }, 5077 5078 _initLayout: function () { 5079 var className = 'leaflet-control-layers', 5080 container = this._container = create$1('div', className), 5081 collapsed = this.options.collapsed; 5082 5083 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 5084 container.setAttribute('aria-haspopup', true); 5085 5086 disableClickPropagation(container); 5087 disableScrollPropagation(container); 5088 5089 var section = this._section = create$1('section', className + '-list'); 5090 5091 if (collapsed) { 5092 this._map.on('click', this.collapse, this); 5093 5094 if (!android) { 5095 on(container, { 5096 mouseenter: this.expand, 5097 mouseleave: this.collapse 5098 }, this); 5099 } 5100 } 5101 5102 var link = this._layersLink = create$1('a', className + '-toggle', container); 5103 link.href = '#'; 5104 link.title = 'Layers'; 5105 5106 if (touch) { 5107 on(link, 'click', stop); 5108 on(link, 'click', this.expand, this); 5109 } else { 5110 on(link, 'focus', this.expand, this); 5111 } 5112 5113 if (!collapsed) { 5114 this.expand(); 5115 } 5116 5117 this._baseLayersList = create$1('div', className + '-base', section); 5118 this._separator = create$1('div', className + '-separator', section); 5119 this._overlaysList = create$1('div', className + '-overlays', section); 5120 5121 container.appendChild(section); 5122 }, 5123 5124 _getLayer: function (id) { 5125 for (var i = 0; i < this._layers.length; i++) { 5126 5127 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5128 return this._layers[i]; 5129 } 5130 } 5131 }, 5132 5133 _addLayer: function (layer, name, overlay) { 5134 if (this._map) { 5135 layer.on('add remove', this._onLayerChange, this); 5136 } 5137 5138 this._layers.push({ 5139 layer: layer, 5140 name: name, 5141 overlay: overlay 5142 }); 5143 5144 if (this.options.sortLayers) { 5145 this._layers.sort(bind(function (a, b) { 5146 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5147 }, this)); 5148 } 5149 5150 if (this.options.autoZIndex && layer.setZIndex) { 5151 this._lastZIndex++; 5152 layer.setZIndex(this._lastZIndex); 5153 } 5154 5155 this._expandIfNotCollapsed(); 5156 }, 5157 5158 _update: function () { 5159 if (!this._container) { return this; } 5160 5161 empty(this._baseLayersList); 5162 empty(this._overlaysList); 5163 5164 this._layerControlInputs = []; 5165 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5166 5167 for (i = 0; i < this._layers.length; i++) { 5168 obj = this._layers[i]; 5169 this._addItem(obj); 5170 overlaysPresent = overlaysPresent || obj.overlay; 5171 baseLayersPresent = baseLayersPresent || !obj.overlay; 5172 baseLayersCount += !obj.overlay ? 1 : 0; 5173 } 5174 5175 // Hide base layers section if there's only one layer. 5176 if (this.options.hideSingleBase) { 5177 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5178 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5179 } 5180 5181 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5182 5183 return this; 5184 }, 5185 5186 _onLayerChange: function (e) { 5187 if (!this._handlingClick) { 5188 this._update(); 5189 } 5190 5191 var obj = this._getLayer(stamp(e.target)); 5192 5193 // @namespace Map 5194 // @section Layer events 5195 // @event baselayerchange: LayersControlEvent 5196 // Fired when the base layer is changed through the [layer control](#control-layers). 5197 // @event overlayadd: LayersControlEvent 5198 // Fired when an overlay is selected through the [layer control](#control-layers). 5199 // @event overlayremove: LayersControlEvent 5200 // Fired when an overlay is deselected through the [layer control](#control-layers). 5201 // @namespace Control.Layers 5202 var type = obj.overlay ? 5203 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5204 (e.type === 'add' ? 'baselayerchange' : null); 5205 5206 if (type) { 5207 this._map.fire(type, obj); 5208 } 5209 }, 5210 5211 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) 5212 _createRadioElement: function (name, checked) { 5213 5214 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5215 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5216 5217 var radioFragment = document.createElement('div'); 5218 radioFragment.innerHTML = radioHtml; 5219 5220 return radioFragment.firstChild; 5221 }, 5222 5223 _addItem: function (obj) { 5224 var label = document.createElement('label'), 5225 checked = this._map.hasLayer(obj.layer), 5226 input; 5227 5228 if (obj.overlay) { 5229 input = document.createElement('input'); 5230 input.type = 'checkbox'; 5231 input.className = 'leaflet-control-layers-selector'; 5232 input.defaultChecked = checked; 5233 } else { 5234 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked); 5235 } 5236 5237 this._layerControlInputs.push(input); 5238 input.layerId = stamp(obj.layer); 5239 5240 on(input, 'click', this._onInputClick, this); 5241 5242 var name = document.createElement('span'); 5243 name.innerHTML = ' ' + obj.name; 5244 5245 // Helps from preventing layer control flicker when checkboxes are disabled 5246 // https://github.com/Leaflet/Leaflet/issues/2771 5247 var holder = document.createElement('div'); 5248 5249 label.appendChild(holder); 5250 holder.appendChild(input); 5251 holder.appendChild(name); 5252 5253 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5254 container.appendChild(label); 5255 5256 this._checkDisabledLayers(); 5257 return label; 5258 }, 5259 5260 _onInputClick: function () { 5261 var inputs = this._layerControlInputs, 5262 input, layer; 5263 var addedLayers = [], 5264 removedLayers = []; 5265 5266 this._handlingClick = true; 5267 5268 for (var i = inputs.length - 1; i >= 0; i--) { 5269 input = inputs[i]; 5270 layer = this._getLayer(input.layerId).layer; 5271 5272 if (input.checked) { 5273 addedLayers.push(layer); 5274 } else if (!input.checked) { 5275 removedLayers.push(layer); 5276 } 5277 } 5278 5279 // Bugfix issue 2318: Should remove all old layers before readding new ones 5280 for (i = 0; i < removedLayers.length; i++) { 5281 if (this._map.hasLayer(removedLayers[i])) { 5282 this._map.removeLayer(removedLayers[i]); 5283 } 5284 } 5285 for (i = 0; i < addedLayers.length; i++) { 5286 if (!this._map.hasLayer(addedLayers[i])) { 5287 this._map.addLayer(addedLayers[i]); 5288 } 5289 } 5290 5291 this._handlingClick = false; 5292 5293 this._refocusOnMap(); 5294 }, 5295 5296 _checkDisabledLayers: function () { 5297 var inputs = this._layerControlInputs, 5298 input, 5299 layer, 5300 zoom = this._map.getZoom(); 5301 5302 for (var i = inputs.length - 1; i >= 0; i--) { 5303 input = inputs[i]; 5304 layer = this._getLayer(input.layerId).layer; 5305 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5306 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5307 5308 } 5309 }, 5310 5311 _expandIfNotCollapsed: function () { 5312 if (this._map && !this.options.collapsed) { 5313 this.expand(); 5314 } 5315 return this; 5316 }, 5317 5318 _expand: function () { 5319 // Backward compatibility, remove me in 1.1. 5320 return this.expand(); 5321 }, 5322 5323 _collapse: function () { 5324 // Backward compatibility, remove me in 1.1. 5325 return this.collapse(); 5326 } 5327 5328}); 5329 5330 5331// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5332// 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. 5333var layers = function (baseLayers, overlays, options) { 5334 return new Layers(baseLayers, overlays, options); 5335}; 5336 5337/* 5338 * @class Control.Zoom 5339 * @aka L.Control.Zoom 5340 * @inherits Control 5341 * 5342 * 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`. 5343 */ 5344 5345var Zoom = Control.extend({ 5346 // @section 5347 // @aka Control.Zoom options 5348 options: { 5349 position: 'topleft', 5350 5351 // @option zoomInText: String = '+' 5352 // The text set on the 'zoom in' button. 5353 zoomInText: '+', 5354 5355 // @option zoomInTitle: String = 'Zoom in' 5356 // The title set on the 'zoom in' button. 5357 zoomInTitle: 'Zoom in', 5358 5359 // @option zoomOutText: String = '−' 5360 // The text set on the 'zoom out' button. 5361 zoomOutText: '−', 5362 5363 // @option zoomOutTitle: String = 'Zoom out' 5364 // The title set on the 'zoom out' button. 5365 zoomOutTitle: 'Zoom out' 5366 }, 5367 5368 onAdd: function (map) { 5369 var zoomName = 'leaflet-control-zoom', 5370 container = create$1('div', zoomName + ' leaflet-bar'), 5371 options = this.options; 5372 5373 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5374 zoomName + '-in', container, this._zoomIn); 5375 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5376 zoomName + '-out', container, this._zoomOut); 5377 5378 this._updateDisabled(); 5379 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5380 5381 return container; 5382 }, 5383 5384 onRemove: function (map) { 5385 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5386 }, 5387 5388 disable: function () { 5389 this._disabled = true; 5390 this._updateDisabled(); 5391 return this; 5392 }, 5393 5394 enable: function () { 5395 this._disabled = false; 5396 this._updateDisabled(); 5397 return this; 5398 }, 5399 5400 _zoomIn: function (e) { 5401 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5402 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5403 } 5404 }, 5405 5406 _zoomOut: function (e) { 5407 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5408 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5409 } 5410 }, 5411 5412 _createButton: function (html, title, className, container, fn) { 5413 var link = create$1('a', className, container); 5414 link.innerHTML = html; 5415 link.href = '#'; 5416 link.title = title; 5417 5418 /* 5419 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5420 */ 5421 link.setAttribute('role', 'button'); 5422 link.setAttribute('aria-label', title); 5423 5424 disableClickPropagation(link); 5425 on(link, 'click', stop); 5426 on(link, 'click', fn, this); 5427 on(link, 'click', this._refocusOnMap, this); 5428 5429 return link; 5430 }, 5431 5432 _updateDisabled: function () { 5433 var map = this._map, 5434 className = 'leaflet-disabled'; 5435 5436 removeClass(this._zoomInButton, className); 5437 removeClass(this._zoomOutButton, className); 5438 5439 if (this._disabled || map._zoom === map.getMinZoom()) { 5440 addClass(this._zoomOutButton, className); 5441 } 5442 if (this._disabled || map._zoom === map.getMaxZoom()) { 5443 addClass(this._zoomInButton, className); 5444 } 5445 } 5446}); 5447 5448// @namespace Map 5449// @section Control options 5450// @option zoomControl: Boolean = true 5451// Whether a [zoom control](#control-zoom) is added to the map by default. 5452Map.mergeOptions({ 5453 zoomControl: true 5454}); 5455 5456Map.addInitHook(function () { 5457 if (this.options.zoomControl) { 5458 // @section Controls 5459 // @property zoomControl: Control.Zoom 5460 // The default zoom control (only available if the 5461 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5462 this.zoomControl = new Zoom(); 5463 this.addControl(this.zoomControl); 5464 } 5465}); 5466 5467// @namespace Control.Zoom 5468// @factory L.control.zoom(options: Control.Zoom options) 5469// Creates a zoom control 5470var zoom = function (options) { 5471 return new Zoom(options); 5472}; 5473 5474/* 5475 * @class Control.Scale 5476 * @aka L.Control.Scale 5477 * @inherits Control 5478 * 5479 * 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`. 5480 * 5481 * @example 5482 * 5483 * ```js 5484 * L.control.scale().addTo(map); 5485 * ``` 5486 */ 5487 5488var Scale = Control.extend({ 5489 // @section 5490 // @aka Control.Scale options 5491 options: { 5492 position: 'bottomleft', 5493 5494 // @option maxWidth: Number = 100 5495 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5496 maxWidth: 100, 5497 5498 // @option metric: Boolean = True 5499 // Whether to show the metric scale line (m/km). 5500 metric: true, 5501 5502 // @option imperial: Boolean = True 5503 // Whether to show the imperial scale line (mi/ft). 5504 imperial: true 5505 5506 // @option updateWhenIdle: Boolean = false 5507 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5508 }, 5509 5510 onAdd: function (map) { 5511 var className = 'leaflet-control-scale', 5512 container = create$1('div', className), 5513 options = this.options; 5514 5515 this._addScales(options, className + '-line', container); 5516 5517 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5518 map.whenReady(this._update, this); 5519 5520 return container; 5521 }, 5522 5523 onRemove: function (map) { 5524 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5525 }, 5526 5527 _addScales: function (options, className, container) { 5528 if (options.metric) { 5529 this._mScale = create$1('div', className, container); 5530 } 5531 if (options.imperial) { 5532 this._iScale = create$1('div', className, container); 5533 } 5534 }, 5535 5536 _update: function () { 5537 var map = this._map, 5538 y = map.getSize().y / 2; 5539 5540 var maxMeters = map.distance( 5541 map.containerPointToLatLng([0, y]), 5542 map.containerPointToLatLng([this.options.maxWidth, y])); 5543 5544 this._updateScales(maxMeters); 5545 }, 5546 5547 _updateScales: function (maxMeters) { 5548 if (this.options.metric && maxMeters) { 5549 this._updateMetric(maxMeters); 5550 } 5551 if (this.options.imperial && maxMeters) { 5552 this._updateImperial(maxMeters); 5553 } 5554 }, 5555 5556 _updateMetric: function (maxMeters) { 5557 var meters = this._getRoundNum(maxMeters), 5558 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5559 5560 this._updateScale(this._mScale, label, meters / maxMeters); 5561 }, 5562 5563 _updateImperial: function (maxMeters) { 5564 var maxFeet = maxMeters * 3.2808399, 5565 maxMiles, miles, feet; 5566 5567 if (maxFeet > 5280) { 5568 maxMiles = maxFeet / 5280; 5569 miles = this._getRoundNum(maxMiles); 5570 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5571 5572 } else { 5573 feet = this._getRoundNum(maxFeet); 5574 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5575 } 5576 }, 5577 5578 _updateScale: function (scale, text, ratio) { 5579 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5580 scale.innerHTML = text; 5581 }, 5582 5583 _getRoundNum: function (num) { 5584 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5585 d = num / pow10; 5586 5587 d = d >= 10 ? 10 : 5588 d >= 5 ? 5 : 5589 d >= 3 ? 3 : 5590 d >= 2 ? 2 : 1; 5591 5592 return pow10 * d; 5593 } 5594}); 5595 5596 5597// @factory L.control.scale(options?: Control.Scale options) 5598// Creates an scale control with the given options. 5599var scale = function (options) { 5600 return new Scale(options); 5601}; 5602 5603/* 5604 * @class Control.Attribution 5605 * @aka L.Control.Attribution 5606 * @inherits Control 5607 * 5608 * 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. 5609 */ 5610 5611var Attribution = Control.extend({ 5612 // @section 5613 // @aka Control.Attribution options 5614 options: { 5615 position: 'bottomright', 5616 5617 // @option prefix: String = 'Leaflet' 5618 // The HTML text shown before the attributions. Pass `false` to disable. 5619 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' 5620 }, 5621 5622 initialize: function (options) { 5623 setOptions(this, options); 5624 5625 this._attributions = {}; 5626 }, 5627 5628 onAdd: function (map) { 5629 map.attributionControl = this; 5630 this._container = create$1('div', 'leaflet-control-attribution'); 5631 disableClickPropagation(this._container); 5632 5633 // TODO ugly, refactor 5634 for (var i in map._layers) { 5635 if (map._layers[i].getAttribution) { 5636 this.addAttribution(map._layers[i].getAttribution()); 5637 } 5638 } 5639 5640 this._update(); 5641 5642 return this._container; 5643 }, 5644 5645 // @method setPrefix(prefix: String): this 5646 // Sets the text before the attributions. 5647 setPrefix: function (prefix) { 5648 this.options.prefix = prefix; 5649 this._update(); 5650 return this; 5651 }, 5652 5653 // @method addAttribution(text: String): this 5654 // Adds an attribution text (e.g. `'Vector data © Mapbox'`). 5655 addAttribution: function (text) { 5656 if (!text) { return this; } 5657 5658 if (!this._attributions[text]) { 5659 this._attributions[text] = 0; 5660 } 5661 this._attributions[text]++; 5662 5663 this._update(); 5664 5665 return this; 5666 }, 5667 5668 // @method removeAttribution(text: String): this 5669 // Removes an attribution text. 5670 removeAttribution: function (text) { 5671 if (!text) { return this; } 5672 5673 if (this._attributions[text]) { 5674 this._attributions[text]--; 5675 this._update(); 5676 } 5677 5678 return this; 5679 }, 5680 5681 _update: function () { 5682 if (!this._map) { return; } 5683 5684 var attribs = []; 5685 5686 for (var i in this._attributions) { 5687 if (this._attributions[i]) { 5688 attribs.push(i); 5689 } 5690 } 5691 5692 var prefixAndAttribs = []; 5693 5694 if (this.options.prefix) { 5695 prefixAndAttribs.push(this.options.prefix); 5696 } 5697 if (attribs.length) { 5698 prefixAndAttribs.push(attribs.join(', ')); 5699 } 5700 5701 this._container.innerHTML = prefixAndAttribs.join(' | '); 5702 } 5703}); 5704 5705// @namespace Map 5706// @section Control options 5707// @option attributionControl: Boolean = true 5708// Whether a [attribution control](#control-attribution) is added to the map by default. 5709Map.mergeOptions({ 5710 attributionControl: true 5711}); 5712 5713Map.addInitHook(function () { 5714 if (this.options.attributionControl) { 5715 new Attribution().addTo(this); 5716 } 5717}); 5718 5719// @namespace Control.Attribution 5720// @factory L.control.attribution(options: Control.Attribution options) 5721// Creates an attribution control. 5722var attribution = function (options) { 5723 return new Attribution(options); 5724}; 5725 5726Control.Layers = Layers; 5727Control.Zoom = Zoom; 5728Control.Scale = Scale; 5729Control.Attribution = Attribution; 5730 5731control.layers = layers; 5732control.zoom = zoom; 5733control.scale = scale; 5734control.attribution = attribution; 5735 5736/* 5737 L.Handler is a base class for handler classes that are used internally to inject 5738 interaction features like dragging to classes like Map and Marker. 5739*/ 5740 5741// @class Handler 5742// @aka L.Handler 5743// Abstract class for map interaction handlers 5744 5745var Handler = Class.extend({ 5746 initialize: function (map) { 5747 this._map = map; 5748 }, 5749 5750 // @method enable(): this 5751 // Enables the handler 5752 enable: function () { 5753 if (this._enabled) { return this; } 5754 5755 this._enabled = true; 5756 this.addHooks(); 5757 return this; 5758 }, 5759 5760 // @method disable(): this 5761 // Disables the handler 5762 disable: function () { 5763 if (!this._enabled) { return this; } 5764 5765 this._enabled = false;
5766 this.removeHooks(); 5767 return this; 5768 }, 5769 5770 // @method enabled(): Boolean 5771 // Returns `true` if the handler is enabled 5772 enabled: function () { 5773 return !!this._enabled; 5774 } 5775 5776 // @section Extension methods 5777 // Classes inheriting from `Handler` must implement the two following methods: 5778 // @method addHooks() 5779 // Called when the handler is enabled, should add event hooks. 5780 // @method removeHooks() 5781 // Called when the handler is disabled, should remove the event hooks added previously. 5782}); 5783 5784// @section There is static function which can be called without instantiating L.Handler: 5785// @function addTo(map: Map, name: String): this 5786// Adds a new Handler to the given map with the given name. 5787Handler.addTo = function (map, name) { 5788 map.addHandler(name, this); 5789 return this; 5790}; 5791 5792var Mixin = {Events: Events}; 5793 5794/* 5795 * @class Draggable 5796 * @aka L.Draggable 5797 * @inherits Evented 5798 * 5799 * A class for making DOM elements draggable (including touch support). 5800 * Used internally for map and marker dragging. Only works for elements 5801 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 5802 * 5803 * @example 5804 * ```js 5805 * var draggable = new L.Draggable(elementToDrag); 5806 * draggable.enable(); 5807 * ``` 5808 */ 5809 5810var START = touch ? 'touchstart mousedown' : 'mousedown'; 5811var END = { 5812 mousedown: 'mouseup', 5813 touchstart: 'touchend', 5814 pointerdown: 'touchend', 5815 MSPointerDown: 'touchend' 5816}; 5817var MOVE = { 5818 mousedown: 'mousemove', 5819 touchstart: 'touchmove', 5820 pointerdown: 'touchmove', 5821 MSPointerDown: 'touchmove' 5822}; 5823 5824 5825var Draggable = Evented.extend({ 5826 5827 options: { 5828 // @section 5829 // @aka Draggable options 5830 // @option clickTolerance: Number = 3 5831 // The max number of pixels a user can shift the mouse pointer during a click 5832 // for it to be considered a valid click (as opposed to a mouse drag). 5833 clickTolerance: 3 5834 }, 5835 5836 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 5837 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 5838 initialize: function (element, dragStartTarget, preventOutline$$1, options) { 5839 setOptions(this, options); 5840 5841 this._element = element; 5842 this._dragStartTarget = dragStartTarget || element; 5843 this._preventOutline = preventOutline$$1; 5844 }, 5845 5846 // @method enable() 5847 // Enables the dragging ability 5848 enable: function () { 5849 if (this._enabled) { return; } 5850 5851 on(this._dragStartTarget, START, this._onDown, this); 5852 5853 this._enabled = true; 5854 }, 5855 5856 // @method disable() 5857 // Disables the dragging ability 5858 disable: function () { 5859 if (!this._enabled) { return; } 5860 5861 // If we're currently dragging this draggable, 5862 // disabling it counts as first ending the drag. 5863 if (Draggable._dragging === this) { 5864 this.finishDrag(); 5865 } 5866 5867 off(this._dragStartTarget, START, this._onDown, this); 5868 5869 this._enabled = false; 5870 this._moved = false; 5871 }, 5872 5873 _onDown: function (e) { 5874 // Ignore simulated events, since we handle both touch and 5875 // mouse explicitly; otherwise we risk getting duplicates of 5876 // touch events, see #4315. 5877 // Also ignore the event if disabled; this happens in IE11 5878 // under some circumstances, see #3666. 5879 if (e._simulated || !this._enabled) { return; } 5880 5881 this._moved = false; 5882 5883 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 5884 5885 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 5886 Draggable._dragging = this; // Prevent dragging multiple objects at once. 5887 5888 if (this._preventOutline) { 5889 preventOutline(this._element); 5890 } 5891 5892 disableImageDrag(); 5893 disableTextSelection(); 5894 5895 if (this._moving) { return; } 5896 5897 // @event down: Event 5898 // Fired when a drag is about to start. 5899 this.fire('down'); 5900 5901 var first = e.touches ? e.touches[0] : e, 5902 sizedParent = getSizedParentNode(this._element); 5903 5904 this._startPoint = new Point(first.clientX, first.clientY); 5905 5906 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 5907 this._parentScale = getScale(sizedParent); 5908 5909 on(document, MOVE[e.type], this._onMove, this); 5910 on(document, END[e.type], this._onUp, this); 5911 }, 5912 5913 _onMove: function (e) { 5914 // Ignore simulated events, since we handle both touch and 5915 // mouse explicitly; otherwise we risk getting duplicates of 5916 // touch events, see #4315. 5917 // Also ignore the event if disabled; this happens in IE11 5918 // under some circumstances, see #3666. 5919 if (e._simulated || !this._enabled) { return; } 5920 5921 if (e.touches && e.touches.length > 1) { 5922 this._moved = true; 5923 return; 5924 } 5925 5926 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 5927 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 5928 5929 if (!offset.x && !offset.y) { return; } 5930 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 5931 5932 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 5933 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 5934 // and we can use the cached value for the scale. 5935 offset.x /= this._parentScale.x; 5936 offset.y /= this._parentScale.y; 5937 5938 preventDefault(e); 5939 5940 if (!this._moved) { 5941 // @event dragstart: Event 5942 // Fired when a drag starts 5943 this.fire('dragstart'); 5944 5945 this._moved = true; 5946 this._startPos = getPosition(this._element).subtract(offset); 5947 5948 addClass(document.body, 'leaflet-dragging'); 5949 5950 this._lastTarget = e.target || e.srcElement; 5951 // IE and Edge do not give the <use> element, so fetch it 5952 // if necessary 5953 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { 5954 this._lastTarget = this._lastTarget.correspondingUseElement; 5955 } 5956 addClass(this._lastTarget, 'leaflet-drag-target'); 5957 } 5958 5959 this._newPos = this._startPos.add(offset); 5960 this._moving = true; 5961
5962 cancelAnimFrame(this._animRequest); 5963 this._lastEvent = e; 5964 this._animRequest = requestAnimFrame(this._updatePosition, this, true); 5965 }, 5966 5967 _updatePosition: function () { 5968 var e = {originalEvent: this._lastEvent}; 5969 5970 // @event predrag: Event 5971 // Fired continuously during dragging *before* each corresponding 5972 // update of the element's position. 5973 this.fire('predrag', e); 5974 setPosition(this._element, this._newPos); 5975 5976 // @event drag: Event 5977 // Fired continuously during dragging. 5978 this.fire('drag', e); 5979 }, 5980 5981 _onUp: function (e) { 5982 // Ignore simulated events, since we handle both touch and 5983 // mouse explicitly; otherwise we risk getting duplicates of 5984 // touch events, see #4315. 5985 // Also ignore the event if disabled; this happens in IE11 5986 // under some circumstances, see #3666. 5987 if (e._simulated || !this._enabled) { return; } 5988 this.finishDrag(); 5989 }, 5990 5991 finishDrag: function () { 5992 removeClass(document.body, 'leaflet-dragging'); 5993 5994 if (this._lastTarget) { 5995 removeClass(this._lastTarget, 'leaflet-drag-target'); 5996 this._lastTarget = null; 5997 } 5998 5999 for (var i in MOVE) { 6000 off(document, MOVE[i], this._onMove, this); 6001 off(document, END[i], this._onUp, this); 6002 } 6003 6004 enableImageDrag(); 6005 enableTextSelection(); 6006 6007 if (this._moved && this._moving) { 6008 // ensure drag is not fired after dragend 6009 cancelAnimFrame(this._animRequest); 6010 6011 // @event dragend: DragEndEvent 6012 // Fired when the drag ends. 6013 this.fire('dragend', { 6014 distance: this._newPos.distanceTo(this._startPos) 6015 }); 6016 } 6017 6018 this._moving = false; 6019 Draggable._dragging = false; 6020 } 6021 6022}); 6023 6024/* 6025 * @namespace LineUtil 6026 * 6027 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 6028 */ 6029 6030// Simplify polyline with vertex reduction and Douglas-Peucker simplification. 6031// Improves rendering performance dramatically by lessening the number of points to draw. 6032 6033// @function simplify(points: Point[], tolerance: Number): Point[] 6034// Dramatically reduces the number of points in a polyline while retaining 6035// its shape and returns a new array of simplified points, using the 6036// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). 6037// Used for a huge performance boost when processing/displaying Leaflet polylines for 6038// each zoom level and also reducing visual noise. tolerance affects the amount of 6039// simplification (lesser value means higher quality but slower and with more points). 6040// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). 6041function simplify(points, tolerance) { 6042 if (!tolerance || !points.length) { 6043 return points.slice(); 6044 } 6045 6046 var sqTolerance = tolerance * tolerance; 6047 6048 // stage 1: vertex reduction 6049 points = _reducePoints(points, sqTolerance); 6050 6051 // stage 2: Douglas-Peucker simplification 6052 points = _simplifyDP(points, sqTolerance); 6053 6054 return points; 6055} 6056 6057// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 6058// Returns the distance between point `p` and segment `p1` to `p2`. 6059function pointToSegmentDistance(p, p1, p2) { 6060 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 6061} 6062 6063// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 6064// Returns the closest point from a point `p` on a segment `p1` to `p2`. 6065function closestPointOnSegment(p, p1, p2) { 6066 return _sqClosestPointOnSegment(p, p1, p2); 6067} 6068 6069// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm 6070function _simplifyDP(points, sqTolerance) { 6071 6072 var len = points.length, 6073 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 6074 markers = new ArrayConstructor(len); 6075 6076 markers[0] = markers[len - 1] = 1; 6077 6078 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 6079 6080 var i, 6081 newPoints = []; 6082 6083 for (i = 0; i < len; i++) { 6084 if (markers[i]) { 6085 newPoints.push(points[i]); 6086 } 6087 } 6088 6089 return newPoints; 6090} 6091 6092function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6093 6094 var maxSqDist = 0, 6095 index, i, sqDist; 6096 6097 for (i = first + 1; i <= last - 1; i++) { 6098 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6099 6100 if (sqDist > maxSqDist) { 6101 index = i; 6102 maxSqDist = sqDist; 6103 } 6104 } 6105 6106 if (maxSqDist > sqTolerance) { 6107 markers[index] = 1; 6108 6109 _simplifyDPStep(points, markers, sqTolerance, first, index); 6110 _simplifyDPStep(points, markers, sqTolerance, index, last); 6111 } 6112} 6113 6114// reduce points that are too close to each other to a single point 6115function _reducePoints(points, sqTolerance) { 6116 var reducedPoints = [points[0]]; 6117 6118 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6119 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6120 reducedPoints.push(points[i]); 6121 prev = i; 6122 } 6123 } 6124 if (prev < len - 1) { 6125 reducedPoints.push(points[len - 1]); 6126 } 6127 return reducedPoints; 6128} 6129 6130var _lastCode; 6131 6132// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, roun
vendor: 47,477 bytes, lines 6132-7917
6132d?: Boolean): Point[]|Boolean 6133// Clips the segment a to b by rectangular bounds with the 6134// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6135// (modifying the segment points directly!). Used by Leaflet to only show polyline 6136// points that are on the screen or near, increasing performance. 6137function clipSegment(a, b, bounds, useLastCode, round) { 6138 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6139 codeB = _getBitCode(b, bounds), 6140 6141 codeOut, p, newCode; 6142 6143 // save 2nd code to avoid calculating it on the next segment 6144 _lastCode = codeB; 6145 6146 while (true) { 6147 // if a,b is inside the clip window (trivial accept) 6148 if (!(codeA | codeB)) { 6149 return [a, b]; 6150 } 6151 6152 // if a,b is outside the clip window (trivial reject) 6153 if (codeA & codeB) { 6154 return false; 6155 } 6156 6157 // other cases 6158 codeOut = codeA || codeB; 6159 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6160 newCode = _getBitCode(p, bounds); 6161 6162 if (codeOut === codeA) { 6163 a = p; 6164 codeA = newCode; 6165 } else { 6166 b = p; 6167 codeB = newCode; 6168 } 6169 } 6170} 6171 6172function _getEdgeIntersection(a, b, code, bounds, round) { 6173 var dx = b.x - a.x, 6174 dy = b.y - a.y, 6175 min = bounds.min, 6176 max = bounds.max, 6177 x, y; 6178 6179 if (code & 8) { // top 6180 x = a.x + dx * (max.y - a.y) / dy; 6181 y = max.y; 6182 6183 } else if (code & 4) { // bottom 6184 x = a.x + dx * (min.y - a.y) / dy; 6185 y = min.y; 6186 6187 } else if (code & 2) { // right 6188 x = max.x; 6189 y = a.y + dy * (max.x - a.x) / dx; 6190 6191 } else if (code & 1) { // left 6192 x = min.x; 6193 y = a.y + dy * (min.x - a.x) / dx; 6194 } 6195 6196 return new Point(x, y, round); 6197} 6198 6199function _getBitCode(p, bounds) { 6200 var code = 0; 6201 6202 if (p.x < bounds.min.x) { // left 6203 code |= 1; 6204 } else if (p.x > bounds.max.x) { // right 6205 code |= 2; 6206 } 6207 6208 if (p.y < bounds.min.y) { // bottom 6209 code |= 4; 6210 } else if (p.y > bounds.max.y) { // top 6211 code |= 8; 6212 } 6213 6214 return code; 6215} 6216 6217// square distance (to avoid unnecessary Math.sqrt calls) 6218function _sqDist(p1, p2) { 6219 var dx = p2.x - p1.x, 6220 dy = p2.y - p1.y; 6221 return dx * dx + dy * dy; 6222} 6223 6224// return closest point on segment or distance to that point 6225function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6226 var x = p1.x, 6227 y = p1.y, 6228 dx = p2.x - x, 6229 dy = p2.y - y, 6230 dot = dx * dx + dy * dy, 6231 t; 6232 6233 if (dot > 0) { 6234 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6235 6236 if (t > 1) { 6237 x = p2.x; 6238 y = p2.y; 6239 } else if (t > 0) { 6240 x += dx * t; 6241 y += dy * t; 6242 } 6243 } 6244 6245 dx = p.x - x; 6246 dy = p.y - y; 6247 6248 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6249} 6250 6251 6252// @function isFlat(latlngs: LatLng[]): Boolean 6253// Returns true if `latlngs` is a flat array, false is nested. 6254function isFlat(latlngs) { 6255 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6256} 6257 6258function _flat(latlngs) { 6259 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6260 return isFlat(latlngs); 6261} 6262 6263 6264var LineUtil = (Object.freeze || Object)({ 6265 simplify: simplify, 6266 pointToSegmentDistance: pointToSegmentDistance, 6267 closestPointOnSegment: closestPointOnSegment, 6268 clipSegment: clipSegment, 6269 _getEdgeIntersection: _getEdgeIntersection, 6270 _getBitCode: _getBitCode, 6271 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6272 isFlat: isFlat, 6273 _flat: _flat 6274}); 6275 6276/* 6277 * @namespace PolyUtil 6278 * Various utility functions for polygon geometries. 6279 */ 6280 6281/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6282 * 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)). 6283 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6284 * performance. Note that polygon points needs different algorithm for clipping 6285 * than polyline, so there's a separate method for it. 6286 */ 6287function clipPolygon(points, bounds, round) { 6288 var clippedPoints, 6289 edges = [1, 4, 2, 8], 6290 i, j, k, 6291 a, b, 6292 len, edge, p; 6293 6294 for (i = 0, len = points.length; i < len; i++) { 6295 points[i]._code = _getBitCode(points[i], bounds); 6296 } 6297 6298 // for each edge (left, bottom, right, top) 6299 for (k = 0; k < 4; k++) { 6300 edge = edges[k]; 6301 clippedPoints = []; 6302 6303 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6304 a = points[i]; 6305 b = points[j]; 6306 6307 // if a is inside the clip window 6308 if (!(a._code & edge)) { 6309 // if b is outside the clip window (a->b goes out of screen) 6310 if (b._code & edge) { 6311 p = _getEdgeIntersection(b, a, edge, bounds, round); 6312 p._code = _getBitCode(p, bounds); 6313 clippedPoints.push(p); 6314 } 6315 clippedPoints.push(a); 6316 6317 // else if b is inside the clip window (a->b enters the screen) 6318 } else if (!(b._code & edge)) { 6319 p = _getEdgeIntersection(b, a, edge, bounds, round); 6320 p._code = _getBitCode(p, bounds); 6321 clippedPoints.push(p); 6322 } 6323 } 6324 points = clippedPoints; 6325 } 6326 6327 return points; 6328} 6329 6330 6331var PolyUtil = (Object.freeze || Object)({ 6332 clipPolygon: clipPolygon 6333}); 6334 6335/* 6336 * @namespace Projection 6337 * @section 6338 * Leaflet comes with a set of already defined Projections out of the box: 6339 * 6340 * @projection L.Projection.LonLat 6341 * 6342 * Equirectangular, or Plate Carree projection â the most simple projection, 6343 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6344 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6345 * `EPSG:4326` and `Simple` CRS. 6346 */ 6347 6348var LonLat = { 6349 project: function (latlng) { 6350 return new Point(latlng.lng, latlng.lat); 6351 }, 6352 6353 unproject: function (point) { 6354 return new LatLng(point.y, point.x); 6355 }, 6356 6357 bounds: new Bounds([-180, -90], [180, 90]) 6358}; 6359 6360/* 6361 * @namespace Projection 6362 * @projection L.Projection.Mercator 6363 * 6364 * Elliptical Mercator projection â more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS. 6365 */ 6366 6367var Mercator = { 6368 R: 6378137, 6369 R_MINOR: 6356752.314245179, 6370 6371 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6372 6373 project: function (latlng) { 6374 var d = Math.PI / 180, 6375 r = this.R, 6376 y = latlng.lat * d, 6377 tmp = this.R_MINOR / r, 6378 e = Math.sqrt(1 - tmp * tmp), 6379 con = e * Math.sin(y); 6380 6381 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6382 y = -r * Math.log(Math.max(ts, 1E-10)); 6383 6384 return new Point(latlng.lng * d * r, y); 6385 }, 6386 6387 unproject: function (point) { 6388 var d = 180 / Math.PI, 6389 r = this.R, 6390 tmp = this.R_MINOR / r, 6391 e = Math.sqrt(1 - tmp * tmp), 6392 ts = Math.exp(-point.y / r), 6393 phi = Math.PI / 2 - 2 * Math.atan(ts); 6394 6395 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6396 con = e * Math.sin(phi); 6397 con = Math.pow((1 - con) / (1 + con), e / 2); 6398 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6399 phi += dphi; 6400 } 6401 6402 return new LatLng(phi * d, point.x * d / r); 6403 } 6404}; 6405 6406/* 6407 * @class Projection 6408 6409 * An object with methods for projecting geographical coordinates of the world onto 6410 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). 6411 6412 * @property bounds: Bounds 6413 * The bounds (specified in CRS units) where the projection is valid 6414 6415 * @method project(latlng: LatLng): Point 6416 * Projects geographical coordinates into a 2D point. 6417 * Only accepts actual `L.LatLng` instances, not arrays. 6418 6419 * @method unproject(point: Point): LatLng 6420 * The inverse of `project`. Projects a 2D point into a geographical location. 6421 * Only accepts actual `L.Point` instances, not arrays. 6422 6423 * Note that the projection instances do not inherit from Leafet's `Class` object, 6424 * and can't be instantiated. Also, new classes can't inherit from them, 6425 * and methods can't be added to them with the `include` function. 6426 6427 */ 6428 6429 6430 6431 6432var index = (Object.freeze || Object)({ 6433 LonLat: LonLat, 6434 Mercator: Mercator, 6435 SphericalMercator: SphericalMercator 6436}); 6437 6438/* 6439 * @namespace CRS 6440 * @crs L.CRS.EPSG3395 6441 * 6442 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6443 */ 6444var EPSG3395 = extend({}, Earth, { 6445 code: 'EPSG:3395', 6446 projection: Mercator, 6447 6448 transformation: (function () { 6449 var scale = 0.5 / (Math.PI * Mercator.R); 6450 return toTransformation(scale, 0.5, -scale, 0.5); 6451 }()) 6452}); 6453 6454/* 6455 * @namespace CRS 6456 * @crs L.CRS.EPSG4326 6457 * 6458 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6459 * 6460 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6461 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6462 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6463 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 6464 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6465 */ 6466 6467var EPSG4326 = extend({}, Earth, { 6468 code: 'EPSG:4326', 6469 projection: LonLat, 6470 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 6471}); 6472 6473/* 6474 * @namespace CRS 6475 * @crs L.CRS.Simple 6476 * 6477 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6478 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6479 * axis should still be inverted (going from bottom to top). `distance()` returns 6480 * simple euclidean distance. 6481 */ 6482 6483var Simple = extend({}, CRS, { 6484 projection: LonLat, 6485 transformation: toTransformation(1, 0, -1, 0), 6486 6487 scale: function (zoom) { 6488 return Math.pow(2, zoom); 6489 }, 6490 6491 zoom: function (scale) { 6492 return Math.log(scale) / Math.LN2; 6493 }, 6494 6495 distance: function (latlng1, latlng2) { 6496 var dx = latlng2.lng - latlng1.lng, 6497 dy = latlng2.lat - latlng1.lat; 6498 6499 return Math.sqrt(dx * dx + dy * dy); 6500 }, 6501 6502 infinite: true 6503}); 6504 6505CRS.Earth = Earth; 6506CRS.EPSG3395 = EPSG3395; 6507CRS.EPSG3857 = EPSG3857; 6508CRS.EPSG900913 = EPSG900913; 6509CRS.EPSG4326 = EPSG4326; 6510CRS.Simple = Simple; 6511 6512/* 6513 * @class Layer 6514 * @inherits Evented 6515 * @aka L.Layer 6516 * @aka ILayer 6517 * 6518 * A set of methods from the Layer base class that all Leaflet layers use. 6519 * Inherits all methods, options and events from `L.Evented`. 6520 * 6521 * @example 6522 * 6523 * ```js 6524 * var layer = L.marker(latlng).addTo(map); 6525 * layer.addTo(map); 6526 * layer.remove(); 6527 * ``` 6528 * 6529 * @event add: Event 6530 * Fired after the layer is added to a map 6531 * 6532 * @event remove: Event 6533 * Fired after the layer is removed from a map 6534 */ 6535 6536 6537var Layer = Evented.extend({ 6538 6539 // Classes extending `L.Layer` will inherit the following options: 6540 options: { 6541 // @option pane: String = 'overlayPane' 6542 // 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. 6543 pane: 'overlayPane', 6544 6545 // @option attribution: String = null 6546 // 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. 6547 attribution: null, 6548 6549 bubblingMouseEvents: true 6550 }, 6551 6552 /* @section 6553 * Classes extending `L.Layer` will inherit the following methods: 6554 * 6555 * @method addTo(map: Map|LayerGroup): this 6556 * Adds the layer to the given map or layer group. 6557 */ 6558 addTo: function (map) { 6559 map.addLayer(this); 6560 return this; 6561 }, 6562 6563 // @method remove: this 6564 // Removes the layer from the map it is currently active on. 6565 remove: function () { 6566 return this.removeFrom(this._map || this._mapToAdd); 6567 }, 6568 6569 // @method removeFrom(map: Map): this 6570 // Removes the layer from the given map 6571 removeFrom: function (obj) { 6572 if (obj) { 6573 obj.removeLayer(this); 6574 } 6575 return this; 6576 }, 6577 6578 // @method getPane(name? : String): HTMLElement 6579 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 6580 getPane: function (name) { 6581 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 6582 }, 6583 6584 addInteractiveTarget: function (targetEl) { 6585 this._map._targets[stamp(targetEl)] = this; 6586 return this; 6587 }, 6588 6589 removeInteractiveTarget: function (targetEl) { 6590 delete this._map._targets[stamp(targetEl)]; 6591 return this; 6592 }, 6593 6594 // @method getAttribution: String 6595 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 6596 getAttribution: function () { 6597 return this.options.attribution; 6598 }, 6599 6600 _layerAdd: function (e) { 6601 var map = e.target; 6602 6603 // check in case layer gets added and then removed before the map is ready 6604 if (!map.hasLayer(this)) { return; } 6605 6606 this._map = map; 6607 this._zoomAnimated = map._zoomAnimated; 6608 6609 if (this.getEvents) { 6610 var events = this.getEvents(); 6611 map.on(events, this); 6612 this.once('remove', function () { 6613 map.off(events, this); 6614 }, this); 6615 } 6616 6617 this.onAdd(map); 6618 6619 if (this.getAttribution && map.attributionControl) { 6620 map.attributionControl.addAttribution(this.getAttribution()); 6621 } 6622 6623 this.fire('add'); 6624 map.fire('layeradd', {layer: this}); 6625 } 6626}); 6627 6628/* @section Extension methods 6629 * @uninheritable 6630 * 6631 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 6632 * 6633 * @method onAdd(map: Map): this 6634 * 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). 6635 * 6636 * @method onRemove(map: Map): this 6637 * 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). 6638 * 6639 * @method getEvents(): Object 6640 * 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. 6641 * 6642 * @method getAttribution(): String 6643 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 6644 * 6645 * @method beforeAdd(map: Map): this 6646 * 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. 6647 */ 6648 6649 6650/* @namespace Map 6651 * @section Layer events 6652 * 6653 * @event layeradd: LayerEvent 6654 * Fired when a new layer is added to the map. 6655 * 6656 * @event layerremove: LayerEvent 6657 * Fired when some layer is removed from the map 6658 * 6659 * @section Methods for Layers and Controls 6660 */ 6661Map.include({ 6662 // @method addLayer(layer: Layer): this 6663 // Adds the given layer to the map 6664 addLayer: function (layer) { 6665 if (!layer._layerAdd) { 6666 throw new Error('The provided object is not a Layer.'); 6667 } 6668 6669 var id = stamp(layer); 6670 if (this._layers[id]) { return this; } 6671 this._layers[id] = layer; 6672 6673 layer._mapToAdd = this; 6674 6675 if (layer.beforeAdd) { 6676 layer.beforeAdd(this); 6677 } 6678 6679 this.whenReady(layer._layerAdd, layer); 6680 6681 return this; 6682 }, 6683 6684 // @method removeLayer(layer: Layer): this 6685 // Removes the given layer from the map. 6686 removeLayer: function (layer) { 6687 var id = stamp(layer); 6688 6689 if (!this._layers[id]) { return this; } 6690 6691 if (this._loaded) { 6692 layer.onRemove(this); 6693 } 6694 6695 if (layer.getAttribution && this.attributionControl) { 6696 this.attributionControl.removeAttribution(layer.getAttribution()); 6697 } 6698 6699 delete this._layers[id]; 6700 6701 if (this._loaded) { 6702 this.fire('layerremove', {layer: layer}); 6703 layer.fire('remove'); 6704 } 6705 6706 layer._map = layer._mapToAdd = null; 6707 6708 return this; 6709 }, 6710 6711 // @method hasLayer(layer: Layer): Boolean 6712 // Returns `true` if the given layer is currently added to the map 6713 hasLayer: function (layer) { 6714 return !!layer && (stamp(layer) in this._layers); 6715 }, 6716 6717 /* @method eachLayer(fn: Function, context?: Object): this 6718 * Iterates over the layers of the map, optionally specifying context of the iterator function. 6719 * ``` 6720 * map.eachLayer(function(layer){ 6721 * layer.bindPopup('Hello'); 6722 * }); 6723 * ``` 6724 */ 6725 eachLayer: function (method, context) { 6726 for (var i in this._layers) { 6727 method.call(context, this._layers[i]); 6728 } 6729 return this; 6730 }, 6731 6732 _addLayers: function (layers) { 6733 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 6734 6735 for (var i = 0, len = layers.length; i < len; i++) { 6736 this.addLayer(layers[i]); 6737 } 6738 }, 6739 6740 _addZoomLimit: function (layer) { 6741 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 6742 this._zoomBoundLayers[stamp(layer)] = layer; 6743 this._updateZoomLevels(); 6744 } 6745 }, 6746 6747 _removeZoomLimit: function (layer) { 6748 var id = stamp(layer); 6749 6750 if (this._zoomBoundLayers[id]) { 6751 delete this._zoomBoundLayers[id]; 6752 this._updateZoomLevels(); 6753 } 6754 }, 6755 6756 _updateZoomLevels: function () { 6757 var minZoom = Infinity, 6758 maxZoom = -Infinity, 6759 oldZoomSpan = this._getZoomSpan(); 6760 6761 for (var i in this._zoomBoundLayers) { 6762 var options = this._zoomBoundLayers[i].options; 6763 6764 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 6765 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 6766 } 6767 6768 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 6769 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 6770 6771 // @section Map state change events 6772 // @event zoomlevelschange: Event 6773 // Fired when the number of zoomlevels on the map is changed due 6774 // to adding or removing a layer. 6775 if (oldZoomSpan !== this._getZoomSpan()) { 6776 this.fire('zoomlevelschange'); 6777 } 6778 6779 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 6780 this.setZoom(this._layersMaxZoom); 6781 } 6782 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 6783 this.setZoom(this._layersMinZoom); 6784 } 6785 } 6786}); 6787 6788/* 6789 * @class LayerGroup 6790 * @aka L.LayerGroup 6791 * @inherits Layer 6792 * 6793 * Used to group several layers and handle them as one. If you add it to the map, 6794 * any layers added or removed from the group will be added/removed on the map as 6795 * well. Extends `Layer`. 6796 * 6797 * @example 6798 * 6799 * ```js 6800 * L.layerGroup([marker1, marker2]) 6801 * .addLayer(polyline) 6802 * .addTo(map); 6803 * ``` 6804 */ 6805 6806var LayerGroup = Layer.extend({ 6807 6808 initialize: function (layers, options) { 6809 setOptions(this, options); 6810 6811 this._layers = {}; 6812 6813 var i, len; 6814 6815 if (layers) { 6816 for (i = 0, len = layers.length; i < len; i++) { 6817 this.addLayer(layers[i]); 6818 } 6819 } 6820 }, 6821 6822 // @method addLayer(layer: Layer): this 6823 // Adds the given layer to the group. 6824 addLayer: function (layer) { 6825 var id = this.getLayerId(layer); 6826 6827 this._layers[id] = layer; 6828 6829 if (this._map) { 6830 this._map.addLayer(layer); 6831 } 6832 6833 return this; 6834 }, 6835 6836 // @method removeLayer(layer: Layer): this 6837 // Removes the given layer from the group. 6838 // @alternative 6839 // @method removeLayer(id: Number): this 6840 // Removes the layer with the given internal ID from the group. 6841 removeLayer: function (layer) { 6842 var id = layer in this._layers ? layer : this.getLayerId(layer); 6843 6844 if (this._map && this._layers[id]) { 6845 this._map.removeLayer(this._layers[id]); 6846 } 6847 6848 delete this._layers[id]; 6849 6850 return this; 6851 }, 6852 6853 // @method hasLayer(layer: Layer): Boolean 6854 // Returns `true` if the given layer is currently added to the group. 6855 // @alternative 6856 // @method hasLayer(id: Number): Boolean 6857 // Returns `true` if the given internal ID is currently added to the group. 6858 hasLayer: function (layer) { 6859 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); 6860 }, 6861 6862 // @method clearLayers(): this 6863 // Removes all the layers from the group. 6864 clearLayers: function () { 6865 return this.eachLayer(this.removeLayer, this); 6866 }, 6867 6868 // @method invoke(methodName: String, â¦): this 6869 // Calls `methodName` on every layer contained in this group, passing any 6870 // additional parameters. Has no effect if the layers contained do not 6871 // implement `methodName`. 6872 invoke: function (methodName) { 6873 var args = Array.prototype.slice.call(arguments, 1), 6874 i, layer; 6875 6876 for (i in this._layers) { 6877 layer = this._layers[i]; 6878 6879 if (layer[methodName]) { 6880 layer[methodName].apply(layer, args); 6881 } 6882 } 6883 6884 return this; 6885 }, 6886 6887 onAdd: function (map) { 6888 this.eachLayer(map.addLayer, map); 6889 }, 6890 6891 onRemove: function (map) { 6892 this.eachLayer(map.removeLayer, map); 6893 }, 6894 6895 // @method eachLayer(fn: Function, context?: Object): this 6896 // Iterates over the layers of the group, optionally specifying context of the iterator function. 6897 // ```js 6898 // group.eachLayer(function (layer) { 6899 // layer.bindPopup('Hello'); 6900 // }); 6901 // ``` 6902 eachLayer: function (method, context) { 6903 for (var i in this._layers) { 6904 method.call(context, this._layers[i]); 6905 } 6906 return this; 6907 }, 6908 6909 // @method getLayer(id: Number): Layer 6910 // Returns the layer with the given internal ID. 6911 getLayer: function (id) { 6912 return this._layers[id]; 6913 }, 6914 6915 // @method getLayers(): Layer[] 6916 // Returns an array of all the layers added to the group. 6917 getLayers: function () { 6918 var layers = []; 6919 this.eachLayer(layers.push, layers); 6920 return layers; 6921 }, 6922 6923 // @method setZIndex(zIndex: Number): this 6924 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 6925 setZIndex: function (zIndex) { 6926 return this.invoke('setZIndex', zIndex); 6927 }, 6928 6929 // @method getLayerId(layer: Layer): Number 6930 // Returns the internal ID for a layer 6931 getLayerId: function (layer) { 6932 return stamp(layer); 6933 } 6934}); 6935 6936 6937// @factory L.layerGroup(layers?: Layer[], options?: Object) 6938// Create a layer group, optionally given an initial set of layers and an `options` object. 6939var layerGroup = function (layers, options) { 6940 return new LayerGroup(layers, options); 6941}; 6942 6943/* 6944 * @class FeatureGroup 6945 * @aka L.FeatureGroup 6946 * @inherits LayerGroup 6947 * 6948 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 6949 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 6950 * * Events are propagated to the `FeatureGroup`, so if the group has an event 6951 * handler, it will handle events from any of the layers. This includes mouse events 6952 * and custom events. 6953 * * Has `layeradd` and `layerremove` events 6954 * 6955 * @example 6956 * 6957 * ```js 6958 * L.featureGroup([marker1, marker2, polyline]) 6959 * .bindPopup('Hello world!') 6960 * .on('click', function() { alert('Clicked on a member of the group!'); }) 6961 * .addTo(map); 6962 * ``` 6963 */ 6964 6965var FeatureGroup = LayerGroup.extend({ 6966 6967 addLayer: function (layer) { 6968 if (this.hasLayer(layer)) { 6969 return this; 6970 } 6971 6972 layer.addEventParent(this); 6973 6974 LayerGroup.prototype.addLayer.call(this, layer); 6975 6976 // @event layeradd: LayerEvent 6977 // Fired when a layer is added to this `FeatureGroup` 6978 return this.fire('layeradd', {layer: layer}); 6979 }, 6980 6981 removeLayer: function (layer) { 6982 if (!this.hasLayer(layer)) { 6983 return this; 6984 } 6985 if (layer in this._layers) { 6986 layer = this._layers[layer]; 6987 } 6988 6989 layer.removeEventParent(this); 6990 6991 LayerGroup.prototype.removeLayer.call(this, layer); 6992 6993 // @event layerremove: LayerEvent 6994 // Fired when a layer is removed from this `FeatureGroup` 6995 return this.fire('layerremove', {layer: layer}); 6996 }, 6997 6998 // @method setStyle(style: Path options): this 6999 // Sets the given path options to each layer of the group that has a `setStyle` method. 7000 setStyle: function (style) { 7001 return this.invoke('setStyle', style); 7002 }, 7003 7004 // @method bringToFront(): this 7005 // Brings the layer group to the top of all other layers 7006 bringToFront: function () { 7007 return this.invoke('bringToFront'); 7008 }, 7009 7010 // @method bringToBack(): this 7011 // Brings the layer group to the back of all other layers 7012 bringToBack: function () { 7013 return this.invoke('bringToBack'); 7014 }, 7015 7016 // @method getBounds(): LatLngBounds 7017 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 7018 getBounds: function () { 7019 var bounds = new LatLngBounds(); 7020 7021 for (var id in this._layers) { 7022 var layer = this._layers[id]; 7023 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 7024 } 7025 return bounds; 7026 } 7027}); 7028 7029// @factory L.featureGroup(layers: Layer[]) 7030// Create a feature group, optionally given an initial set of layers. 7031var featureGroup = function (layers) { 7032 return new FeatureGroup(layers); 7033}; 7034 7035/* 7036 * @class Icon 7037 * @aka L.Icon 7038 * 7039 * Represents an icon to provide when creating a marker. 7040 * 7041 * @example 7042 * 7043 * ```js 7044 * var myIcon = L.icon({ 7045 * iconUrl: 'my-icon.png', 7046 * iconRetinaUrl: '[email protected]', 7047 * iconSize: [38, 95], 7048 * iconAnchor: [22, 94], 7049 * popupAnchor: [-3, -76], 7050 * shadowUrl: 'my-icon-shadow.png', 7051 * shadowRetinaUrl: '[email protected]', 7052 * shadowSize: [68, 95], 7053 * shadowAnchor: [22, 94] 7054 * }); 7055 * 7056 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 7057 * ``` 7058 * 7059 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 7060 * 7061 */ 7062 7063var Icon = Class.extend({ 7064 7065 /* @section 7066 * @aka Icon options 7067 * 7068 * @option iconUrl: String = null 7069 * **(required)** The URL to the icon image (absolute or relative to your script path). 7070 * 7071 * @option iconRetinaUrl: String = null 7072 * The URL to a retina sized version of the icon image (absolute or relative to your 7073 * script path). Used for Retina screen devices. 7074 * 7075 * @option iconSize: Point = null 7076 * Size of the icon image in pixels. 7077 * 7078 * @option iconAnchor: Point = null 7079 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 7080 * will be aligned so that this point is at the marker's geographical location. Centered 7081 * by default if size is specified, also can be set in CSS with negative margins. 7082 * 7083 * @option popupAnchor: Point = [0, 0] 7084 * The coordinates of the point from which popups will "open", relative to the icon anchor. 7085 * 7086 * @option tooltipAnchor: Point = [0, 0] 7087 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7088 * 7089 * @option shadowUrl: String = null 7090 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7091 * 7092 * @option shadowRetinaUrl: String = null 7093 * 7094 * @option shadowSize: Point = null 7095 * Size of the shadow image in pixels. 7096 * 7097 * @option shadowAnchor: Point = null 7098 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7099 * as iconAnchor if not specified). 7100 * 7101 * @option className: String = '' 7102 * A custom class name to assign to both icon and shadow images. Empty by default. 7103 */ 7104 7105 options: { 7106 popupAnchor: [0, 0], 7107 tooltipAnchor: [0, 0] 7108 }, 7109 7110 initialize: function (options) { 7111 setOptions(this, options); 7112 }, 7113 7114 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7115 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7116 // styled according to the options. 7117 createIcon: function (oldIcon) { 7118 return this._createIcon('icon', oldIcon); 7119 }, 7120 7121 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7122 // As `createIcon`, but for the shadow beneath it. 7123 createShadow: function (oldIcon) { 7124 return this._createIcon('shadow', oldIcon); 7125 }, 7126 7127 _createIcon: function (name, oldIcon) { 7128 var src = this._getIconUrl(name); 7129 7130 if (!src) { 7131 if (name === 'icon') { 7132 throw new Error('iconUrl not set in Icon options (see the docs).'); 7133 } 7134 return null; 7135 } 7136 7137 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7138 this._setIconStyles(img, name); 7139 7140 return img; 7141 }, 7142 7143 _setIconStyles: function (img, name) { 7144 var options = this.options; 7145 var sizeOption = options[name + 'Size']; 7146 7147 if (typeof sizeOption === 'number') { 7148 sizeOption = [sizeOption, sizeOption]; 7149 } 7150 7151 var size = toPoint(sizeOption), 7152 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7153 size && size.divideBy(2, true)); 7154 7155 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7156 7157 if (anchor) { 7158 img.style.marginLeft = (-anchor.x) + 'px'; 7159 img.style.marginTop = (-anchor.y) + 'px'; 7160 } 7161 7162 if (size) { 7163 img.style.width = size.x + 'px'; 7164 img.style.height = size.y + 'px'; 7165 } 7166 }, 7167 7168 _createImg: function (src, el) { 7169 el = el || document.createElement('img'); 7170 el.src = src; 7171 return el; 7172 }, 7173 7174 _getIconUrl: function (name) { 7175 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7176 } 7177}); 7178 7179 7180// @factory L.icon(options: Icon options) 7181// Creates an icon instance with the given options. 7182function icon(options) { 7183 return new Icon(options); 7184} 7185 7186/* 7187 * @miniclass Icon.Default (Icon) 7188 * @aka L.Icon.Default 7189 * @section 7190 * 7191 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7192 * no icon is specified. Points to the blue marker image distributed with Leaflet 7193 * releases. 7194 * 7195 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7196 * (which is a set of `Icon options`). 7197 * 7198 * If you want to _completely_ replace the default icon, override the 7199 * `L.Marker.prototype.options.icon` with your own icon instead. 7200 */ 7201 7202var IconDefault = Icon.extend({ 7203 7204 options: { 7205 iconUrl: 'marker-icon.png', 7206 iconRetinaUrl: 'marker-icon-2x.png', 7207 shadowUrl: 'marker-shadow.png', 7208 iconSize: [25, 41], 7209 iconAnchor: [12, 41], 7210 popupAnchor: [1, -34], 7211 tooltipAnchor: [16, -28], 7212 shadowSize: [41, 41] 7213 }, 7214 7215 _getIconUrl: function (name) { 7216 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only 7217 IconDefault.imagePath = this._detectIconPath(); 7218 } 7219 7220 // @option imagePath: String 7221 // `Icon.Default` will try to auto-detect the location of the 7222 // blue icon images. If you are placing these images in a non-standard 7223 // way, set this option to point to the right path. 7224 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7225 }, 7226 7227 _detectIconPath: function () { 7228 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7229 var path = getStyle(el, 'background-image') || 7230 getStyle(el, 'backgroundImage'); // IE8 7231 7232 document.body.removeChild(el); 7233 7234 if (path === null || path.indexOf('url') !== 0) { 7235 path = ''; 7236 } else { 7237 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, ''); 7238 } 7239 7240 return path; 7241 } 7242}); 7243 7244/* 7245 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7246 */ 7247 7248 7249/* @namespace Marker 7250 * @section Interaction handlers 7251 * 7252 * 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: 7253 * 7254 * ```js 7255 * marker.dragging.disable(); 7256 * ``` 7257 * 7258 * @property dragging: Handler 7259 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7260 */ 7261 7262var MarkerDrag = Handler.extend({ 7263 initialize: function (marker) { 7264 this._marker = marker; 7265 }, 7266 7267 addHooks: function () { 7268 var icon = this._marker._icon; 7269 7270 if (!this._draggable) { 7271 this._draggable = new Draggable(icon, icon, true); 7272 } 7273 7274 this._draggable.on({ 7275 dragstart: this._onDragStart, 7276 predrag: this._onPreDrag, 7277 drag: this._onDrag, 7278 dragend: this._onDragEnd 7279 }, this).enable(); 7280 7281 addClass(icon, 'leaflet-marker-draggable'); 7282 }, 7283 7284 removeHooks: function () { 7285 this._draggable.off({ 7286 dragstart: this._onDragStart, 7287 predrag: this._onPreDrag, 7288 drag: this._onDrag, 7289 dragend: this._onDragEnd 7290 }, this).disable(); 7291 7292 if (this._marker._icon) { 7293 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7294 } 7295 }, 7296 7297 moved: function () { 7298 return this._draggable && this._draggable._moved; 7299 }, 7300 7301 _adjustPan: function (e) { 7302 var marker = this._marker, 7303 map = marker._map, 7304 speed = this._marker.options.autoPanSpeed, 7305 padding = this._marker.options.autoPanPadding, 7306 iconPos = getPosition(marker._icon), 7307 bounds = map.getPixelBounds(), 7308 origin = map.getPixelOrigin(); 7309 7310 var panBounds = toBounds( 7311 bounds.min._subtract(origin).add(padding), 7312 bounds.max._subtract(origin).subtract(padding) 7313 ); 7314 7315 if (!panBounds.contains(iconPos)) { 7316 // Compute incremental movement 7317 var movement = toPoint( 7318 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7319 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7320 7321 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7322 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7323 ).multiplyBy(speed); 7324 7325 map.panBy(movement, {animate: false}); 7326 7327 this._draggable._newPos._add(movement); 7328 this._draggable._startPos._add(movement); 7329 7330 setPosition(marker._icon, this._draggable._newPos); 7331 this._onDrag(e); 7332 7333 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7334 } 7335 }, 7336 7337 _onDragStart: function () { 7338 // @section Dragging events 7339 // @event dragstart: Event 7340 // Fired when the user starts dragging the marker. 7341 7342 // @event movestart: Event 7343 // Fired when the marker starts moving (because of dragging). 7344 7345 this._oldLatLng = this._marker.getLatLng(); 7346 this._marker 7347 .closePopup() 7348 .fire('movestart') 7349 .fire('dragstart'); 7350 }, 7351 7352 _onPreDrag: function (e) { 7353 if (this._marker.options.autoPan) { 7354 cancelAnimFrame(this._panRequest); 7355 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7356 } 7357 }, 7358 7359 _onDrag: function (e) { 7360 var marker = this._marker, 7361 shadow = marker._shadow, 7362 iconPos = getPosition(marker._icon), 7363 latlng = marker._map.layerPointToLatLng(iconPos); 7364 7365 // update shadow position 7366 if (shadow) { 7367 setPosition(shadow, iconPos); 7368 } 7369 7370 marker._latlng = latlng; 7371 e.latlng = latlng; 7372 e.oldLatLng = this._oldLatLng; 7373 7374 // @event drag: Event 7375 // Fired repeatedly while the user drags the marker. 7376 marker 7377 .fire('move', e) 7378 .fire('drag', e); 7379 }, 7380 7381 _onDragEnd: function (e) { 7382 // @event dragend: DragEndEvent 7383 // Fired when the user stops dragging the marker. 7384 7385 cancelAnimFrame(this._panRequest); 7386 7387 // @event moveend: Event 7388 // Fired when the marker stops moving (because of dragging). 7389 delete this._oldLatLng; 7390 this._marker 7391 .fire('moveend') 7392 .fire('dragend', e); 7393 } 7394}); 7395 7396/* 7397 * @class Marker 7398 * @inherits Interactive layer 7399 * @aka L.Marker 7400 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7401 * 7402 * @example 7403 * 7404 * ```js 7405 * L.marker([50.5, 30.5]).addTo(map); 7406 * ``` 7407 */ 7408 7409var Marker = Layer.extend({ 7410 7411 // @section 7412 // @aka Marker options 7413 options: { 7414 // @option icon: Icon = * 7415 // Icon instance to use for rendering the marker. 7416 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7417 // If not specified, a common instance of `L.Icon.Default` is used. 7418 icon: new IconDefault(), 7419 7420 // Option inherited from "Interactive layer" abstract class 7421 interactive: true, 7422 7423 // @option keyboard: Boolean = true 7424 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7425 keyboard: true, 7426 7427 // @option title: String = '' 7428 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7429 title: '', 7430 7431 // @option alt: String = '' 7432 // Text for the `alt` attribute of the icon image (useful for accessibility). 7433 alt: '', 7434 7435 // @option zIndexOffset: Number = 0 7436 // 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). 7437 zIndexOffset: 0, 7438 7439 // @option opacity: Number = 1.0 7440 // The opacity of the marker. 7441 opacity: 1, 7442 7443 // @option riseOnHover: Boolean = false 7444 // If `true`, the marker will get on top of others when you hover the mouse over it. 7445 riseOnHover: false, 7446 7447 // @option riseOffset: Number = 250 7448 // The z-index offset used for the `riseOnHover` feature. 7449 riseOffset: 250, 7450 7451 // @option pane: String = 'markerPane' 7452 // `Map pane` where the markers icon will be added. 7453 pane: 'markerPane', 7454 7455 // @option pane: String = 'shadowPane' 7456 // `Map pane` where the markers shadow will be added. 7457 shadowPane: 'shadowPane', 7458 7459 // @option bubblingMouseEvents: Boolean = false 7460 // When `true`, a mouse event on this marker will trigger the same event on the map 7461 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7462 bubblingMouseEvents: false, 7463 7464 // @section Draggable marker options 7465 // @option draggable: Boolean = false 7466 // Whether the marker is draggable with mouse/touch or not. 7467 draggable: false, 7468 7469 // @option autoPan: Boolean = false 7470 // Whether to pan the map when dragging this marker near its edge or not. 7471 autoPan: false, 7472 7473 // @option autoPanPadding: Point = Point(50, 50) 7474 // Distance (in pixels to the left/right and to the top/bottom) of the 7475 // map edge to start panning the map. 7476 autoPanPadding: [50, 50], 7477 7478 // @option autoPanSpeed: Number = 10 7479 // Number of pixels the map should pan by. 7480 autoPanSpeed: 10 7481 }, 7482 7483 /* @section 7484 * 7485 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 7486 */ 7487 7488 initialize: function (latlng, options) { 7489 setOptions(this, options); 7490 this._latlng = toLatLng(latlng); 7491 }, 7492 7493 onAdd: function (map) { 7494 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 7495 7496 if (this._zoomAnimated) { 7497 map.on('zoomanim', this._animateZoom, this); 7498 } 7499 7500 this._initIcon(); 7501 this.update(); 7502 }, 7503 7504 onRemove: function (map) { 7505 if (this.dragging && this.dragging.enabled()) { 7506 this.options.draggable = true; 7507 this.dragging.removeHooks(); 7508 } 7509 delete this.dragging; 7510 7511 if (this._zoomAnimated) { 7512 map.off('zoomanim', this._animateZoom, this); 7513 } 7514 7515 this._removeIcon(); 7516 this._removeShadow(); 7517 }, 7518 7519 getEvents: function () { 7520 return { 7521 zoom: this.update, 7522 viewreset: this.update 7523 }; 7524 }, 7525 7526 // @method getLatLng: LatLng 7527 // Returns the current geographical position of the marker. 7528 getLatLng: function () { 7529 return this._latlng; 7530 }, 7531 7532 // @method setLatLng(latlng: LatLng): this 7533 // Changes the marker position to the given point. 7534 setLatLng: function (latlng) { 7535 var oldLatLng = this._latlng; 7536 this._latlng = toLatLng(latlng); 7537 this.update(); 7538 7539 // @event move: Event 7540 // 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`. 7541 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7542 }, 7543 7544 // @method setZIndexOffset(offset: Number): this 7545 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 7546 setZIndexOffset: function (offset) { 7547 this.options.zIndexOffset = offset; 7548 return this.update(); 7549 }, 7550 7551 // @method getIcon: Icon 7552 // Returns the current icon used by the marker 7553 getIcon: function () { 7554 return this.options.icon; 7555 }, 7556 7557 // @method setIcon(icon: Icon): this 7558 // Changes the marker icon. 7559 setIcon: function (icon) { 7560 7561 this.options.icon = icon; 7562 7563 if (this._map) { 7564 this._initIcon(); 7565 this.update(); 7566 } 7567 7568 if (this._popup) { 7569 this.bindPopup(this._popup, this._popup.options); 7570 } 7571 7572 return this; 7573 }, 7574 7575 getElement: function () { 7576 return this._icon; 7577 }, 7578 7579 update: function () { 7580 7581 if (this._icon && this._map) { 7582 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 7583 this._setPos(pos); 7584 } 7585 7586 return this; 7587 }, 7588 7589 _initIcon: function () { 7590 var options = this.options, 7591 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7592 7593 var icon = options.icon.createIcon(this._icon), 7594 addIcon = false; 7595 7596 // if we're not reusing the icon, remove the old one and init new one 7597 if (icon !== this._icon) { 7598 if (this._icon) { 7599 this._removeIcon(); 7600 } 7601 addIcon = true; 7602 7603 if (options.title) { 7604 icon.title = options.title; 7605 } 7606 7607 if (icon.tagName === 'IMG') { 7608 icon.alt = options.alt || ''; 7609 } 7610 } 7611 7612 addClass(icon, classToAdd); 7613 7614 if (options.keyboard) { 7615 icon.tabIndex = '0'; 7616 } 7617 7618 this._icon = icon; 7619 7620 if (options.riseOnHover) { 7621 this.on({ 7622 mouseover: this._bringToFront, 7623 mouseout: this._resetZIndex 7624 }); 7625 } 7626 7627 var newShadow = options.icon.createShadow(this._shadow), 7628 addShadow = false; 7629 7630 if (newShadow !== this._shadow) { 7631 this._removeShadow(); 7632 addShadow = true; 7633 } 7634 7635 if (newShadow) { 7636 addClass(newShadow, classToAdd); 7637 newShadow.alt = ''; 7638 } 7639 this._shadow = newShadow; 7640 7641 7642 if (options.opacity < 1) { 7643 this._updateOpacity(); 7644 } 7645 7646 7647 if (addIcon) { 7648 this.getPane().appendChild(this._icon); 7649 } 7650 this._initInteraction(); 7651 if (newShadow && addShadow) { 7652 this.getPane(options.shadowPane).appendChild(this._shadow); 7653 } 7654 }, 7655 7656 _removeIcon: function () { 7657 if (this.options.riseOnHover) { 7658 this.off({ 7659 mouseover: this._bringToFront, 7660 mouseout: this._resetZIndex 7661 }); 7662 } 7663 7664 remove(this._icon); 7665 this.removeInteractiveTarget(this._icon); 7666 7667 this._icon = null; 7668 }, 7669 7670 _removeShadow: function () { 7671 if (this._shadow) { 7672 remove(this._shadow); 7673 } 7674 this._shadow = null; 7675 }, 7676 7677 _setPos: function (pos) { 7678 7679 if (this._icon) { 7680 setPosition(this._icon, pos); 7681 } 7682 7683 if (this._shadow) { 7684 setPosition(this._shadow, pos); 7685 } 7686 7687 this._zIndex = pos.y + this.options.zIndexOffset; 7688 7689 this._resetZIndex(); 7690 }, 7691 7692 _updateZIndex: function (offset) { 7693 if (this._icon) { 7694 this._icon.style.zIndex = this._zIndex + offset; 7695 } 7696 }, 7697 7698 _animateZoom: function (opt) { 7699 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 7700 7701 this._setPos(pos); 7702 }, 7703 7704 _initInteraction: function () { 7705 7706 if (!this.options.interactive) { return; } 7707 7708 addClass(this._icon, 'leaflet-interactive'); 7709 7710 this.addInteractiveTarget(this._icon); 7711 7712 if (MarkerDrag) { 7713 var draggable = this.options.draggable; 7714 if (this.dragging) { 7715 draggable = this.dragging.enabled(); 7716 this.dragging.disable(); 7717 } 7718 7719 this.dragging = new MarkerDrag(this); 7720 7721 if (draggable) { 7722 this.dragging.enable(); 7723 } 7724 } 7725 }, 7726 7727 // @method setOpacity(opacity: Number): this 7728 // Changes the opacity of the marker. 7729 setOpacity: function (opacity) { 7730 this.options.opacity = opacity; 7731 if (this._map) { 7732 this._updateOpacity(); 7733 } 7734 7735 return this; 7736 }, 7737 7738 _updateOpacity: function () { 7739 var opacity = this.options.opacity; 7740 7741 if (this._icon) { 7742 setOpacity(this._icon, opacity); 7743 } 7744 7745 if (this._shadow) { 7746 setOpacity(this._shadow, opacity); 7747 } 7748 }, 7749 7750 _bringToFront: function () { 7751 this._updateZIndex(this.options.riseOffset); 7752 }, 7753 7754 _resetZIndex: function () { 7755 this._updateZIndex(0); 7756 }, 7757 7758 _getPopupAnchor: function () { 7759 return this.options.icon.options.popupAnchor; 7760 }, 7761 7762 _getTooltipAnchor: function () { 7763 return this.options.icon.options.tooltipAnchor; 7764 } 7765}); 7766 7767 7768// factory L.marker(latlng: LatLng, options? : Marker options) 7769 7770// @factory L.marker(latlng: LatLng, options? : Marker options) 7771// Instantiates a Marker object given a geographical point and optionally an options object. 7772function marker(latlng, options) { 7773 return new Marker(latlng, options); 7774} 7775 7776/* 7777 * @class Path 7778 * @aka L.Path 7779 * @inherits Interactive layer 7780 * 7781 * An abstract class that contains options and constants shared between vector 7782 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 7783 */ 7784 7785var Path = Layer.extend({ 7786 7787 // @section 7788 // @aka Path options 7789 options: { 7790 // @option stroke: Boolean = true 7791 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 7792 stroke: true, 7793 7794 // @option color: String = '#3388ff' 7795 // Stroke color 7796 color: '#3388ff', 7797 7798 // @option weight: Number = 3 7799 // Stroke width in pixels 7800 weight: 3, 7801 7802 // @option opacity: Number = 1.0 7803 // Stroke opacity 7804 opacity: 1, 7805 7806 // @option lineCap: String= 'round' 7807 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 7808 lineCap: 'round', 7809 7810 // @option lineJoin: String = 'round' 7811 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 7812 lineJoin: 'round', 7813 7814 // @option dashArray: String = null 7815 // 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). 7816 dashArray: null, 7817 7818 // @option dashOffset: String = null 7819 // 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). 7820 dashOffset: null, 7821 7822 // @option fill: Boolean = depends 7823 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 7824 fill: false, 7825 7826 // @option fillColor: String = * 7827 // Fill color. Defaults to the value of the [`color`](#path-color) option 7828 fillColor: null, 7829 7830 // @option fillOpacity: Number = 0.2 7831 // Fill opacity. 7832 fillOpacity: 0.2, 7833 7834 // @option fillRule: String = 'evenodd' 7835 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 7836 fillRule: 'evenodd', 7837 7838 // className: '', 7839 7840 // Option inherited from "Interactive layer" abstract class 7841 interactive: true, 7842 7843 // @option bubblingMouseEvents: Boolean = true 7844 // When `true`, a mouse event on this path will trigger the same event on the map 7845 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7846 bubblingMouseEvents: true 7847 }, 7848 7849 beforeAdd: function (map) { 7850 // Renderer is set here because we need to call renderer.getEvents 7851 // before this.getEvents. 7852 this._renderer = map.getRenderer(this); 7853 }, 7854 7855 onAdd: function () { 7856 this._renderer._initPath(this); 7857 this._reset(); 7858 this._renderer._addPath(this); 7859 }, 7860 7861 onRemove: function () { 7862 this._renderer._removePath(this); 7863 }, 7864 7865 // @method redraw(): this 7866 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 7867 redraw: function () { 7868 if (this._map) { 7869 this._renderer._updatePath(this); 7870 } 7871 return this; 7872 }, 7873 7874 // @method setStyle(style: Path options): this 7875 // Changes the appearance of a Path based on the options in the `Path options` object. 7876 setStyle: function (style) { 7877 setOptions(this, style); 7878 if (this._renderer) { 7879 this._renderer._updateStyle(this); 7880 if (this.options.stroke && style && style.hasOwnProperty('weight')) { 7881 this._updateBounds(); 7882 } 7883 } 7884 return this; 7885 }, 7886 7887 // @method bringToFront(): this 7888 // Brings the layer to the top of all path layers. 7889 bringToFront: function () { 7890 if (this._renderer) { 7891 this._renderer._bringToFront(this); 7892 } 7893 return this; 7894 }, 7895 7896 // @method bringToBack(): this 7897 // Brings the layer to the bottom of all path layers. 7898 bringToBack: function () { 7899 if (this._renderer) { 7900 this._renderer._bringToBack(this); 7901 } 7902 return this; 7903 }, 7904 7905 getElement: function () { 7906 return this._path; 7907 }, 7908 7909 _reset: function () { 7910 // defined in child classes 7911 this._project(); 7912 this._update(); 7913 }, 7914 7915 _clickTolerance: function () { 7916 // used when doing hit detection for Canvas layers 7917 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance
vendor: 8,554 bytes, lines 7917-8239
7917; 7918 } 7919}); 7920 7921/* 7922 * @class CircleMarker 7923 * @aka L.CircleMarker 7924 * @inherits Path 7925 * 7926 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 7927 */ 7928 7929var CircleMarker = Path.extend({ 7930 7931 // @section 7932 // @aka CircleMarker options 7933 options: { 7934 fill: true, 7935 7936 // @option radius: Number = 10 7937 // Radius of the circle marker, in pixels 7938 radius: 10 7939 }, 7940 7941 initialize: function (latlng, options) { 7942 setOptions(this, options); 7943 this._latlng = toLatLng(latlng); 7944 this._radius = this.options.radius; 7945 }, 7946 7947 // @method setLatLng(latLng: LatLng): this 7948 // Sets the position of a circle marker to a new location. 7949 setLatLng: function (latlng) { 7950 var oldLatLng = this._latlng; 7951 this._latlng = toLatLng(latlng); 7952 this.redraw(); 7953 7954 // @event move: Event 7955 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 7956 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7957 }, 7958 7959 // @method getLatLng(): LatLng 7960 // Returns the current geographical position of the circle marker 7961 getLatLng: function () { 7962 return this._latlng; 7963 }, 7964 7965 // @method setRadius(radius: Number): this 7966 // Sets the radius of a circle marker. Units are in pixels. 7967 setRadius: function (radius) { 7968 this.options.radius = this._radius = radius; 7969 return this.redraw(); 7970 }, 7971 7972 // @method getRadius(): Number 7973 // Returns the current radius of the circle 7974 getRadius: function () { 7975 return this._radius; 7976 }, 7977 7978 setStyle : function (options) { 7979 var radius = options && options.radius || this._radius; 7980 Path.prototype.setStyle.call(this, options); 7981 this.setRadius(radius); 7982 return this; 7983 }, 7984 7985 _project: function () { 7986 this._point = this._map.latLngToLayerPoint(this._latlng); 7987 this._updateBounds(); 7988 }, 7989 7990 _updateBounds: function () { 7991 var r = this._radius, 7992 r2 = this._radiusY || r, 7993 w = this._clickTolerance(), 7994 p = [r + w, r2 + w]; 7995 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 7996 }, 7997 7998 _update: function () { 7999 if (this._map) { 8000 this._updatePath(); 8001 } 8002 }, 8003 8004 _updatePath: function () { 8005 this._renderer._updateCircle(this); 8006 }, 8007 8008 _empty: function () { 8009 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 8010 }, 8011 8012 // Needed by the `Canvas` renderer for interactivity 8013 _containsPoint: function (p) { 8014 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 8015 } 8016}); 8017 8018 8019// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 8020// Instantiates a circle marker object given a geographical point, and an optional options object. 8021function circleMarker(latlng, options) { 8022 return new CircleMarker(latlng, options); 8023} 8024 8025/* 8026 * @class Circle 8027 * @aka L.Circle 8028 * @inherits CircleMarker 8029 * 8030 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 8031 * 8032 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 8033 * 8034 * @example 8035 * 8036 * ```js 8037 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 8038 * ``` 8039 */ 8040 8041var Circle = CircleMarker.extend({ 8042 8043 initialize: function (latlng, options, legacyOptions) { 8044 if (typeof options === 'number') { 8045 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 8046 options = extend({}, legacyOptions, {radius: options}); 8047 } 8048 setOptions(this, options); 8049 this._latlng = toLatLng(latlng); 8050 8051 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 8052 8053 // @section 8054 // @aka Circle options 8055 // @option radius: Number; Radius of the circle, in meters. 8056 this._mRadius = this.options.radius; 8057 }, 8058 8059 // @method setRadius(radius: Number): this 8060 // Sets the radius of a circle. Units are in meters. 8061 setRadius: function (radius) { 8062 this._mRadius = radius; 8063 return this.redraw(); 8064 }, 8065 8066 // @method getRadius(): Number 8067 // Returns the current radius of a circle. Units are in meters. 8068 getRadius: function () { 8069 return this._mRadius; 8070 }, 8071 8072 // @method getBounds(): LatLngBounds 8073 // Returns the `LatLngBounds` of the path. 8074 getBounds: function () { 8075 var half = [this._radius, this._radiusY || this._radius]; 8076 8077 return new LatLngBounds( 8078 this._map.layerPointToLatLng(this._point.subtract(half)), 8079 this._map.layerPointToLatLng(this._point.add(half))); 8080 }, 8081 8082 setStyle: Path.prototype.setStyle, 8083 8084 _project: function () { 8085 8086 var lng = this._latlng.lng, 8087 lat = this._latlng.lat, 8088 map = this._map, 8089 crs = map.options.crs; 8090 8091 if (crs.distance === Earth.distance) { 8092 var d = Math.PI / 180, 8093 latR = (this._mRadius / Earth.R) / d, 8094 top = map.project([lat + latR, lng]), 8095 bottom = map.project([lat - latR, lng]), 8096 p = top.add(bottom).divideBy(2), 8097 lat2 = map.unproject(p).lat, 8098 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8099 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8100 8101 if (isNaN(lngR) || lngR === 0) { 8102 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8103 } 8104 8105 this._point = p.subtract(map.getPixelOrigin()); 8106 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 8107 this._radiusY = p.y - top.y; 8108 8109 } else { 8110 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8111 8112 this._point = map.latLngToLayerPoint(this._latlng); 8113 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8114 } 8115 8116 this._updateBounds(); 8117 } 8118}); 8119 8120// @factory L.circle(latlng: LatLng, options?: Circle options) 8121// Instantiates a circle object given a geographical point, and an options object 8122// which contains the circle radius. 8123// @alternative 8124// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8125// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8126// Do not use in new applications or plugins. 8127function circle(latlng, options, legacyOptions) { 8128 return new Circle(latlng, options, legacyOptions); 8129} 8130 8131/* 8132 * @class Polyline 8133 * @aka L.Polyline 8134 * @inherits Path 8135 * 8136 * A class for drawing polyline overlays on a map. Extends `Path`. 8137 * 8138 * @example 8139 * 8140 * ```js 8141 * // create a red polyline from an array of LatLng points 8142 * var latlngs = [ 8143 * [45.51, -122.68], 8144 * [37.77, -122.43], 8145 * [34.04, -118.2] 8146 * ]; 8147 * 8148 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8149 * 8150 * // zoom the map to the polyline 8151 * map.fitBounds(polyline.getBounds()); 8152 * ``` 8153 * 8154 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8155 * 8156 * ```js 8157 * // create a red polyline from an array of arrays of LatLng points 8158 * var latlngs = [ 8159 * [[45.51, -122.68], 8160 * [37.77, -122.43], 8161 * [34.04, -118.2]], 8162 * [[40.78, -73.91], 8163 * [41.83, -87.62], 8164 * [32.76, -96.72]] 8165 * ]; 8166 * ``` 8167 */ 8168 8169 8170var Polyline = Path.extend({ 8171 8172 // @section 8173 // @aka Polyline options 8174 options: { 8175 // @option smoothFactor: Number = 1.0 8176 // How much to simplify the polyline on each zoom level. More means 8177 // better performance and smoother look, and less means more accurate representation. 8178 smoothFactor: 1.0, 8179 8180 // @option noClip: Boolean = false 8181 // Disable polyline clipping. 8182 noClip: false 8183 }, 8184 8185 initialize: function (latlngs, options) { 8186 setOptions(this, options); 8187 this._setLatLngs(latlngs); 8188 }, 8189 8190 // @method getLatLngs(): LatLng[] 8191 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8192 getLatLngs: function () { 8193 return this._latlngs; 8194 }, 8195 8196 // @method setLatLngs(latlngs: LatLng[]): this 8197 // Replaces all the points in the polyline with the given array of geographical points. 8198 setLatLngs: function (latlngs) { 8199 this._setLatLngs(latlngs); 8200 return this.redraw(); 8201 }, 8202 8203 // @method isEmpty(): Boolean 8204 // Returns `true` if the Polyline has no LatLngs. 8205 isEmpty: function () { 8206 return !this._latlngs.length; 8207 }, 8208 8209 // @method closestLayerPoint(p: Point): Point 8210 // Returns the point closest to `p` on the Polyline. 8211 closestLayerPoint: function (p) { 8212 var minDistance = Infinity, 8213 minPoint = null, 8214 closest = _sqClosestPointOnSegment, 8215 p1, p2; 8216 8217 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8218 var points = this._parts[j]; 8219 8220 for (var i = 1, len = points.length; i < len; i++) { 8221 p1 = points[i - 1]; 8222 p2 = points[i]; 8223 8224 var sqDist = closest(p, p1, p2, true); 8225 8226 if (sqDist < minDistance) { 8227 minDistance = sqDist; 8228 minPoint = closest(p, p1, p2); 8229 } 8230 } 8231 } 8232 if (minPoint) { 8233 minPoint.distance = Math.sqrt(minDistance); 8234 } 8235 return minPoint; 8236 }, 8237 8238 // @method getCenter(): LatLng 8239 // Returns the center ([centroid](http
vendor: 11,613 bytes, lines 8239-8672
8239://en.wikipedia.org/wiki/Centroid)) of the polyline. 8240 getCenter: function () { 8241 // throws error when not yet added to map as this center calculation requires projected coordinates 8242 if (!this._map) { 8243 throw new Error('Must add layer to map before using getCenter()'); 8244 } 8245 8246 var i, halfDist, segDist, dist, p1, p2, ratio, 8247 points = this._rings[0], 8248 len = points.length; 8249 8250 if (!len) { return null; } 8251 8252 // polyline centroid algorithm; only uses the first ring if there are multiple 8253 8254 for (i = 0, halfDist = 0; i < len - 1; i++) { 8255 halfDist += points[i].distanceTo(points[i + 1]) / 2; 8256 } 8257 8258 // The line is so small in the current view that all points are on the same pixel. 8259 if (halfDist === 0) { 8260 return this._map.layerPointToLatLng(points[0]); 8261 } 8262 8263 for (i = 0, dist = 0; i < len - 1; i++) { 8264 p1 = points[i]; 8265 p2 = points[i + 1]; 8266 segDist = p1.distanceTo(p2); 8267 dist += segDist; 8268 8269 if (dist > halfDist) { 8270 ratio = (dist - halfDist) / segDist; 8271 return this._map.layerPointToLatLng([ 8272 p2.x - ratio * (p2.x - p1.x), 8273 p2.y - ratio * (p2.y - p1.y) 8274 ]); 8275 } 8276 } 8277 }, 8278 8279 // @method getBounds(): LatLngBounds 8280 // Returns the `LatLngBounds` of the path. 8281 getBounds: function () { 8282 return this._bounds; 8283 }, 8284 8285 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this 8286 // Adds a given point to the polyline. By default, adds to the first ring of 8287 // the polyline in case of a multi-polyline, but can be overridden by passing 8288 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8289 addLatLng: function (latlng, latlngs) { 8290 latlngs = latlngs || this._defaultShape(); 8291 latlng = toLatLng(latlng); 8292 latlngs.push(latlng); 8293 this._bounds.extend(latlng); 8294 return this.redraw(); 8295 }, 8296 8297 _setLatLngs: function (latlngs) { 8298 this._bounds = new LatLngBounds(); 8299 this._latlngs = this._convertLatLngs(latlngs); 8300 }, 8301 8302 _defaultShape: function () { 8303 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8304 }, 8305 8306 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8307 _convertLatLngs: function (latlngs) { 8308 var result = [], 8309 flat = isFlat(latlngs); 8310 8311 for (var i = 0, len = latlngs.length; i < len; i++) { 8312 if (flat) { 8313 result[i] = toLatLng(latlngs[i]); 8314 this._bounds.extend(result[i]); 8315 } else { 8316 result[i] = this._convertLatLngs(latlngs[i]); 8317 } 8318 } 8319 8320 return result; 8321 }, 8322 8323 _project: function () { 8324 var pxBounds = new Bounds(); 8325 this._rings = []; 8326 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8327 8328 if (this._bounds.isValid() && pxBounds.isValid()) { 8329 this._rawPxBounds = pxBounds; 8330 this._updateBounds(); 8331 } 8332 }, 8333 8334 _updateBounds: function () { 8335 var w = this._clickTolerance(), 8336 p = new Point(w, w); 8337 this._pxBounds = new Bounds([ 8338 this._rawPxBounds.min.subtract(p), 8339 this._rawPxBounds.max.add(p) 8340 ]); 8341 }, 8342 8343 // recursively turns latlngs into a set of rings with projected coordinates 8344 _projectLatlngs: function (latlngs, result, projectedBounds) { 8345 var flat = latlngs[0] instanceof LatLng, 8346 len = latlngs.length, 8347 i, ring; 8348 8349 if (flat) { 8350 ring = []; 8351 for (i = 0; i < len; i++) { 8352 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8353 projectedBounds.extend(ring[i]); 8354 } 8355 result.push(ring); 8356 } else { 8357 for (i = 0; i < len; i++) { 8358 this._projectLatlngs(latlngs[i], result, projectedBounds); 8359 } 8360 } 8361 }, 8362 8363 // clip polyline by renderer bounds so that we have less to render for performance 8364 _clipPoints: function () { 8365 var bounds = this._renderer._bounds; 8366 8367 this._parts = []; 8368 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8369 return; 8370 } 8371 8372 if (this.options.noClip) { 8373 this._parts = this._rings; 8374 return; 8375 } 8376 8377 var parts = this._parts, 8378 i, j, k, len, len2, segment, points; 8379 8380 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8381 points = this._rings[i]; 8382 8383 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8384 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8385 8386 if (!segment) { continue; } 8387 8388 parts[k] = parts[k] || []; 8389 parts[k].push(segment[0]); 8390 8391 // if segment goes out of screen, or it's the last one, it's the end of the line part 8392 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8393 parts[k].push(segment[1]); 8394 k++; 8395 } 8396 } 8397 } 8398 }, 8399 8400 // simplify each clipped part of the polyline for performance 8401 _simplifyPoints: function () { 8402 var parts = this._parts, 8403 tolerance = this.options.smoothFactor; 8404 8405 for (var i = 0, len = parts.length; i < len; i++) { 8406 parts[i] = simplify(parts[i], tolerance); 8407 } 8408 }, 8409 8410 _update: function () { 8411 if (!this._map) { return; } 8412 8413 this._clipPoints(); 8414 this._simplifyPoints(); 8415 this._updatePath(); 8416 }, 8417 8418 _updatePath: function () { 8419 this._renderer._updatePoly(this); 8420 }, 8421 8422 // Needed by the `Canvas` renderer for interactivity 8423 _containsPoint: function (p, closed) { 8424 var i, j, k, len, len2, part, 8425 w = this._clickTolerance(); 8426 8427 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8428 8429 // hit detection for polylines 8430 for (i = 0, len = this._parts.length; i < len; i++) { 8431 part = this._parts[i]; 8432 8433 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8434 if (!closed && (j === 0)) { continue; } 8435 8436 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8437 return true; 8438 } 8439 } 8440 } 8441 return false; 8442 } 8443}); 8444 8445// @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8446// Instantiates a polyline object given an array of geographical points and 8447// optionally an options object. You can create a `Polyline` object with 8448// multiple separate lines (`MultiPolyline`) by passing an array of arrays 8449// of geographic points. 8450function polyline(latlngs, options) { 8451 return new Polyline(latlngs, options); 8452} 8453 8454// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8455Polyline._flat = _flat; 8456 8457/* 8458 * @class Polygon 8459 * @aka L.Polygon 8460 * @inherits Polyline 8461 * 8462 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8463 * 8464 * 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. 8465 * 8466 * 8467 * @example 8468 * 8469 * ```js 8470 * // create a red polygon from an array of LatLng points 8471 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 8472 * 8473 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8474 * 8475 * // zoom the map to the polygon 8476 * map.fitBounds(polygon.getBounds()); 8477 * ``` 8478 * 8479 * 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: 8480 * 8481 * ```js 8482 * var latlngs = [ 8483 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8484 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8485 * ]; 8486 * ``` 8487 * 8488 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8489 * 8490 * ```js 8491 * var latlngs = [ 8492 * [ // first polygon 8493 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8494 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8495 * ], 8496 * [ // second polygon 8497 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 8498 * ] 8499 * ]; 8500 * ``` 8501 */ 8502 8503var Polygon = Polyline.extend({ 8504 8505 options: { 8506 fill: true 8507 }, 8508 8509 isEmpty: function () { 8510 return !this._latlngs.length || !this._latlngs[0].length; 8511 }, 8512 8513 getCenter: function () { 8514 // throws error when not yet added to map as this center calculation requires projected coordinates 8515 if (!this._map) { 8516 throw new Error('Must add layer to map before using getCenter()'); 8517 } 8518 8519 var i, j, p1, p2, f, area, x, y, center, 8520 points = this._rings[0], 8521 len = points.length; 8522 8523 if (!len) { return null; } 8524 8525 // polygon centroid algorithm; only uses the first ring if there are multiple 8526 8527 area = x = y = 0; 8528 8529 for (i = 0, j = len - 1; i < len; j = i++) { 8530 p1 = points[i]; 8531 p2 = points[j]; 8532 8533 f = p1.y * p2.x - p2.y * p1.x; 8534 x += (p1.x + p2.x) * f; 8535 y += (p1.y + p2.y) * f; 8536 area += f * 3; 8537 } 8538 8539 if (area === 0) { 8540 // Polygon is so small that all points are on same pixel. 8541 center = points[0]; 8542 } else { 8543 center = [x / area, y / area]; 8544 } 8545 return this._map.layerPointToLatLng(center); 8546 }, 8547 8548 _convertLatLngs: function (latlngs) { 8549 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 8550 len = result.length; 8551 8552 // remove last point if it equals first one 8553 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 8554 result.pop(); 8555 } 8556 return result; 8557 }, 8558 8559 _setLatLngs: function (latlngs) { 8560 Polyline.prototype._setLatLngs.call(this, latlngs); 8561 if (isFlat(this._latlngs)) { 8562 this._latlngs = [this._latlngs]; 8563 } 8564 }, 8565 8566 _defaultShape: function () { 8567 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8568 }, 8569 8570 _clipPoints: function () { 8571 // polygons need a different clipping algorithm so we redefine that 8572 8573 var bounds = this._renderer._bounds, 8574 w = this.options.weight, 8575 p = new Point(w, w); 8576 8577 // increase clip padding by stroke width to avoid stroke on clip edges 8578 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8579 8580 this._parts = []; 8581 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8582 return; 8583 } 8584 8585 if (this.options.noClip) { 8586 this._parts = this._rings; 8587 return; 8588 } 8589 8590 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8591 clipped = clipPolygon(this._rings[i], bounds, true); 8592 if (clipped.length) { 8593 this._parts.push(clipped); 8594 } 8595 } 8596 }, 8597 8598 _updatePath: function () { 8599 this._renderer._updatePoly(this, true); 8600 }, 8601 8602 // Needed by the `Canvas` renderer for interactivity 8603 _containsPoint: function (p) { 8604 var inside = false, 8605 part, p1, p2, i, j, k, len, len2; 8606 8607 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8608 8609 // ray casting algorithm for detecting if point is in polygon 8610 for (i = 0, len = this._parts.length; i < len; i++) { 8611 part = this._parts[i]; 8612 8613 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8614 p1 = part[j]; 8615 p2 = part[k]; 8616 8617 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)) { 8618 inside = !inside; 8619 } 8620 } 8621 } 8622 8623 // also check if it's on polygon stroke 8624 return inside || Polyline.prototype._containsPoint.call(this, p, true); 8625 } 8626 8627}); 8628 8629 8630// @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8631function polygon(latlngs, options) { 8632 return new Polygon(latlngs, options); 8633} 8634 8635/* 8636 * @class GeoJSON 8637 * @aka L.GeoJSON 8638 * @inherits FeatureGroup 8639 * 8640 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 8641 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 8642 * 8643 * @example 8644 * 8645 * ```js 8646 * L.geoJSON(data, { 8647 * style: function (feature) { 8648 * return {color: feature.properties.color}; 8649 * } 8650 * }).bindPopup(function (layer) { 8651 * return layer.feature.properties.description; 8652 * }).addTo(map); 8653 * ``` 8654 */ 8655 8656var GeoJSON = FeatureGroup.extend({ 8657 8658 /* @section 8659 * @aka GeoJSON options 8660 * 8661 * @option pointToLayer: Function = * 8662 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 8663 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 8664 * The default is to spawn a default `Marker`: 8665 * ```js 8666 * function(geoJsonPoint, latlng) { 8667 * return L.marker(latlng); 8668 * } 8669 * ``` 8670 * 8671 * @option style: Function = * 8672 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8673 * called internally when data is added. 8674 * The default value is to not override any defaults: 8675 * ```js 8676 * function (geoJsonFeature) { 8677 * return {} 8678 * } 8679 * ``` 8680 * 8681 * @option onEachFeature: Function = * 8682 * A `Function` that will be called once for each created `Feature`, after it has 8683 * been created and styled. Useful for attaching events and popups to features. 8684 * The default is to do nothing with the newly created layers: 8685 * ```js 8686 * function (feature, layer) {} 8687 * ``` 8688 * 8689 * @option filter: Function = * 8690 * A `Function` that will be used to decide whether to include a feature or not. 8691 * The default is to include all features: 8692 * ```js 8693 * function (geoJsonFeature) { 8694 * return true; 8695 * } 8696 * ``` 8697 * Note: dynamically changing the `filter` option will have effect only on newly 8698 * added data. It will _not_ re-evaluate already included features. 8699 * 8700 * @option coordsToLatLng: Function = * 8701 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 8702 * The default is the `coordsToLatLng` static method. 8703 * 8704 * @option markersInheritOptions: Boolean = false 8705 * Whether default Markers for "Point" type Features inherit from group options. 8706 */ 8707 8708 initialize: function (geojson, options) { 8709 setOptions(this, options); 8710 8711 this._layers = {}; 8712 8713 if (geojson) { 8714 this.addData(geojson); 8715 } 8716 }, 8717 8718 // @method addData( <GeoJSON> data ): this 8719 // Adds a GeoJSON object to the layer. 8720 addData: function (geojson) { 8721 var features = isArray(geojson) ? geojson : geojson.features, 8722 i, len, feature; 8723 8724 if (features) { 8725 for (i = 0, len = features.length; i < len; i++) { 8726 // only add this if geometry or geometries are set and not null 8727 feature = features[i]; 8728 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 8729 this.addData(feature); 8730 } 8731 } 8732 return this; 8733 } 8734 8735 var options = this.options; 8736 8737 if (options.filter && !options.filter(geojson)) { return this; } 8738 8739 var layer = geometryToLayer(geojson, options); 8740 if (!layer) { 8741 return this; 8742 } 8743 layer.feature = asFeature(geojson); 8744 8745 layer.defaultOptions = layer.options; 8746 this.resetStyle(layer); 8747 8748 if (options.onEachFeature) { 8749 options.onEachFeature(geojson, layer); 8750 } 8751 8752 return this.addLayer(layer); 8753 }, 8754 8755 // @method resetStyle( <Path> layer? ): this 8756 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 8757 // If `layer` is omitted, the style of all features in the current layer is reset. 8758 resetStyle: function (layer) { 8759 if (layer === undefined) { 8760 return this.eachLayer(this.resetStyle, this); 8761 } 8762 // reset any custom styles 8763 layer.options = extend({}, layer.defaultOptions); 8764 this._setLayerStyle(layer, this.options.style); 8765 return this; 8766 }, 8767 8768 // @method setStyle( <Function> style ): this 8769 // Changes styles of GeoJSON vector layers with the given style function. 8770 setStyle: function (style) { 8771 return this.eachLayer(function (layer) { 8772 this._setLayerStyle(layer, style); 8773 }, this); 8774 }, 8775 8776 _setLayerStyle: function (layer, style) { 8777 if (layer.setStyle) { 8778 if (typeof style === 'function') { 8779 style = style(layer.feature); 8780 } 8781 layer.setStyle(style); 8782 } 8783 } 8784}); 8785 8786// @section 8787// There are several static functions which can be called without instantiating L.GeoJSON: 8788 8789// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 8790// Creates a `Layer` from a given GeoJSON feature. Can use a custom 8791// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 8792// functions if provided as options. 8793function geometryToLayer(geojson, options) { 8794 8795 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 8796 coords = geometry ? geometry.coordinates : null, 8797 layers = [], 8798 pointToLayer = options && options.pointToLayer, 8799 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 8800 latlng, latlngs, i, len; 8801 8802 if (!coords && !geometry) { 8803 return null; 8804 } 8805 8806 switch (geometry.type) { 8807 case 'Point': 8808 latlng = _coordsToLatLng(coords); 8809 return _pointToLayer(pointToLayer, geojson, latlng, options); 8810 8811 case 'MultiPoint':
vendor: 25,377 bytes, lines 8812-9713
8812 for (i = 0, len = coords.length; i < len; i++) { 8813 latlng = _coordsToLatLng(coords[i]); 8814 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 8815 } 8816 return new FeatureGroup(layers); 8817 8818 case 'LineString': 8819 case 'MultiLineString': 8820 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 8821 return new Polyline(latlngs, options); 8822 8823 case 'Polygon': 8824 case 'MultiPolygon': 8825 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 8826 return new Polygon(latlngs, options); 8827 8828 case 'GeometryCollection': 8829 for (i = 0, len = geometry.geometries.length; i < len; i++) { 8830 var layer = geometryToLayer({ 8831 geometry: geometry.geometries[i], 8832 type: 'Feature', 8833 properties: geojson.properties 8834 }, options); 8835 8836 if (layer) { 8837 layers.push(layer); 8838 } 8839 } 8840 return new FeatureGroup(layers); 8841 8842 default: 8843 throw new Error('Invalid GeoJSON object.'); 8844 } 8845} 8846 8847function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 8848 return pointToLayerFn ? 8849 pointToLayerFn(geojson, latlng) : 8850 new Marker(latlng, options && options.markersInheritOptions && options); 8851} 8852 8853// @function coordsToLatLng(coords: Array): LatLng 8854// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 8855// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 8856function coordsToLatLng(coords) { 8857 return new LatLng(coords[1], coords[0], coords[2]); 8858} 8859 8860// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 8861// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 8862// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 8863// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 8864function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 8865 var latlngs = []; 8866 8867 for (var i = 0, len = coords.length, latlng; i < len; i++) { 8868 latlng = levelsDeep ? 8869 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 8870 (_coordsToLatLng || coordsToLatLng)(coords[i]); 8871 8872 latlngs.push(latlng); 8873 } 8874 8875 return latlngs; 8876} 8877 8878// @function latLngToCoords(latlng: LatLng, precision?: Number): Array 8879// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 8880function latLngToCoords(latlng, precision) { 8881 precision = typeof precision === 'number' ? precision : 6; 8882 return latlng.alt !== undefined ? 8883 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 8884 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 8885} 8886 8887// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array 8888// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 8889// `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. 8890function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 8891 var coords = []; 8892 8893 for (var i = 0, len = latlngs.length; i < len; i++) { 8894 coords.push(levelsDeep ? 8895 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : 8896 latLngToCoords(latlngs[i], precision)); 8897 } 8898 8899 if (!levelsDeep && closed) { 8900 coords.push(coords[0]); 8901 } 8902 8903 return coords; 8904} 8905 8906function getFeature(layer, newGeometry) { 8907 return layer.feature ? 8908 extend({}, layer.feature, {geometry: newGeometry}) : 8909 asFeature(newGeometry); 8910} 8911 8912// @function asFeature(geojson: Object): Object 8913// Normalize GeoJSON geometries/features into GeoJSON features. 8914function asFeature(geojson) { 8915 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 8916 return geojson; 8917 } 8918 8919 return { 8920 type: 'Feature', 8921 properties: {}, 8922 geometry: geojson 8923 }; 8924} 8925 8926var PointToGeoJSON = { 8927 toGeoJSON: function (precision) { 8928 return getFeature(this, { 8929 type: 'Point', 8930 coordinates: latLngToCoords(this.getLatLng(), precision) 8931 }); 8932 } 8933}; 8934 8935// @namespace Marker 8936// @section Other methods 8937// @method toGeoJSON(precision?: Number): Object 8938// `precision` is the number of decimal places for coordinates. 8939// The default value is 6 places. 8940// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 8941Marker.include(PointToGeoJSON); 8942 8943// @namespace CircleMarker 8944// @method toGeoJSON(precision?: Number): Object 8945// `precision` is the number of decimal places for coordinates. 8946// The default value is 6 places. 8947// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 8948Circle.include(PointToGeoJSON); 8949CircleMarker.include(PointToGeoJSON); 8950 8951 8952// @namespace Polyline 8953// @method toGeoJSON(precision?: Number): Object 8954// `precision` is the number of decimal places for coordinates. 8955// The default value is 6 places. 8956// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 8957Polyline.include({ 8958 toGeoJSON: function (precision) { 8959 var multi = !isFlat(this._latlngs); 8960 8961 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 8962 8963 return getFeature(this, { 8964 type: (multi ? 'Multi' : '') + 'LineString', 8965 coordinates: coords 8966 }); 8967 } 8968}); 8969 8970// @namespace Polygon 8971// @method toGeoJSON(precision?: Number): Object 8972// `precision` is the number of decimal places for coordinates. 8973// The default value is 6 places. 8974// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 8975Polygon.include({ 8976 toGeoJSON: function (precision) { 8977 var holes = !isFlat(this._latlngs), 8978 multi = holes && !isFlat(this._latlngs[0]); 8979 8980 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 8981 8982 if (!holes) { 8983 coords = [coords]; 8984 } 8985 8986 return getFeature(this, { 8987 type: (multi ? 'Multi' : '') + 'Polygon', 8988 coordinates: coords 8989 }); 8990 } 8991}); 8992 8993 8994// @namespace LayerGroup 8995LayerGroup.include({ 8996 toMultiPoint: function (precision) { 8997 var coords = []; 8998 8999 this.eachLayer(function (layer) { 9000 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 9001 }); 9002 9003 return getFeature(this, { 9004 type: 'MultiPoint', 9005 coordinates: coords 9006 }); 9007 }, 9008 9009 // @method toGeoJSON(precision?: Number): Object 9010 // `precision` is the number of decimal places for coordinates. 9011 // The default value is 6 places. 9012 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 9013 toGeoJSON: function (precision) { 9014 9015 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 9016 9017 if (type === 'MultiPoint') { 9018 return this.toMultiPoint(precision); 9019 } 9020 9021 var isGeometryCollection = type === 'GeometryCollection', 9022 jsons = []; 9023 9024 this.eachLayer(function (layer) { 9025 if (layer.toGeoJSON) { 9026 var json = layer.toGeoJSON(precision); 9027 if (isGeometryCollection) { 9028 jsons.push(json.geometry); 9029 } else { 9030 var feature = asFeature(json); 9031 // Squash nested feature collections 9032 if (feature.type === 'FeatureCollection') { 9033 jsons.push.apply(jsons, feature.features); 9034 } else { 9035 jsons.push(feature); 9036 } 9037 } 9038 } 9039 }); 9040 9041 if (isGeometryCollection) { 9042 return getFeature(this, { 9043 geometries: jsons, 9044 type: 'GeometryCollection' 9045 }); 9046 } 9047 9048 return { 9049 type: 'FeatureCollection', 9050 features: jsons 9051 }; 9052 } 9053}); 9054 9055// @namespace GeoJSON 9056// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9057// Creates a GeoJSON layer. Optionally accepts an object in 9058// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 9059// (you can alternatively add it later with `addData` method) and an `options` object. 9060function geoJSON(geojson, options) { 9061 return new GeoJSON(geojson, options); 9062} 9063 9064// Backward compatibility. 9065var geoJson = geoJSON; 9066 9067/* 9068 * @class ImageOverlay 9069 * @aka L.ImageOverlay 9070 * @inherits Interactive layer 9071 * 9072 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 9073 * 9074 * @example 9075 * 9076 * ```js 9077 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 9078 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 9079 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 9080 * ``` 9081 */ 9082 9083var ImageOverlay = Layer.extend({ 9084 9085 // @section 9086 // @aka ImageOverlay options 9087 options: { 9088 // @option opacity: Number = 1.0 9089 // The opacity of the image overlay. 9090 opacity: 1, 9091 9092 // @option alt: String = '' 9093 // Text for the `alt` attribute of the image (useful for accessibility). 9094 alt: '', 9095 9096 // @option interactive: Boolean = false 9097 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 9098 interactive: false, 9099 9100 // @option crossOrigin: Boolean|String = false 9101 // Whether the crossOrigin attribute will be added to the image. 9102 // 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. 9103 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 9104 crossOrigin: false, 9105 9106 // @option errorOverlayUrl: String = '' 9107 // URL to the overlay image to show in place of the overlay that failed to load. 9108 errorOverlayUrl: '', 9109 9110 // @option zIndex: Number = 1 9111 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 9112 zIndex: 1, 9113 9114 // @option className: String = '' 9115 // A custom class name to assign to the image. Empty by default. 9116 className: '' 9117 }, 9118 9119 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 9120 this._url = url; 9121 this._bounds = toLatLngBounds(bounds); 9122 9123 setOptions(this, options); 9124 }, 9125 9126 onAdd: function () { 9127 if (!this._image) { 9128 this._initImage(); 9129 9130 if (this.options.opacity < 1) { 9131 this._updateOpacity(); 9132 } 9133 } 9134 9135 if (this.options.interactive) { 9136 addClass(this._image, 'leaflet-interactive'); 9137 this.addInteractiveTarget(this._image); 9138 } 9139 9140 this.getPane().appendChild(this._image); 9141 this._reset(); 9142 }, 9143 9144 onRemove: function () { 9145 remove(this._image); 9146 if (this.options.interactive) { 9147 this.removeInteractiveTarget(this._image); 9148 } 9149 }, 9150 9151 // @method setOpacity(opacity: Number): this 9152 // Sets the opacity of the overlay. 9153 setOpacity: function (opacity) { 9154 this.options.opacity = opacity; 9155 9156 if (this._image) { 9157 this._updateOpacity(); 9158 } 9159 return this; 9160 }, 9161 9162 setStyle: function (styleOpts) { 9163 if (styleOpts.opacity) { 9164 this.setOpacity(styleOpts.opacity); 9165 } 9166 return this; 9167 }, 9168 9169 // @method bringToFront(): this 9170 // Brings the layer to the top of all overlays. 9171 bringToFront: function () { 9172 if (this._map) { 9173 toFront(this._image); 9174 } 9175 return this; 9176 }, 9177 9178 // @method bringToBack(): this 9179 // Brings the layer to the bottom of all overlays. 9180 bringToBack: function () { 9181 if (this._map) { 9182 toBack(this._image); 9183 } 9184 return this; 9185 }, 9186 9187 // @method setUrl(url: String): this 9188 // Changes the URL of the image. 9189 setUrl: function (url) { 9190 this._url = url; 9191 9192 if (this._image) { 9193 this._image.src = url; 9194 } 9195 return this; 9196 }, 9197 9198 // @method setBounds(bounds: LatLngBounds): this 9199 // Update the bounds that this ImageOverlay covers 9200 setBounds: function (bounds) { 9201 this._bounds = toLatLngBounds(bounds); 9202 9203 if (this._map) { 9204 this._reset(); 9205 } 9206 return this; 9207 }, 9208 9209 getEvents: function () { 9210 var events = { 9211 zoom: this._reset, 9212 viewreset: this._reset 9213 }; 9214 9215 if (this._zoomAnimated) { 9216 events.zoomanim = this._animateZoom; 9217 } 9218 9219 return events; 9220 }, 9221 9222 // @method setZIndex(value: Number): this 9223 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9224 setZIndex: function (value) { 9225 this.options.zIndex = value; 9226 this._updateZIndex(); 9227 return this; 9228 }, 9229 9230 // @method getBounds(): LatLngBounds 9231 // Get the bounds that this ImageOverlay covers 9232 getBounds: function () { 9233 return this._bounds; 9234 }, 9235 9236 // @method getElement(): HTMLElement 9237 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9238 // used by this overlay. 9239 getElement: function () { 9240 return this._image; 9241 }, 9242 9243 _initImage: function () { 9244 var wasElementSupplied = this._url.tagName === 'IMG'; 9245 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9246 9247 addClass(img, 'leaflet-image-layer'); 9248 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9249 if (this.options.className) { addClass(img, this.options.className); } 9250 9251 img.onselectstart = falseFn; 9252 img.onmousemove = falseFn; 9253 9254 // @event load: Event 9255 // Fired when the ImageOverlay layer has loaded its image 9256 img.onload = bind(this.fire, this, 'load'); 9257 img.onerror = bind(this._overlayOnError, this, 'error'); 9258 9259 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9260 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9261 } 9262 9263 if (this.options.zIndex) { 9264 this._updateZIndex(); 9265 } 9266 9267 if (wasElementSupplied) { 9268 this._url = img.src; 9269 return; 9270 } 9271 9272 img.src = this._url; 9273 img.alt = this.options.alt; 9274 }, 9275 9276 _animateZoom: function (e) { 9277 var scale = this._map.getZoomScale(e.zoom), 9278 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9279 9280 setTransform(this._image, offset, scale); 9281 }, 9282 9283 _reset: function () { 9284 var image = this._image, 9285 bounds = new Bounds( 9286 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9287 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9288 size = bounds.getSize(); 9289 9290 setPosition(image, bounds.min); 9291 9292 image.style.width = size.x + 'px'; 9293 image.style.height = size.y + 'px'; 9294 }, 9295 9296 _updateOpacity: function () { 9297 setOpacity(this._image, this.options.opacity); 9298 }, 9299 9300 _updateZIndex: function () { 9301 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9302 this._image.style.zIndex = this.options.zIndex; 9303 } 9304 }, 9305 9306 _overlayOnError: function () { 9307 // @event error: Event 9308 // Fired when the ImageOverlay layer fails to load its image 9309 this.fire('error'); 9310 9311 var errorUrl = this.options.errorOverlayUrl; 9312 if (errorUrl && this._url !== errorUrl) { 9313 this._url = errorUrl; 9314 this._image.src = errorUrl; 9315 } 9316 } 9317}); 9318 9319// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9320// Instantiates an image overlay object given the URL of the image and the 9321// geographical bounds it is tied to. 9322var imageOverlay = function (url, bounds, options) { 9323 return new ImageOverlay(url, bounds, options); 9324}; 9325 9326/* 9327 * @class VideoOverlay 9328 * @aka L.VideoOverlay 9329 * @inherits ImageOverlay 9330 * 9331 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9332 * 9333 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9334 * HTML5 element. 9335 * 9336 * @example 9337 * 9338 * ```js 9339 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9340 * videoBounds = [[ 32, -130], [ 13, -100]]; 9341 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9342 * ``` 9343 */ 9344 9345var VideoOverlay = ImageOverlay.extend({ 9346 9347 // @section 9348 // @aka VideoOverlay options 9349 options: { 9350 // @option autoplay: Boolean = true 9351 // Whether the video starts playing automatically when loaded. 9352 autoplay: true, 9353 9354 // @option loop: Boolean = true 9355 // Whether the video will loop back to the beginning when played. 9356 loop: true, 9357 9358 // @option keepAspectRatio: Boolean = true 9359 // Whether the video will save aspect ratio after the projection. 9360 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit 9361 keepAspectRatio: true 9362 }, 9363 9364 _initImage: function () { 9365 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9366 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9367 9368 addClass(vid, 'leaflet-image-layer'); 9369 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9370 if (this.options.className) { addClass(vid, this.options.className); } 9371 9372 vid.onselectstart = falseFn; 9373 vid.onmousemove = falseFn; 9374 9375 // @event load: Event 9376 // Fired when the video has finished loading the first frame 9377 vid.onloadeddata = bind(this.fire, this, 'load'); 9378 9379 if (wasElementSupplied) { 9380 var sourceElements = vid.getElementsByTagName('source'); 9381 var sources = []; 9382 for (var j = 0; j < sourceElements.length; j++) { 9383 sources.push(sourceElements[j].src); 9384 } 9385 9386 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9387 return; 9388 } 9389 9390 if (!isArray(this._url)) { this._url = [this._url]; } 9391 9392 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; } 9393 vid.autoplay = !!this.options.autoplay; 9394 vid.loop = !!this.options.loop; 9395 for (var i = 0; i < this._url.length; i++) { 9396 var source = create$1('source'); 9397 source.src = this._url[i]; 9398 vid.appendChild(source); 9399 } 9400 } 9401 9402 // @method getElement(): HTMLVideoElement 9403 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9404 // used by this overlay. 9405}); 9406 9407 9408// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9409// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9410// geographical bounds it is tied to. 9411 9412function videoOverlay(video, bounds, options) { 9413 return new VideoOverlay(video, bounds, options); 9414} 9415 9416/* 9417 * @class SVGOverlay 9418 * @aka L.SVGOverlay 9419 * @inherits ImageOverlay 9420 * 9421 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`. 9422 * 9423 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. 9424 * 9425 * @example 9426 * 9427 * ```js 9428 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9429 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); 9430 * svgElement.setAttribute('viewBox', "0 0 200 200"); 9431 * 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"/>'; 9432 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ]; 9433 * L.svgOverlay(svgElement, svgElementBounds).addTo(map); 9434 * ``` 9435 */ 9436 9437var SVGOverlay = ImageOverlay.extend({ 9438 _initImage: function () { 9439 var el = this._image = this._url; 9440 9441 addClass(el, 'leaflet-image-layer'); 9442 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); } 9443 if (this.options.className) { addClass(el, this.options.className); } 9444 9445 el.onselectstart = falseFn; 9446 el.onmousemove = falseFn; 9447 } 9448 9449 // @method getElement(): SVGElement 9450 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement) 9451 // used by this overlay. 9452}); 9453 9454 9455// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options) 9456// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to. 9457// A viewBox attribute is required on the SVG element to zoom in and out properly. 9458 9459function svgOverlay(el, bounds, options) { 9460 return new SVGOverlay(el, bounds, options); 9461} 9462 9463/* 9464 * @class DivOverlay 9465 * @inherits Layer 9466 * @aka L.DivOverlay 9467 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins. 9468 */ 9469 9470// @namespace DivOverlay 9471var DivOverlay = Layer.extend({ 9472 9473 // @section 9474 // @aka DivOverlay options 9475 options: { 9476 // @option offset: Point = Point(0, 7) 9477 // The offset of the popup position. Useful to control the anchor 9478 // of the popup when opening it on some overlays. 9479 offset: [0, 7], 9480 9481 // @option className: String = '' 9482 // A custom CSS class name to assign to the popup. 9483 className: '', 9484 9485 // @option pane: String = 'popupPane' 9486 // `Map pane` where the popup will be added. 9487 pane: 'popupPane' 9488 }, 9489 9490 initialize: function (options, source) { 9491 setOptions(this, options); 9492 9493 this._source = source; 9494 }, 9495 9496 onAdd: function (map) { 9497 this._zoomAnimated = map._zoomAnimated; 9498 9499 if (!this._container) { 9500 this._initLayout(); 9501 } 9502 9503 if (map._fadeAnimated) { 9504 setOpacity(this._container, 0); 9505 } 9506 9507 clearTimeout(this._removeTimeout); 9508 this.getPane().appendChild(this._container); 9509 this.update(); 9510 9511 if (map._fadeAnimated) { 9512 setOpacity(this._container, 1); 9513 } 9514 9515 this.bringToFront(); 9516 }, 9517 9518 onRemove: function (map) { 9519 if (map._fadeAnimated) { 9520 setOpacity(this._container, 0); 9521 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200); 9522 } else { 9523 remove(this._container); 9524 } 9525 }, 9526 9527 // @namespace Popup 9528 // @method getLatLng: LatLng 9529 // Returns the geographical point of popup. 9530 getLatLng: function () { 9531 return this._latlng; 9532 }, 9533 9534 // @method setLatLng(latlng: LatLng): this 9535 // Sets the geographical point where the popup will open. 9536 setLatLng: function (latlng) { 9537 this._latlng = toLatLng(latlng); 9538 if (this._map) { 9539 this._updatePosition(); 9540 this._adjustPan(); 9541 } 9542 return this; 9543 }, 9544 9545 // @method getContent: String|HTMLElement 9546 // Returns the content of the popup. 9547 getContent: function () { 9548 return this._content; 9549 }, 9550 9551 // @method setContent(htmlContent: String|HTMLElement|Function): this 9552 // 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. 9553 setContent: function (content) { 9554 this._content = content; 9555 this.update(); 9556 return this; 9557 }, 9558 9559 // @method getElement: String|HTMLElement 9560 // Alias for [getContent()](#popup-getcontent) 9561 getElement: function () { 9562 return this._container; 9563 }, 9564 9565 // @method update: null 9566 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded. 9567 update: function () { 9568 if (!this._map) { return; } 9569 9570 this._container.style.visibility = 'hidden'; 9571 9572 this._updateContent(); 9573 this._updateLayout(); 9574 this._updatePosition(); 9575 9576 this._container.style.visibility = ''; 9577 9578 this._adjustPan(); 9579 }, 9580 9581 getEvents: function () { 9582 var events = { 9583 zoom: this._updatePosition, 9584 viewreset: this._updatePosition 9585 }; 9586 9587 if (this._zoomAnimated) { 9588 events.zoomanim = this._animateZoom; 9589 } 9590 return events; 9591 }, 9592 9593 // @method isOpen: Boolean 9594 // Returns `true` when the popup is visible on the map. 9595 isOpen: function () { 9596 return !!this._map && this._map.hasLayer(this); 9597 }, 9598 9599 // @method bringToFront: this 9600 // Brings this popup in front of other popups (in the same map pane). 9601 bringToFront: function () { 9602 if (this._map) { 9603 toFront(this._container); 9604 } 9605 return this; 9606 }, 9607 9608 // @method bringToBack: this 9609 // Brings this popup to the back of other popups (in the same map pane). 9610 bringToBack: function () { 9611 if (this._map) { 9612 toBack(this._container); 9613 } 9614 return this; 9615 }, 9616 9617 _prepareOpen: function (parent, layer, latlng) { 9618 if (!(layer instanceof Layer)) { 9619 latlng = layer; 9620 layer = parent; 9621 } 9622 9623 if (layer instanceof FeatureGroup) { 9624 for (var id in parent._layers) { 9625 layer = parent._layers[id]; 9626 break; 9627 } 9628 } 9629 9630 if (!latlng) { 9631 if (layer.getCenter) { 9632 latlng = layer.getCenter(); 9633 } else if (layer.getLatLng) { 9634 latlng = layer.getLatLng(); 9635 } else { 9636 throw new Error('Unable to get source layer LatLng.'); 9637 } 9638 } 9639 9640 // set overlay source to this layer 9641 this._source = layer; 9642 9643 // update the overlay (content, layout, ect...) 9644 this.update(); 9645 9646 return latlng; 9647 }, 9648 9649 _updateContent: function () { 9650 if (!this._content) { return; } 9651 9652 var node = this._contentNode; 9653 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 9654 9655 if (typeof content === 'string') { 9656 node.innerHTML = content; 9657 } else { 9658 while (node.hasChildNodes()) { 9659 node.removeChild(node.firstChild); 9660 } 9661 node.appendChild(content); 9662 } 9663 this.fire('contentupdate'); 9664 }, 9665 9666 _updatePosition: function () { 9667 if (!this._map) { return; } 9668 9669 var pos = this._map.latLngToLayerPoint(this._latlng), 9670 offset = toPoint(this.options.offset), 9671 anchor = this._getAnchor(); 9672 9673 if (this._zoomAnimated) { 9674 setPosition(this._container, pos.add(anchor)); 9675 } else { 9676 offset = offset.add(pos).add(anchor); 9677 } 9678 9679 var bottom = this._containerBottom = -offset.y, 9680 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 9681 9682 // bottom position the popup in case the height of the popup changes (images loading etc) 9683 this._container.style.bottom = bottom + 'px'; 9684 this._container.style.left = left + 'px'; 9685 }, 9686 9687 _getAnchor: function () { 9688 return [0, 0]; 9689 } 9690 9691}); 9692 9693/* 9694 * @class Popup 9695 * @inherits DivOverlay 9696 * @aka L.Popup 9697 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 9698 * open popups while making sure that only one popup is open at one time 9699 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 9700 * 9701 * @example 9702 * 9703 * If you want to just bind a popup to marker click and then open it, it's really easy: 9704 * 9705 * ```js 9706 * marker.bindPopup(popupContent).openPopup(); 9707 * ``` 9708 * Path overlays like polylines also have a `bindPopup` method. 9709 * Here's a more complicated way to open a popup on a map: 9710 * 9711 * ```js 9712 * var popup = L.popup() 9713 * .setLatLng(latlng)
9714 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 9715 * .openOn(map); 9716 * ``` 9717 */ 9718 9719 9720// @namespace Popup 9721var Popup = DivOverlay.extend({ 9722 9723 // @section 9724 // @aka Popup options 9725 options: { 9726 // @option maxWidth: Number = 300 9727 // Max width of the popup, in pixels. 9728 maxWidth: 300, 9729 9730 // @option minWidth: Number = 50 9731 // Min width of the popup, in pixels. 9732 minWidth: 50, 9733 9734 // @option maxHeight: Number = null 9735 // If set, creates a scrollable container of the given height 9736 // inside a popup if its content exceeds it. 9737 maxHeight: null, 9738 9739 // @option autoPan: Boolean = true 9740 // Set it to `false` if you don't want the map to do panning animation 9741 // to fit the opened popup. 9742 autoPan: true, 9743 9744 // @option autoPanPaddingTopLeft: Point = null 9745 // The margin between the popup and the top left corner of the map 9746 // view after autopanning was performed. 9747 autoPanPaddingTopLeft: null, 9748 9749 // @option autoPanPaddingBottomRight: Point = null 9750 // The margin between the popup and the bottom right corner of the map 9751 // view after autopanning was performed. 9752 autoPanPaddingBottomRight: null, 9753 9754 // @option autoPanPadding: Point = Point(5, 5) 9755 // Equivalent of setting both top left and bottom right autopan padding to the same value. 9756 autoPanPadding: [5, 5], 9757 9758 // @option keepInView: Boolean = false 9759 // Set it to `true` if you want to prevent users from panning the popup 9760 // off of the screen while it is open. 9761 keepInView: false, 9762 9763 // @option closeButton: Boolean = true 9764 // Controls the presence of a close button in the popup. 9765 closeButton: true, 9766 9767 // @option autoClose: Boolean = true 9768 // Set it to `false` if you want to override the default behavior of 9769 // the popup closing when another popup is opened. 9770 autoClose: true, 9771 9772 // @option closeOnEscapeKey: Boolean = true 9773 // Set it to `false` if you want to override the default behavior of 9774 // the ESC key for closing of the popup. 9775 closeOnEscapeKey: true, 9776 9777 // @option closeOnClick: Boolean = * 9778 // Set it if you want to override the default behavior of the popup closing when user clicks 9779 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 9780 9781 // @option className: String = '' 9782 // A custom CSS class name to assign to the popup. 9783 className: '' 9784 }, 9785 9786 // @namespace Popup 9787 // @method openOn(map: Map): this 9788 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`. 9789 openOn: function (map) { 9790 map.openPopup(this); 9791 return this; 9792 }, 9793 9794 onAdd: function (map) { 9795 DivOverlay.prototype.onAdd.call(this, map); 9796 9797 // @namespace Map 9798 // @section Popup events 9799 // @event popupopen: PopupEvent 9800 // Fired when a popup is opened in the map 9801 map.fire('popupopen', {popup: this}); 9802 9803 if (this._source) { 9804 // @namespace Layer 9805 // @section Popup events 9806 // @event popupopen: PopupEvent 9807 // Fired when a popup bound to this layer is opened 9808 this._source.fire('popupopen', {popup: this}, true); 9809 // For non-path layers, we toggle the popup when clicking 9810 // again the layer, so prevent the map to reopen it. 9811 if (!(this._source instanceof Path)) { 9812 this._source.on('preclick', stopPropagation); 9813 } 9814 } 9815 }, 9816 9817 onRemove: function (map) { 9818 DivOverlay.prototype.onRemove.call(this, map); 9819 9820 // @namespace Map 9821 // @section Popup events 9822 // @event popupclose: PopupEvent 9823 // Fired when a popup in the map is closed 9824 map.fire('popupclose', {popup: this}); 9825 9826 if (this._source) { 9827 // @namespace Layer 9828 // @section Popup events 9829 // @event popupclose: PopupEvent 9830 // Fired when a popup bound to this layer is closed 9831 this._source.fire('popupclose', {popup: this}, true); 9832 if (!(this._source instanceof Path)) { 9833 this._source.off('preclick', stopPropagation); 9834 } 9835 } 9836 }, 9837 9838 getEvents: function () { 9839 var events = DivOverlay.prototype.getEvents.call(this); 9840 9841 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 9842 events.preclick = this._close; 9843 } 9844 9845 if (this.options.keepInView) { 9846 events.moveend = this._adjustPan; 9847 } 9848 9849 return events; 9850 }, 9851 9852 _close: function () { 9853 if (this._map) { 9854 this._map.closePopup(this); 9855 } 9856 }, 9857 9858 _initLayout: function () { 9859 var prefix = 'leaflet-popup', 9860 container = this._container = create$1('div', 9861 prefix + ' ' + (this.options.className || '') + 9862 ' leaflet-zoom-animated'); 9863 9864 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container); 9865 this._contentNode = create$1('div', prefix + '-content', wrapper); 9866 9867 disableClickPropagation(wrapper); 9868 disableScrollPropagation(this._contentNode); 9869 on(wrapper, 'contextmenu', stopPropagation); 9870 9871 this._tipContainer = create$1('div', prefix + '-tip-container', container); 9872 this._tip = create$1('div', prefix + '-tip', this._tipContainer); 9873 9874 if (this.options.closeButton) { 9875 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container); 9876 closeButton.href = '#close'; 9877 closeButton.innerHTML = '×'; 9878 9879 on(closeButton, 'click', this._onCloseButtonClick, this); 9880 } 9881 }, 9882 9883 _updateLayout: function () { 9884 var container = this._contentNode, 9885 style = container.style; 9886 9887 style.width = ''; 9888 style.whiteSpace = 'nowrap'; 9889 9890 var width = container.offsetWidth; 9891 width = Math.min(width, this.options.maxWidth); 9892 width = Math.max(width, this.options.minWidth); 9893 9894 style.width = (width + 1) + 'px'; 9895 style.whiteSpace = ''; 9896 9897 style.height = ''; 9898 9899 var height = container.offsetHeight, 9900 maxHeight = this.options.maxHeight, 9901 scrolledClass = 'leaflet-popup-scrolled'; 9902 9903 if (maxHeight && height > maxHeight) { 9904 style.height = maxHeight + 'px'; 9905 addClass(container, scrolledClass); 9906 } else { 9907 removeClass(container, scrolledClass); 9908 } 9909 9910 this._containerWidth = this._container.offsetWidth; 9911 }, 9912 9913 _animateZoom: function (e) { 9914 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 9915 anchor = this._getAnchor(); 9916 setPosition(this._container, pos.add(anchor)); 9917 }, 9918 9919 _adjustPan: function () { 9920 if (!this.options.autoPan) { return; } 9921 if (this._map._panAnim) { this._map._panAnim.stop(); } 9922 9923 var map = this._map, 9924 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0, 9925 containerHeight = this._container.offsetHeight + marginBottom, 9926 containerWidth = this._containerWidth, 9927 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 9928 9929 layerPos._add(getPosition(this._container)); 9930 9931 var containerPos = map.layerPointToContainerPoint(layerPos), 9932 padding = toPoint(this.options.autoPanPadding), 9933 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), 9934 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), 9935 size = map.getSize(), 9936 dx = 0, 9937 dy = 0; 9938 9939 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 9940 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 9941 } 9942 if (containerPos.x - dx - paddingTL.x < 0) { // left 9943 dx = containerPos.x - paddingTL.x; 9944 } 9945 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 9946 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 9947 } 9948 if (containerPos.y - dy - paddingTL.y < 0) { // top 9949 dy = containerPos.y - paddingTL.y; 9950 } 9951 9952 // @namespace Map 9953 // @section Popup events 9954 // @event autopanstart: Event 9955 // Fired when the map starts autopanning when opening a popup. 9956 if (dx || dy) { 9957 map 9958 .fire('autopanstart') 9959 .panBy([dx, dy]); 9960 } 9961 }, 9962 9963 _onCloseButtonClick: function (e) { 9964 this._close(); 9965 stop(e); 9966 }, 9967 9968 _getAnchor: function () { 9969 // Where should we anchor the popup on the source layer? 9970 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 9971 } 9972 9973}); 9974 9975// @namespace Popup 9976// @factory L.popup(options?: Popup options, source?: Layer) 9977// 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. 9978var popup = function (options, source) { 9979 return new Popup(options, source); 9980}; 9981 9982 9983/* @namespace Map 9984 * @section Interaction Options 9985 * @option closePopupOnClick: Boolean = true 9986 * Set it to `false` if you don't want popups to close when user clicks the map. 9987 */ 9988Map.mergeOptions({ 9989 closePopupOnClick: true 9990}); 9991 9992 9993// @namespace Map 9994// @section Methods for Layers and Controls 9995Map.include({ 9996 // @method openPopup(popup: Popup): this 9997 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 9998 // @alternative 9999 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 10000 // Creates a popup with the specified content and options and opens it in the given point on a map. 10001 openPopup: function (popup, latlng, options) { 10002 if (!(popup instanceof Popup)) { 10003 popup = new Popup(options).setContent(popup); 10004 } 10005 10006 if (latlng) { 10007 popup.setLatLng(latlng); 10008 } 10009 10010 if (this.hasLayer(popup)) { 10011 return this; 10012 } 10013 10014 if (this._popup && this._popup.options.autoClose) { 10015 this.closePopup(); 10016 } 10017 10018 this._popup = popup; 10019 return this.addLayer(popup); 10020 }, 10021 10022 // @method closePopup(popup?: Popup): this 10023 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 10024 closePopup: function (popup) { 10025 if (!popup || popup === this._popup) { 10026 popup = this._popup; 10027 this._popup = null; 10028 } 10029 if (popup) { 10030 this.removeLayer(popup); 10031 } 10032 return this; 10033 } 10034}); 10035 10036/* 10037 * @namespace Layer 10038 * @section Popup methods example 10039 * 10040 * All layers share a set of methods convenient for binding popups to it. 10041 * 10042 * ```js 10043 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 10044 * layer.openPopup(); 10045 * layer.closePopup(); 10046 * ``` 10047 * 10048 * 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. 10049 */ 10050 10051// @section Popup methods 10052Layer.include({ 10053 10054 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 10055 // Binds a popup to the layer with the passed `content` and sets up the 10056 // necessary event listeners. If a `Function` is passed it will receive 10057 // the layer as the first argument and should return a `String` or `HTMLElement`. 10058 bindPopup: function (content, options) { 10059 10060 if (content instanceof Popup) { 10061 setOptions(content, options); 10062 this._popup = content; 10063 content._source = this; 10064 } else { 10065 if (!this._popup || options) { 10066 this._popup = new Popup(options, this); 10067 } 10068 this._popup.setContent(content); 10069 } 10070 10071 if (!this._popupHandlersAdded) { 10072 this.on({ 10073 click: this._openPopup, 10074 keypress: this._onKeyPress, 10075 remove: this.closePopup,
10076 move: this._movePopup 10077 }); 10078 this._popupHandlersAdded = true; 10079 } 10080 10081 return this; 10082 }, 10083 10084 // @method unbindPopup(): this 10085 // Removes the popup previously bound with `bindPopup`. 10086 unbindPopup: function () { 10087 if (this._popup) { 10088 this.off({ 10089 click: this._openPopup, 10090 keypress: this._onKeyPress, 10091 remove: this.closePopup, 10092 move: this._movePopup 10093 }); 10094 this._popupHandlersAdded = false; 10095 this._popup = null; 10096 } 10097 return this; 10098 }, 10099 10100 // @method openPopup(latlng?: LatLng): this 10101 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 10102 openPopup: function (layer, latlng) { 10103 if (this._popup && this._map) { 10104 latlng = this._popup._prepareOpen(this, layer, latlng); 10105 10106 // open the popup on the map 10107 this._map.openPopup(this._popup, latlng); 10108 } 10109 10110 return this; 10111 }, 10112 10113 // @method closePopup(): this 10114 // Closes the popup bound to this layer if it is open. 10115 closePopup: function () { 10116 if (this._popup) { 10117 this._popup._close(); 10118 } 10119 return this; 10120 }, 10121 10122 // @method togglePopup(): this 10123 // Opens or closes the popup bound to this layer depending on its current state. 10124 togglePopup: function (target) { 10125 if (this._popup) { 10126 if (this._popup._map) { 10127 this.closePopup(); 10128 } else { 10129 this.openPopup(target); 10130 } 10131 } 10132 return this; 10133 }, 10134 10135 // @method isPopupOpen(): boolean 10136 // Returns `true` if the popup bound to this layer is currently open. 10137 isPopupOpen: function () { 10138 return (this._popup ? this._popup.isOpen() : false); 10139 }, 10140 10141 // @method setPopupContent(content: String|HTMLElement|Popup): this 10142 // Sets the content of the popup bound to this layer. 10143 setPopupContent: function (content) { 10144 if (this._popup) { 10145 this._popup.setContent(content); 10146 } 10147 return this; 10148 }, 10149 10150 // @method getPopup(): Popup 10151 // Returns the popup bound to this layer. 10152 getPopup: function () { 10153 return this._popup; 10154 }, 10155 10156 _openPopup: function (e) { 10157 var layer = e.layer || e.target; 10158 10159 if (!this._popup) { 10160 return; 10161 } 10162 10163 if (!this._map) { 10164 return; 10165 } 10166 10167 // prevent map click 10168 stop(e); 10169 10170 // if this inherits from Path its a vector and we can just 10171 // open the popup at the new location 10172 if (layer instanceof Path) { 10173 this.openPopup(e.layer || e.target, e.latlng); 10174 return; 10175 } 10176 10177 // otherwise treat it like a marker and figure out 10178 // if we should toggle it open/closed 10179 if (this._map.hasLayer(this._popup) && this._popup._source === layer) { 10180 this.closePopup(); 10181 } else { 10182 this.openPopup(layer, e.latlng); 10183 } 10184 }, 10185 10186 _movePopup: function (e) { 10187 this._popup.setLatLng(e.latlng); 10188 }, 10189 10190 _onKeyPress: function (e) { 10191 if (e.originalEvent.keyCode === 13) { 10192 this._openPopup(e); 10193 } 10194 } 10195}); 10196 10197/* 10198 * @class Tooltip 10199 * @inherits DivOverlay 10200 * @aka L.Tooltip 10201 * Used to display small texts on top of map layers. 10202 * 10203 * @example 10204 * 10205 * ```js 10206 * marker.bindTooltip("my tooltip text").openTooltip(); 10207 * ``` 10208 * Note about tooltip offset. Leaflet takes two options in consideration 10209 * for computing tooltip offsetting: 10210 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 10211 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 10212 * move it to the bottom. Negatives will move to the left and top. 10213 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 10214 * should adapt this value if you use a custom icon. 10215 */ 10216 10217 10218// @namespace Tooltip 10219var Tooltip = DivOverlay.extend({ 10220 10221 // @section 10222 // @aka Tooltip options 10223 options: { 10224 // @option pane: String = 'tooltipPane' 10225 // `Map pane` where the tooltip will be added. 10226 pane: 'tooltipPane', 10227 10228 // @option offset: Point = Point(0, 0) 10229 // Optional offset of the tooltip position. 10230 offset: [0, 0], 10231 10232 // @option direction: String = 'auto' 10233 // Direction where to open the tooltip. Possible values are: `right`, `left`, 10234 // `top`, `bottom`, `center`, `auto`. 10235 // `auto` will dynamically switch between `right` and `left` according to the tooltip 10236 // position on the map. 10237 direction: 'auto', 10238 10239 // @option permanent: Boolean = false 10240 // Whether to open the tooltip permanently or only on mouseover. 10241 permanent: false, 10242 10243 // @option sticky: Boolean = false 10244 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10245 sticky: false, 10246 10247 // @option interactive: Boolean = false 10248 // If true, the tooltip will listen to the feature events. 10249 interactive: false, 10250 10251 // @option opacity: Number = 0.9 10252 // Tooltip container opacity. 10253 opacity: 0.9 10254 }, 10255 10256 onAdd: function (map) { 10257 DivOverlay.prototype.onAdd.call(this, map); 10258 this.setOpacity(this.options.opacity); 10259 10260 // @namespace Map 10261 // @section Tooltip events 10262 // @event tooltipopen: TooltipEvent 10263 // Fired when a tooltip is opened in the map. 10264 map.fire('tooltipopen', {tooltip: this}); 10265 10266 if (this._source) { 10267 // @namespace Layer 10268 // @section Tooltip events 10269 // @event tooltipopen: TooltipEvent 10270 // Fired when a tooltip bound to this layer is opened. 10271 this._source.fire('tooltipopen', {tooltip: this}, true); 10272 } 10273 }, 10274 10275 onRemove: function (map) { 10276 DivOverlay.prototype.onRemove.call(this, map); 10277 10278 // @namespace Map 10279 // @section Tooltip events 10280 // @event tooltipclose: TooltipEvent 10281 // Fired when a tooltip in the map is closed. 10282 map.fire('tooltipclose', {tooltip: this}); 10283 10284 if (this._source) { 10285 // @namespace Layer 10286 // @section Tooltip events 10287 // @event tooltipclose: TooltipEvent 10288 // Fired when a tooltip bound to this layer is closed. 10289 this._source.fire('tooltipclose', {tooltip: this}, true); 10290 } 10291 }, 10292 10293 getEvents: function () { 10294 var events = DivOverlay.prototype.getEvents.call(this); 10295 10296 if (touch && !this.options.permanent) { 10297 events.preclick = this._close; 10298 } 10299 10300 return events; 10301 }, 10302 10303 _close: function () { 10304 if (this._map) { 10305 this._map.closeTooltip(this); 10306 } 10307 }, 10308 10309 _initLayout: function () { 10310 var prefix = 'leaflet-tooltip', 10311 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 10312 10313 this._contentNode = this._container = create$1('div', className); 10314 }, 10315 10316 _updateLayout: function () {}, 10317 10318 _adjustPan: function () {}, 10319 10320 _setPosition: function (pos) { 10321 var map = this._map, 10322 container = this._container, 10323 centerPoint = map.latLngToContainerPoint(map.getCenter()), 10324 tooltipPoint = map.layerPointToContainerPoint(pos), 10325 direction = this.options.direction, 10326 tooltipWidth = container.offsetWidth, 10327 tooltipHeight = container.offsetHeight, 10328 offset = toPoint(this.options.offset), 10329 anchor = this._getAnchor(); 10330 10331 if (direction === 'top') { 10332 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true)); 10333 } else if (direction === 'bottom') { 10334 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true)); 10335 } else if (direction === 'center') { 10336 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true)); 10337 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) { 10338 direction = 'right'; 10339 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true)); 10340 } else { 10341 direction = 'left'; 10342 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true)); 10343 } 10344 10345 removeClass(container, 'leaflet-tooltip-right'); 10346 removeClass(container, 'leaflet-tooltip-left'); 10347 removeClass(container, 'leaflet-tooltip-top'); 10348 removeClass(container, 'leaflet-tooltip-bottom'); 10349 addClass(container, 'leaflet-tooltip-' + direction); 10350 setPosition(container, pos); 10351 }, 10352 10353 _updatePosition: function () { 10354 var pos = this._map.latLngToLayerPoint(this._latlng); 10355 this._setPosition(pos); 10356 }, 10357 10358 setOpacity: function (opacity) { 10359 this.options.opacity = opacity; 10360 10361 if (this._container) { 10362 setOpacity(this._container, opacity); 10363 } 10364 }, 10365 10366 _animateZoom: function (e) { 10367 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 10368 this._setPosition(pos); 10369 }, 10370 10371 _getAnchor: function () { 10372 // Where should we anchor the tooltip on the source layer? 10373 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 10374 } 10375 10376}); 10377 10378// @namespace Tooltip 10379// @factory L.tooltip(options?: Tooltip options, source?: Layer) 10380// 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. 10381var tooltip = function (options, source) { 10382 return new Tooltip(options, source); 10383}; 10384 10385// @namespace Map 10386// @section Methods for Layers and Controls 10387Map.include({ 10388 10389 // @method openTooltip(tooltip: Tooltip): this 10390 // Opens the specified tooltip. 10391 // @alternative 10392 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 10393 // Creates a tooltip with the specified content and options and open it. 10394 openTooltip: function (tooltip, latlng, options) { 10395 if (!(tooltip instanceof Tooltip)) { 10396 tooltip = new Tooltip(options).setContent(tooltip); 10397 } 10398 10399 if (latlng) { 10400 tooltip.setLatLng(latlng); 10401 } 10402 10403 if (this.hasLayer(tooltip)) { 10404 return this; 10405 } 10406 10407 return this.addLayer(tooltip); 10408 }, 10409 10410 // @method closeTooltip(tooltip?: Tooltip): this 10411 // Closes the tooltip given as parameter. 10412 closeTooltip: function (tooltip) { 10413 if (tooltip) { 10414 this.removeLayer(tooltip); 10415 } 10416 return this; 10417 } 10418 10419}); 10420 10421/* 10422 * @namespace Layer 10423 * @section Tooltip methods example 10424 * 10425 * All layers share a set of methods convenient for binding tooltips to it. 10426 * 10427 * ```js 10428 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 10429 * layer.openTooltip(); 10430 * layer.closeTooltip(); 10431 * ``` 10432 */ 10433 10434// @section Tooltip methods 10435Layer.include({ 10436 10437 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 10438 // Binds a tooltip to the layer with the passed `content` and sets up the 10439 // necessary event listeners. If a `Function` is passed it will receive 10440 // the layer as the first argument and should return a `String` or `HTMLElement`. 10441 bindTooltip: function (content, options) { 10442 10443 if (content instanceof Tooltip) { 10444 setOptions(content, options); 10445 this._tooltip = content; 10446 content._source = this; 10447 } else { 10448 if (!this._tooltip || options) { 10449 this._tooltip = new Tooltip(options, this); 10450 } 10451 this._tooltip.setContent(content); 10452 10453 } 10454 10455 this._initTooltipInteractions(); 10456 10457 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 10458 this.openTooltip(); 10459 } 10460 10461 return this; 10462 }, 10463 10464 // @method unbindTooltip(): this 10465 // Removes the tooltip previously bound with `bindTooltip`. 10466 unbindTooltip: function () { 10467 if (this._tooltip) { 10468 this._initTooltipInteractions(true); 10469 this.closeTooltip(); 10470 this._tooltip = null; 10471 } 10472 return this; 10473 }, 10474 10475 _initTooltipInteractions: function (remove$$1) { 10476 if (!remove$$1 && this._tooltipHandlersAdded) { return; } 10477 var onOff = remove$$1 ? 'off' : 'on', 10478 events = { 10479 remove: this.closeTooltip, 10480 move: this._moveTooltip 10481 }; 10482 if (!this._tooltip.options.permanent) { 10483 events.mouseover = this._openTooltip; 10484 events.mouseout = this.closeTooltip; 10485 if (this._tooltip.options.sticky) { 10486 events.mousemove = this._moveTooltip; 10487 } 10488 if (touch) { 10489 events.click = this._openTooltip; 10490 } 10491 } else { 10492 events.add = this._openTooltip; 10493 } 10494 this[onOff](events); 10495 this._tooltipHandlersAdded = !remove$$1; 10496 }, 10497 10498 // @method openTooltip(latlng?: LatLng): this 10499 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 10500 openTooltip: function (layer, latlng) { 10501 if (this._tooltip && this._map) { 10502 latlng = this._tooltip._prepareOpen(this, layer, latlng); 10503 10504 // open the tooltip on the map 10505 this._map.openTooltip(this._tooltip, latlng); 10506 10507 // Tooltip container may not be defined if not permanent and never 10508 // opened. 10509 if (this._tooltip.options.interactive && this._tooltip._container) { 10510 addClass(this._tooltip._container, 'leaflet-clickable'); 10511 this.addInteractiveTarget(this._tooltip._container); 10512 } 10513 } 10514 10515 return this; 10516 }, 10517 10518 // @method closeTooltip(): this 10519 // Closes the tooltip bound to this layer if it is open. 10520 closeTooltip: function () { 10521 if (this._tooltip) { 10522 this._tooltip._close(); 10523 if (this._tooltip.options.interactive && this._tooltip._container) { 10524 removeClass(this._tooltip._container, 'leaflet-clickable'); 10525 this.removeInteractiveTarget(this._tooltip._container); 10526 } 10527 } 10528 return this; 10529 }, 10530 10531 // @method toggleTooltip(): this 10532 // Opens or closes the tooltip bound to this layer depending on its current state. 10533 toggleTooltip: function (target) { 10534 if (this._tooltip) { 10535 if (this._tooltip._map) { 10536 this.closeTooltip(); 10537 } else { 10538 this.openTooltip(target); 10539 } 10540 } 10541 return this; 10542 }, 10543 10544 // @method isTooltipOpen(): boolean 10545 // Returns `true` if the tooltip bound to this layer is currently open. 10546 isTooltipOpen: function () { 10547 return this._tooltip.isOpen(); 10548 }, 10549 10550 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
vendor: 4,153 bytes, lines 10551-10695
10551 // Sets the content of the tooltip bound to this layer. 10552 setTooltipContent: function (content) { 10553 if (this._tooltip) { 10554 this._tooltip.setContent(content); 10555 } 10556 return this; 10557 }, 10558 10559 // @method getTooltip(): Tooltip 10560 // Returns the tooltip bound to this layer. 10561 getTooltip: function () { 10562 return this._tooltip; 10563 }, 10564 10565 _openTooltip: function (e) { 10566 var layer = e.layer || e.target; 10567 10568 if (!this._tooltip || !this._map) { 10569 return; 10570 } 10571 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined); 10572 }, 10573 10574 _moveTooltip: function (e) { 10575 var latlng = e.latlng, containerPoint, layerPoint; 10576 if (this._tooltip.options.sticky && e.originalEvent) { 10577 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 10578 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 10579 latlng = this._map.layerPointToLatLng(layerPoint); 10580 } 10581 this._tooltip.setLatLng(latlng); 10582 } 10583}); 10584 10585/* 10586 * @class DivIcon 10587 * @aka L.DivIcon 10588 * @inherits Icon 10589 * 10590 * Represents a lightweight icon for markers that uses a simple `<div>` 10591 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 10592 * 10593 * @example 10594 * ```js 10595 * var myIcon = L.divIcon({className: 'my-div-icon'}); 10596 * // you can set .my-div-icon styles in CSS 10597 * 10598 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 10599 * ``` 10600 * 10601 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 10602 */ 10603 10604var DivIcon = Icon.extend({ 10605 options: { 10606 // @section 10607 // @aka DivIcon options 10608 iconSize: [12, 12], // also can be set through CSS 10609 10610 // iconAnchor: (Point), 10611 // popupAnchor: (Point), 10612 10613 // @option html: String|HTMLElement = '' 10614 // Custom HTML code to put inside the div element, empty by default. Alternatively, 10615 // an instance of `HTMLElement`. 10616 html: false, 10617 10618 // @option bgPos: Point = [0, 0] 10619 // Optional relative position of the background, in pixels 10620 bgPos: null, 10621 10622 className: 'leaflet-div-icon' 10623 }, 10624 10625 createIcon: function (oldIcon) { 10626 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 10627 options = this.options; 10628 10629 if (options.html instanceof Element) { 10630 empty(div); 10631 div.appendChild(options.html); 10632 } else { 10633 div.innerHTML = options.html !== false ? options.html : ''; 10634 } 10635 10636 if (options.bgPos) { 10637 var bgPos = toPoint(options.bgPos); 10638 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 10639 } 10640 this._setIconStyles(div, 'icon'); 10641 10642 return div; 10643 }, 10644 10645 createShadow: function () { 10646 return null; 10647 } 10648}); 10649 10650// @factory L.divIcon(options: DivIcon options) 10651// Creates a `DivIcon` instance with the given options. 10652function divIcon(options) { 10653 return new DivIcon(options); 10654} 10655 10656Icon.Default = IconDefault; 10657 10658/* 10659 * @class GridLayer 10660 * @inherits Layer 10661 * @aka L.GridLayer 10662 * 10663 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 10664 * 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. 10665 * 10666 * 10667 * @section Synchronous usage 10668 * @example 10669 * 10670 * 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. 10671 * 10672 * ```js 10673 * var CanvasLayer = L.GridLayer.extend({ 10674 * createTile: function(coords){ 10675 * // create a <canvas> element for drawing 10676 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10677 * 10678 * // setup tile width and height according to the options 10679 * var size = this.getTileSize(); 10680 * tile.width = size.x; 10681 * tile.height = size.y; 10682 * 10683 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 10684 * var ctx = tile.getContext('2d'); 10685 * 10686 * // return the tile so it can be rendered on screen 10687 * return tile; 10688 * } 10689 * }); 10690 * ``` 10691 * 10692 * @section Asynchronous usage 10693 * @example 10694 * 10695 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. O
10695nce the tile is finished drawing it can be passed to the `done()` callback. 10696 * 10697 * ```js 10698 * var CanvasLayer = L.GridLayer.extend({ 10699 * createTile: function(coords, done){ 10700 * var error; 10701 * 10702 * // create a <canvas> element for drawing 10703 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10704 * 10705 * // setup tile width and height according to the options 10706 * var size = this.getTileSize(); 10707 * tile.width = size.x; 10708 * tile.height = size.y; 10709 * 10710 * // draw something asynchronously and pass the tile to the done() callback 10711 * setTimeout(function() { 10712 * done(error, tile); 10713 * }, 1000); 10714 * 10715 * return tile; 10716 * } 10717 * }); 10718 * ``` 10719 * 10720 * @section 10721 */ 10722 10723 10724var GridLayer = Layer.extend({ 10725 10726 // @section 10727 // @aka GridLayer options 10728 options: { 10729 // @option tileSize: Number|Point = 256 10730 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 10731 tileSize: 256, 10732 10733 // @option opacity: Number = 1.0 10734 // Opacity of the tiles. Can be used in the `createTile()` function. 10735 opacity: 1, 10736 10737 // @option updateWhenIdle: Boolean = (depends) 10738 // Load new tiles only when panning ends. 10739 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 10740 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 10741 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 10742 updateWhenIdle: mobile, 10743 10744 // @option updateWhenZooming: Boolean = true 10745 // 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. 10746 updateWhenZooming: true, 10747 10748 // @option updateInterval: Number = 200 10749 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 10750 updateInterval: 200, 10751 10752 // @option zIndex: Number = 1 10753 // The explicit zIndex of the tile layer. 10754 zIndex: 1, 10755 10756 // @option bounds: LatLngBounds = undefined 10757 // If set, tiles will only be loaded inside the set `LatLngBounds`. 10758 bounds: null, 10759 10760 // @option minZoom: Number = 0 10761 // The minimum zoom level down to which this layer will be displayed (inclusive). 10762 minZoom: 0, 10763 10764 // @option maxZoom: Number = undefined 10765 // The maximum zoom level up to which this layer will be displayed (inclusive). 10766 maxZoom: undefined, 10767 10768 // @option maxNativeZoom: Number = undefined 10769 // Maximum zoom number the tile source has available. If it is specified, 10770 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 10771 // from `maxNativeZoom` level and auto-scaled. 10772 maxNativeZoom: undefined, 10773 10774 // @option minNativeZoom: Number = undefined 10775 // Minimum zoom number the tile source has available. If it is specified, 10776 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 10777 // from `minNativeZoom` level and auto-scaled. 10778 minNativeZoom: undefined, 10779 10780 // @option noWrap: Boolean = false 10781 // Whether the layer is wrapped around the antimeridian. If `true`, the 10782 // GridLayer will only be displayed once at low zoom levels. Has no 10783 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 10784 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 10785 // tiles outside the CRS limits. 10786 noWrap: false, 10787 10788 // @option pane: String = 'tilePane' 10789 // `Map pane` where the grid layer will be added. 10790 pane: 'tilePane', 10791 10792 // @option className: String = '' 10793 // A custom class name to assign to the tile layer. Empty by default. 10794 className: '', 10795 10796 // @option keepBuffer: Number = 2 10797 // When panning the map, keep this many rows and columns of tiles before unloading them. 10798 keepBuffer: 2 10799 }, 10800 10801 initialize: function (options) { 10802 setOptions(this, options); 10803 }, 10804 10805 onAdd: function () { 10806 this._initContainer(); 10807 10808 this._levels = {}; 10809 this._tiles = {}; 10810 10811 this._resetView(); 10812 this._update(); 10813 }, 10814 10815 beforeAdd: function (map) {
vendor: 21,432 bytes, lines 10816-11618
10816 map._addZoomLimit(this); 10817 }, 10818 10819 onRemove: function (map) { 10820 this._removeAllTiles(); 10821 remove(this._container); 10822 map._removeZoomLimit(this); 10823 this._container = null; 10824 this._tileZoom = undefined; 10825 }, 10826 10827 // @method bringToFront: this 10828 // Brings the tile layer to the top of all tile layers. 10829 bringToFront: function () { 10830 if (this._map) { 10831 toFront(this._container); 10832 this._setAutoZIndex(Math.max); 10833 } 10834 return this; 10835 }, 10836 10837 // @method bringToBack: this 10838 // Brings the tile layer to the bottom of all tile layers. 10839 bringToBack: function () { 10840 if (this._map) { 10841 toBack(this._container); 10842 this._setAutoZIndex(Math.min); 10843 } 10844 return this; 10845 }, 10846 10847 // @method getContainer: HTMLElement 10848 // Returns the HTML element that contains the tiles for this layer. 10849 getContainer: function () { 10850 return this._container; 10851 }, 10852 10853 // @method setOpacity(opacity: Number): this 10854 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 10855 setOpacity: function (opacity) { 10856 this.options.opacity = opacity; 10857 this._updateOpacity(); 10858 return this; 10859 }, 10860 10861 // @method setZIndex(zIndex: Number): this 10862 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 10863 setZIndex: function (zIndex) { 10864 this.options.zIndex = zIndex; 10865 this._updateZIndex(); 10866 10867 return this; 10868 }, 10869 10870 // @method isLoading: Boolean 10871 // Returns `true` if any tile in the grid layer has not finished loading. 10872 isLoading: function () { 10873 return this._loading; 10874 }, 10875 10876 // @method redraw: this 10877 // Causes the layer to clear all the tiles and request them again. 10878 redraw: function () { 10879 if (this._map) { 10880 this._removeAllTiles(); 10881 this._update(); 10882 } 10883 return this; 10884 }, 10885 10886 getEvents: function () { 10887 var events = { 10888 viewprereset: this._invalidateAll, 10889 viewreset: this._resetView, 10890 zoom: this._resetView, 10891 moveend: this._onMoveEnd 10892 }; 10893 10894 if (!this.options.updateWhenIdle) { 10895 // update tiles on move, but not more often than once per given interval 10896 if (!this._onMove) { 10897 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 10898 } 10899 10900 events.move = this._onMove; 10901 } 10902 10903 if (this._zoomAnimated) { 10904 events.zoomanim = this._animateZoom; 10905 } 10906 10907 return events; 10908 }, 10909 10910 // @section Extension methods 10911 // Layers extending `GridLayer` shall reimplement the following method. 10912 // @method createTile(coords: Object, done?: Function): HTMLElement 10913 // Called only internally, must be overridden by classes extending `GridLayer`. 10914 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 10915 // is specified, it must be called when the tile has finished loading and drawing. 10916 createTile: function () { 10917 return document.createElement('div'); 10918 }, 10919 10920 // @section 10921 // @method getTileSize: Point 10922 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 10923 getTileSize: function () { 10924 var s = this.options.tileSize; 10925 return s instanceof Point ? s : new Point(s, s); 10926 }, 10927 10928 _updateZIndex: function () { 10929 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 10930 this._container.style.zIndex = this.options.zIndex; 10931 } 10932 }, 10933 10934 _setAutoZIndex: function (compare) { 10935 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 10936 10937 var layers = this.getPane().children, 10938 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 10939 10940 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 10941 10942 zIndex = layers[i].style.zIndex; 10943 10944 if (layers[i] !== this._container && zIndex) { 10945 edgeZIndex = compare(edgeZIndex, +zIndex); 10946 } 10947 } 10948 10949 if (isFinite(edgeZIndex)) { 10950 this.options.zIndex = edgeZIndex + compare(-1, 1); 10951 this._updateZIndex(); 10952 } 10953 }, 10954 10955 _updateOpacity: function () { 10956 if (!this._map) { return; } 10957 10958 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 10959 if (ielt9) { return; } 10960 10961 setOpacity(this._container, this.options.opacity); 10962 10963 var now = +new Date(), 10964 nextFrame = false, 10965 willPrune = false; 10966 10967 for (var key in this._tiles) { 10968 var tile = this._tiles[key]; 10969 if (!tile.current || !tile.loaded) { continue; } 10970 10971 var fade = Math.min(1, (now - tile.loaded) / 200); 10972 10973 setOpacity(tile.el, fade); 10974 if (fade < 1) { 10975 nextFrame = true; 10976 } else { 10977 if (tile.active) { 10978 willPrune = true; 10979 } else { 10980 this._onOpaqueTile(tile); 10981 } 10982 tile.active = true; 10983 } 10984 } 10985 10986 if (willPrune && !this._noPrune) { this._pruneTiles(); } 10987 10988 if (nextFrame) { 10989 cancelAnimFrame(this._fadeFrame); 10990 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 10991 } 10992 }, 10993 10994 _onOpaqueTile: falseFn, 10995 10996 _initContainer: function () { 10997 if (this._container) { return; } 10998 10999 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || '')); 11000 this._updateZIndex(); 11001 11002 if (this.options.opacity < 1) { 11003 this._updateOpacity(); 11004 } 11005 11006 this.getPane().appendChild(this._container); 11007 }, 11008 11009 _updateLevels: function () { 11010 11011 var zoom = this._tileZoom, 11012 maxZoom = this.options.maxZoom; 11013 11014 if (zoom === undefined) { return undefined; } 11015 11016 for (var z in this._levels) { 11017 if (this._levels[z].el.children.length || z === zoom) { 11018 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 11019 this._onUpdateLevel(z); 11020 } else { 11021 remove(this._levels[z].el); 11022 this._removeTilesAtZoom(z); 11023 this._onRemoveLevel(z); 11024 delete this._levels[z]; 11025 } 11026 } 11027 11028 var level = this._levels[zoom], 11029 map = this._map; 11030 11031 if (!level) { 11032 level = this._levels[zoom] = {}; 11033 11034 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 11035 level.el.style.zIndex = maxZoom; 11036 11037 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 11038 level.zoom = zoom; 11039 11040 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 11041 11042 // force the browser to consider the newly added element for transition 11043 falseFn(level.el.offsetWidth); 11044 11045 this._onCreateLevel(level); 11046 } 11047 11048 this._level = level; 11049 11050 return level; 11051 }, 11052 11053 _onUpdateLevel: falseFn, 11054 11055 _onRemoveLevel: falseFn, 11056 11057 _onCreateLevel: falseFn, 11058 11059 _pruneTiles: function () { 11060 if (!this._map) { 11061 return; 11062 } 11063 11064 var key, tile; 11065 11066 var zoom = this._map.getZoom(); 11067 if (zoom > this.options.maxZoom || 11068 zoom < this.options.minZoom) { 11069 this._removeAllTiles(); 11070 return; 11071 } 11072 11073 for (key in this._tiles) { 11074 tile = this._tiles[key]; 11075 tile.retain = tile.current; 11076 } 11077 11078 for (key in this._tiles) { 11079 tile = this._tiles[key]; 11080 if (tile.current && !tile.active) { 11081 var coords = tile.coords; 11082 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 11083 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 11084 } 11085 } 11086 } 11087 11088 for (key in this._tiles) { 11089 if (!this._tiles[key].retain) { 11090 this._removeTile(key); 11091 } 11092 } 11093 }, 11094 11095 _removeTilesAtZoom: function (zoom) { 11096 for (var key in this._tiles) { 11097 if (this._tiles[key].coords.z !== zoom) { 11098 continue; 11099 } 11100 this._removeTile(key); 11101 } 11102 }, 11103 11104 _removeAllTiles: function () { 11105 for (var key in this._tiles) { 11106 this._removeTile(key); 11107 } 11108 }, 11109 11110 _invalidateAll: function () { 11111 for (var z in this._levels) { 11112 remove(this._levels[z].el); 11113 this._onRemoveLevel(z); 11114 delete this._levels[z]; 11115 } 11116 this._removeAllTiles(); 11117 11118 this._tileZoom = undefined; 11119 }, 11120 11121 _retainParent: function (x, y, z, minZoom) { 11122 var x2 = Math.floor(x / 2), 11123 y2 = Math.floor(y / 2), 11124 z2 = z - 1, 11125 coords2 = new Point(+x2, +y2); 11126 coords2.z = +z2; 11127 11128 var key = this._tileCoordsToKey(coords2), 11129 tile = this._tiles[key]; 11130 11131 if (tile && tile.active) { 11132 tile.retain = true; 11133 return true; 11134 11135 } else if (tile && tile.loaded) { 11136 tile.retain = true; 11137 } 11138 11139 if (z2 > minZoom) { 11140 return this._retainParent(x2, y2, z2, minZoom); 11141 } 11142 11143 return false; 11144 }, 11145 11146 _retainChildren: function (x, y, z, maxZoom) { 11147 11148 for (var i = 2 * x; i < 2 * x + 2; i++) { 11149 for (var j = 2 * y; j < 2 * y + 2; j++) { 11150 11151 var coords = new Point(i, j); 11152 coords.z = z + 1; 11153 11154 var key = this._tileCoordsToKey(coords), 11155 tile = this._tiles[key]; 11156 11157 if (tile && tile.active) { 11158 tile.retain = true; 11159 continue; 11160 11161 } else if (tile && tile.loaded) { 11162 tile.retain = true; 11163 } 11164 11165 if (z + 1 < maxZoom) { 11166 this._retainChildren(i, j, z + 1, maxZoom); 11167 } 11168 } 11169 } 11170 }, 11171 11172 _resetView: function (e) { 11173 var animating = e && (e.pinch || e.flyTo); 11174 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 11175 }, 11176 11177 _animateZoom: function (e) { 11178 this._setView(e.center, e.zoom, true, e.noUpdate); 11179 }, 11180 11181 _clampZoom: function (zoom) { 11182 var options = this.options; 11183 11184 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) { 11185 return options.minNativeZoom; 11186 } 11187 11188 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) { 11189 return options.maxNativeZoom; 11190 } 11191 11192 return zoom; 11193 }, 11194 11195 _setView: function (center, zoom, noPrune, noUpdate) { 11196 var tileZoom = this._clampZoom(Math.round(zoom)); 11197 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 11198 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 11199 tileZoom = undefined; 11200 } 11201 11202 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 11203 11204 if (!noUpdate || tileZoomChanged) { 11205 11206 this._tileZoom = tileZoom; 11207 11208 if (this._abortLoading) { 11209 this._abortLoading(); 11210 } 11211 11212 this._updateLevels(); 11213 this._resetGrid(); 11214 11215 if (tileZoom !== undefined) { 11216 this._update(center); 11217 } 11218 11219 if (!noPrune) { 11220 this._pruneTiles(); 11221 } 11222 11223 // Flag to prevent _updateOpacity from pruning tiles during 11224 // a zoom anim or a pinch gesture 11225 this._noPrune = !!noPrune; 11226 } 11227 11228 this._setZoomTransforms(center, zoom); 11229 }, 11230 11231 _setZoomTransforms: function (center, zoom) { 11232 for (var i in this._levels) { 11233 this._setZoomTransform(this._levels[i], center, zoom); 11234 } 11235 }, 11236 11237 _setZoomTransform: function (level, center, zoom) { 11238 var scale = this._map.getZoomScale(zoom, level.zoom), 11239 translate = level.origin.multiplyBy(scale) 11240 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 11241 11242 if (any3d) { 11243 setTransform(level.el, translate, scale); 11244 } else { 11245 setPosition(level.el, translate); 11246 } 11247 }, 11248 11249 _resetGrid: function () { 11250 var map = this._map, 11251 crs = map.options.crs, 11252 tileSize = this._tileSize = this.getTileSize(), 11253 tileZoom = this._tileZoom; 11254 11255 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 11256 if (bounds) { 11257 this._globalTileRange = this._pxBoundsToTileRange(bounds); 11258 } 11259 11260 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 11261 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 11262 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 11263 ]; 11264 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 11265 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 11266 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 11267 ]; 11268 }, 11269 11270 _onMoveEnd: function () { 11271 if (!this._map || this._map._animatingZoom) { return; } 11272 11273 this._update(); 11274 }, 11275 11276 _getTiledPixelBounds: function (center) { 11277 var map = this._map, 11278 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 11279 scale = map.getZoomScale(mapZoom, this._tileZoom), 11280 pixelCenter = map.project(center, this._tileZoom).floor(), 11281 halfSize = map.getSize().divideBy(scale * 2); 11282 11283 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 11284 }, 11285 11286 // Private method to load tiles in the grid's active zoom level according to map bounds 11287 _update: function (center) { 11288 var map = this._map; 11289 if (!map) { return; } 11290 var zoom = this._clampZoom(map.getZoom()); 11291 11292 if (center === undefined) { center = map.getCenter(); } 11293 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 11294 11295 var pixelBounds = this._getTiledPixelBounds(center), 11296 tileRange = this._pxBoundsToTileRange(pixelBounds), 11297 tileCenter = tileRange.getCenter(), 11298 queue = [], 11299 margin = this.options.keepBuffer, 11300 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 11301 tileRange.getTopRight().add([margin, -margin])); 11302 11303 // Sanity check: panic if the tile range contains Infinity somewhere. 11304 if (!(isFinite(tileRange.min.x) && 11305 isFinite(tileRange.min.y) && 11306 isFinite(tileRange.max.x) && 11307 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 11308 11309 for (var key in this._tiles) { 11310 var c = this._tiles[key].coords; 11311 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 11312 this._tiles[key].current = false; 11313 } 11314 } 11315 11316 // _update just loads more tiles. If the tile zoom level differs too much 11317 // from the map's, let _setView reset levels and prune old tiles. 11318 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 11319 11320 // create a queue of coordinates to load tiles from 11321 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 11322 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 11323 var coords = new Point(i, j); 11324 coords.z = this._tileZoom; 11325 11326 if (!this._isValidTile(coords)) { continue; } 11327 11328 var tile = this._tiles[this._tileCoordsToKey(coords)]; 11329 if (tile) { 11330 tile.current = true; 11331 } else { 11332 queue.push(coords); 11333 } 11334 } 11335 } 11336 11337 // sort tile queue to load tiles in order of their distance to center 11338 queue.sort(function (a, b) { 11339 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 11340 }); 11341 11342 if (queue.length !== 0) { 11343 // if it's the first batch of tiles to load 11344 if (!this._loading) { 11345 this._loading = true; 11346 // @event loading: Event 11347 // Fired when the grid layer starts loading tiles. 11348 this.fire('loading'); 11349 } 11350 11351 // create DOM fragment to append tiles in one batch 11352 var fragment = document.createDocumentFragment(); 11353 11354 for (i = 0; i < queue.length; i++) { 11355 this._addTile(queue[i], fragment); 11356 } 11357 11358 this._level.el.appendChild(fragment); 11359 } 11360 }, 11361 11362 _isValidTile: function (coords) { 11363 var crs = this._map.options.crs; 11364 11365 if (!crs.infinite) { 11366 // don't load tile if it's out of bounds and not wrapped 11367 var bounds = this._globalTileRange; 11368 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 11369 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 11370 } 11371 11372 if (!this.options.bounds) { return true; } 11373 11374 // don't load tile if it doesn't intersect the bounds in options 11375 var tileBounds = this._tileCoordsToBounds(coords); 11376 return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 11377 }, 11378 11379 _keyToBounds: function (key) { 11380 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 11381 }, 11382 11383 _tileCoordsToNwSe: function (coords) { 11384 var map = this._map, 11385 tileSize = this.getTileSize(), 11386 nwPoint = coords.scaleBy(tileSize), 11387 sePoint = nwPoint.add(tileSize), 11388 nw = map.unproject(nwPoint, coords.z), 11389 se = map.unproject(sePoint, coords.z); 11390 return [nw, se]; 11391 }, 11392 11393 // converts tile coordinates to its geographical bounds 11394 _tileCoordsToBounds: function (coords) { 11395 var bp = this._tileCoordsToNwSe(coords), 11396 bounds = new LatLngBounds(bp[0], bp[1]); 11397 11398 if (!this.options.noWrap) { 11399 bounds = this._map.wrapLatLngBounds(bounds); 11400 } 11401 return bounds; 11402 }, 11403 // converts tile coordinates to key for the tile cache 11404 _tileCoordsToKey: function (coords) { 11405 return coords.x + ':' + coords.y + ':' + coords.z; 11406 }, 11407 11408 // converts tile cache key to coordinates 11409 _keyToTileCoords: function (key) { 11410 var k = key.split(':'), 11411 coords = new Point(+k[0], +k[1]); 11412 coords.z = +k[2]; 11413 return coords; 11414 }, 11415 11416 _removeTile: function (key) { 11417 var tile = this._tiles[key]; 11418 if (!tile) { return; } 11419 11420 remove(tile.el); 11421 11422 delete this._tiles[key]; 11423 11424 // @event tileunload: TileEvent 11425 // Fired when a tile is removed (e.g. when a tile goes off the screen). 11426 this.fire('tileunload', { 11427 tile: tile.el, 11428 coords: this._keyToTileCoords(key) 11429 }); 11430 }, 11431 11432 _initTile: function (tile) { 11433 addClass(tile, 'leaflet-tile'); 11434 11435 var tileSize = this.getTileSize(); 11436 tile.style.width = tileSize.x + 'px'; 11437 tile.style.height = tileSize.y + 'px'; 11438 11439 tile.onselectstart = falseFn; 11440 tile.onmousemove = falseFn; 11441 11442 // update opacity on tiles in IE7-8 because of filter inheritance problems 11443 if (ielt9 && this.options.opacity < 1) { 11444 setOpacity(tile, this.options.opacity); 11445 } 11446 11447 // without this hack, tiles disappear after zoom on Chrome for Android 11448 // https://github.com/Leaflet/Leaflet/issues/2078 11449 if (android && !android23) { 11450 tile.style.WebkitBackfaceVisibility = 'hidden'; 11451 } 11452 }, 11453 11454 _addTile: function (coords, container) { 11455 var tilePos = this._getTilePos(coords), 11456 key = this._tileCoordsToKey(coords); 11457 11458 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 11459 11460 this._initTile(tile); 11461 11462 // if createTile is defined with a second argument ("done" callback), 11463 // we know that tile is async and will be ready later; otherwise 11464 if (this.createTile.length < 2) { 11465 // mark tile as ready, but delay one frame for opacity animation to happen 11466 requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 11467 } 11468 11469 setPosition(tile, tilePos); 11470 11471 // save tile in cache 11472 this._tiles[key] = { 11473 el: tile, 11474 coords: coords, 11475 current: true 11476 }; 11477 11478 container.appendChild(tile); 11479 // @event tileloadstart: TileEvent 11480 // Fired when a tile is requested and starts loading. 11481 this.fire('tileloadstart', { 11482 tile: tile, 11483 coords: coords 11484 }); 11485 }, 11486 11487 _tileReady: function (coords, err, tile) { 11488 if (err) { 11489 // @event tileerror: TileErrorEvent 11490 // Fired when there is an error loading a tile. 11491 this.fire('tileerror', { 11492 error: err, 11493 tile: tile, 11494 coords: coords 11495 }); 11496 } 11497 11498 var key = this._tileCoordsToKey(coords); 11499 11500 tile = this._tiles[key]; 11501 if (!tile) { return; } 11502 11503 tile.loaded = +new Date(); 11504 if (this._map._fadeAnimated) { 11505 setOpacity(tile.el, 0); 11506 cancelAnimFrame(this._fadeFrame); 11507 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11508 } else { 11509 tile.active = true; 11510 this._pruneTiles(); 11511 } 11512 11513 if (!err) { 11514 addClass(tile.el, 'leaflet-tile-loaded'); 11515 11516 // @event tileload: TileEvent 11517 // Fired when a tile loads. 11518 this.fire('tileload', { 11519 tile: tile.el, 11520 coords: coords 11521 }); 11522 } 11523 11524 if (this._noTilesToLoad()) { 11525 this._loading = false; 11526 // @event load: Event 11527 // Fired when the grid layer loaded all visible tiles. 11528 this.fire('load'); 11529 11530 if (ielt9 || !this._map._fadeAnimated) { 11531 requestAnimFrame(this._pruneTiles, this); 11532 } else { 11533 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 11534 // to trigger a pruning. 11535 setTimeout(bind(this._pruneTiles, this), 250); 11536 } 11537 } 11538 }, 11539 11540 _getTilePos: function (coords) { 11541 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 11542 }, 11543 11544 _wrapCoords: function (coords) { 11545 var newCoords = new Point( 11546 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 11547 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y); 11548 newCoords.z = coords.z; 11549 return newCoords; 11550 }, 11551 11552 _pxBoundsToTileRange: function (bounds) { 11553 var tileSize = this.getTileSize(); 11554 return new Bounds( 11555 bounds.min.unscaleBy(tileSize).floor(), 11556 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 11557 }, 11558 11559 _noTilesToLoad: function () { 11560 for (var key in this._tiles) { 11561 if (!this._tiles[key].loaded) { return false; } 11562 } 11563 return true; 11564 } 11565}); 11566 11567// @factory L.gridLayer(options?: GridLayer options) 11568// Creates a new instance of GridLayer with the supplied options. 11569function gridLayer(options) { 11570 return new GridLayer(options); 11571} 11572 11573/* 11574 * @class TileLayer 11575 * @inherits GridLayer 11576 * @aka L.TileLayer 11577 * 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`. 11578 * 11579 * @example 11580 * 11581 * ```js 11582 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map); 11583 * ``` 11584 * 11585 * @section URL template 11586 * @example 11587 * 11588 * A string of the following form: 11589 * 11590 * ``` 11591 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 11592 * ``` 11593 * 11594 * `{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. 11595 * 11596 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 11597 * 11598 * ``` 11599 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 11600 * ``` 11601 */ 11602 11603 11604var TileLayer = GridLayer.extend({ 11605 11606 // @section 11607 // @aka TileLayer options 11608 options: { 11609 // @option minZoom: Number = 0 11610 // The minimum zoom level down to which this layer will be displayed (inclusive). 11611 minZoom: 0, 11612 11613 // @option maxZoom: Number = 18 11614 // The maximum zoom level up to which this layer will be displayed (inclusive). 11615 maxZoom: 18, 11616 11617 // @option subdomains: String|String[] = 'abc' 11618 // 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
11618ings. 11619 subdomains: 'abc', 11620 11621 // @option errorTileUrl: String = '' 11622 // URL to the tile image to show in place of the tile that failed to load. 11623 errorTileUrl: '', 11624 11625 // @option zoomOffset: Number = 0 11626 // The zoom number used in tile URLs will be offset with this value. 11627 zoomOffset: 0, 11628 11629 // @option tms: Boolean = false 11630 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 11631 tms: false, 11632 11633 // @option zoomReverse: Boolean = false 11634 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 11635 zoomReverse: false, 11636 11637 // @option detectRetina: Boolean = false 11638 // 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. 11639 detectRetina: false, 11640 11641 // @option crossOrigin: Boolean|String = false 11642 // Whether the crossOrigin attribute will be added to the tiles. 11643 // 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. 11644 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 11645 crossOrigin: false 11646 }, 11647 11648 initialize: function (url, options) { 11649 11650 this._url = url; 11651 11652 options = setOptions(this, options); 11653 11654 // detecting retina displays, adjusting tileSize and zoom levels 11655 if (options.detectRetina && retina && options.maxZoom > 0) { 11656 11657 options.tileSize = Math.floor(options.tileSize / 2); 11658 11659 if (!options.zoomReverse) { 11660 options.zoomOffset++; 11661 options.maxZoom--; 11662 } else { 11663 options.zoomOffset--; 11664 options.minZoom++; 11665 } 11666 11667 options.minZoom = Math.max(0, options.minZoom); 11668 } 11669 11670 if (typeof options.subdomains === 'string') { 11671 options.subdomains = options.subdomains.split(''); 11672 } 11673 11674 // for https://github.com/Leaflet/Leaflet/issues/137 11675 if (!android) { 11676 this.on('tileunload', this._onTileRemove); 11677 } 11678 }, 11679 11680 // @method setUrl(url: String, noRedraw?: Boolean): this 11681 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 11682 // If the URL does not change, the layer will not be redrawn unless 11683 // the noRedraw parameter is set to false. 11684 setUrl: function (url, noRedraw) { 11685 if (this._url === url && noRedraw === undefined) { 11686 noRedraw = true; 11687 } 11688 11689 this._url = url; 11690 11691 if (!noRedraw) { 11692 this.redraw(); 11693 } 11694 return this; 11695 }, 11696 11697 // @method createTile(coords: Object, done?: Function): HTMLElement 11698 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 11699 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 11700 // callback is called when the tile has been loaded. 11701 createTile: function (coords, done) { 11702 var tile = document.createElement('img'); 11703 11704 on(tile, 'load', bind(this._tileOnLoad, this, done, tile)); 11705 on(tile, 'error', bind(this._tileOnError, this, done, tile)); 11706 11707 if (this.options.crossOrigin || this.options.crossOrigin === '') { 11708 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 11709 } 11710 11711 /* 11712 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons 11713 http://www.w3.org/TR/WCAG20-TECHS/H67 11714 */ 11715 tile.alt = ''; 11716 11717 /* 11718 Set role="presentation" to force screen readers to ignore this 11719 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation 11720 */ 11721 tile.setAttribute('role', 'presentation'); 11722 11723 tile.src = this.getTileUrl(coords); 11724 11725 return tile; 11726 }, 11727 11728 // @section Extension methods 11729 // @uninheritable 11730 // Layers extending `TileLayer` might reimplement the following method. 11731 // @method getTileUrl(coords: Object): String 11732 // Called only internally, returns the URL for a tile given its coordinates. 11733 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 11734 getTileUrl: function (coords) { 11735 var data = { 11736 r: retina ? '@2x' : '', 11737 s: this._getSubdomain(coords), 11738 x: coords.x, 11739 y: coords.y, 11740 z: this._getZoomForUrl() 11741 }; 11742 if (this._map && !this._map.options.crs.infinite) { 11743 var invertedY = this._globalTileRange.max.y - coords.y; 11744 if (this.options.tms) { 11745 data['y'] = invertedY; 11746 }
vendor: 48,575 bytes, lines 11747-13639
11747 data['-y'] = invertedY; 11748 } 11749 11750 return template(this._url, extend(data, this.options)); 11751 }, 11752 11753 _tileOnLoad: function (done, tile) { 11754 // For https://github.com/Leaflet/Leaflet/issues/3332 11755 if (ielt9) { 11756 setTimeout(bind(done, this, null, tile), 0); 11757 } else { 11758 done(null, tile); 11759 } 11760 }, 11761 11762 _tileOnError: function (done, tile, e) { 11763 var errorUrl = this.options.errorTileUrl; 11764 if (errorUrl && tile.getAttribute('src') !== errorUrl) { 11765 tile.src = errorUrl; 11766 } 11767 done(e, tile); 11768 }, 11769 11770 _onTileRemove: function (e) { 11771 e.tile.onload = null; 11772 }, 11773 11774 _getZoomForUrl: function () { 11775 var zoom = this._tileZoom, 11776 maxZoom = this.options.maxZoom, 11777 zoomReverse = this.options.zoomReverse, 11778 zoomOffset = this.options.zoomOffset; 11779 11780 if (zoomReverse) { 11781 zoom = maxZoom - zoom; 11782 } 11783 11784 return zoom + zoomOffset; 11785 }, 11786 11787 _getSubdomain: function (tilePoint) { 11788 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 11789 return this.options.subdomains[index]; 11790 }, 11791 11792 // stops loading all tiles in the background layer 11793 _abortLoading: function () { 11794 var i, tile; 11795 for (i in this._tiles) { 11796 if (this._tiles[i].coords.z !== this._tileZoom) { 11797 tile = this._tiles[i].el; 11798 11799 tile.onload = falseFn; 11800 tile.onerror = falseFn; 11801 11802 if (!tile.complete) { 11803 tile.src = emptyImageUrl; 11804 remove(tile); 11805 delete this._tiles[i]; 11806 } 11807 } 11808 } 11809 }, 11810 11811 _removeTile: function (key) { 11812 var tile = this._tiles[key]; 11813 if (!tile) { return; } 11814 11815 // Cancels any pending http requests associated with the tile 11816 // unless we're on Android's stock browser, 11817 // see https://github.com/Leaflet/Leaflet/issues/137 11818 if (!androidStock) { 11819 tile.el.setAttribute('src', emptyImageUrl); 11820 } 11821 11822 return GridLayer.prototype._removeTile.call(this, key); 11823 }, 11824 11825 _tileReady: function (coords, err, tile) { 11826 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) { 11827 return; 11828 } 11829 11830 return GridLayer.prototype._tileReady.call(this, coords, err, tile); 11831 } 11832}); 11833 11834 11835// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 11836// Instantiates a tile layer object given a `URL template` and optionally an options object. 11837 11838function tileLayer(url, options) { 11839 return new TileLayer(url, options); 11840} 11841 11842/* 11843 * @class TileLayer.WMS 11844 * @inherits TileLayer 11845 * @aka L.TileLayer.WMS 11846 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 11847 * 11848 * @example 11849 * 11850 * ```js 11851 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 11852 * layers: 'nexrad-n0r-900913', 11853 * format: 'image/png', 11854 * transparent: true, 11855 * attribution: "Weather data © 2012 IEM Nexrad" 11856 * }); 11857 * ``` 11858 */ 11859 11860var TileLayerWMS = TileLayer.extend({ 11861 11862 // @section 11863 // @aka TileLayer.WMS options 11864 // If any custom options not documented here are used, they will be sent to the 11865 // WMS server as extra parameters in each request URL. This can be useful for 11866 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 11867 defaultWmsParams: { 11868 service: 'WMS', 11869 request: 'GetMap', 11870 11871 // @option layers: String = '' 11872 // **(required)** Comma-separated list of WMS layers to show. 11873 layers: '', 11874 11875 // @option styles: String = '' 11876 // Comma-separated list of WMS styles. 11877 styles: '', 11878 11879 // @option format: String = 'image/jpeg' 11880 // WMS image format (use `'image/png'` for layers with transparency). 11881 format: 'image/jpeg', 11882 11883 // @option transparent: Boolean = false 11884 // If `true`, the WMS service will return images with transparency. 11885 transparent: false, 11886 11887 // @option version: String = '1.1.1' 11888 // Version of the WMS service to use 11889 version: '1.1.1' 11890 }, 11891 11892 options: { 11893 // @option crs: CRS = null 11894 // Coordinate Reference System to use for the WMS requests, defaults to 11895 // map CRS. Don't change this if you're not sure what it means. 11896 crs: null, 11897 11898 // @option uppercase: Boolean = false 11899 // If `true`, WMS request parameter keys will be uppercase. 11900 uppercase: false 11901 }, 11902 11903 initialize: function (url, options) { 11904 11905 this._url = url; 11906 11907 var wmsParams = extend({}, this.defaultWmsParams); 11908 11909 // all keys that are not TileLayer options go to WMS params 11910 for (var i in options) { 11911 if (!(i in this.options)) { 11912 wmsParams[i] = options[i]; 11913 } 11914 } 11915 11916 options = setOptions(this, options); 11917 11918 var realRetina = options.detectRetina && retina ? 2 : 1; 11919 var tileSize = this.getTileSize(); 11920 wmsParams.width = tileSize.x * realRetina; 11921 wmsParams.height = tileSize.y * realRetina; 11922 11923 this.wmsParams = wmsParams; 11924 }, 11925 11926 onAdd: function (map) { 11927 11928 this._crs = this.options.crs || map.options.crs; 11929 this._wmsVersion = parseFloat(this.wmsParams.version); 11930 11931 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 11932 this.wmsParams[projectionKey] = this._crs.code; 11933 11934 TileLayer.prototype.onAdd.call(this, map); 11935 }, 11936 11937 getTileUrl: function (coords) { 11938 11939 var tileBounds = this._tileCoordsToNwSe(coords), 11940 crs = this._crs, 11941 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), 11942 min = bounds.min, 11943 max = bounds.max, 11944 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? 11945 [min.y, min.x, max.y, max.x] : 11946 [min.x, min.y, max.x, max.y]).join(','), 11947 url = TileLayer.prototype.getTileUrl.call(this, coords); 11948 return url + 11949 getParamString(this.wmsParams, url, this.options.uppercase) + 11950 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 11951 }, 11952 11953 // @method setParams(params: Object, noRedraw?: Boolean): this 11954 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 11955 setParams: function (params, noRedraw) { 11956 11957 extend(this.wmsParams, params); 11958 11959 if (!noRedraw) { 11960 this.redraw(); 11961 } 11962 11963 return this; 11964 } 11965}); 11966 11967 11968// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 11969// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 11970function tileLayerWMS(url, options) { 11971 return new TileLayerWMS(url, options); 11972} 11973 11974TileLayer.WMS = TileLayerWMS; 11975tileLayer.wms = tileLayerWMS; 11976 11977/* 11978 * @class Renderer 11979 * @inherits Layer 11980 * @aka L.Renderer 11981 * 11982 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 11983 * DOM container of the renderer, its bounds, and its zoom animation. 11984 * 11985 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 11986 * itself can be added or removed to the map. All paths use a renderer, which can 11987 * be implicit (the map will decide the type of renderer and use it automatically) 11988 * or explicit (using the [`renderer`](#path-renderer) option of the path). 11989 * 11990 * Do not use this class directly, use `SVG` and `Canvas` instead. 11991 * 11992 * @event update: Event 11993 * Fired when the renderer updates its bounds, center and zoom, for example when 11994 * its map has moved 11995 */ 11996 11997var Renderer = Layer.extend({ 11998 11999 // @section 12000 // @aka Renderer options 12001 options: { 12002 // @option padding: Number = 0.1 12003 // How much to extend the clip area around the map view (relative to its size) 12004 // e.g. 0.1 would be 10% of map view in each direction 12005 padding: 0.1, 12006 12007 // @option tolerance: Number = 0 12008 // How much to extend click tolerance round a path/object on the map 12009 tolerance : 0 12010 }, 12011 12012 initialize: function (options) { 12013 setOptions(this, options); 12014 stamp(this); 12015 this._layers = this._layers || {}; 12016 }, 12017 12018 onAdd: function () { 12019 if (!this._container) { 12020 this._initContainer(); // defined by renderer implementations 12021 12022 if (this._zoomAnimated) { 12023 addClass(this._container, 'leaflet-zoom-animated'); 12024 } 12025 } 12026 12027 this.getPane().appendChild(this._container); 12028 this._update(); 12029 this.on('update', this._updatePaths, this); 12030 }, 12031 12032 onRemove: function () { 12033 this.off('update', this._updatePaths, this); 12034 this._destroyContainer(); 12035 }, 12036 12037 getEvents: function () { 12038 var events = { 12039 viewreset: this._reset, 12040 zoom: this._onZoom, 12041 moveend: this._update, 12042 zoomend: this._onZoomEnd 12043 }; 12044 if (this._zoomAnimated) { 12045 events.zoomanim = this._onAnimZoom; 12046 } 12047 return events; 12048 }, 12049 12050 _onAnimZoom: function (ev) { 12051 this._updateTransform(ev.center, ev.zoom); 12052 }, 12053 12054 _onZoom: function () { 12055 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 12056 }, 12057 12058 _updateTransform: function (center, zoom) { 12059 var scale = this._map.getZoomScale(zoom, this._zoom), 12060 position = getPosition(this._container), 12061 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 12062 currentCenterPoint = this._map.project(this._center, zoom), 12063 destCenterPoint = this._map.project(center, zoom), 12064 centerOffset = destCenterPoint.subtract(currentCenterPoint), 12065 12066 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset); 12067 12068 if (any3d) { 12069 setTransform(this._container, topLeftOffset, scale); 12070 } else { 12071 setPosition(this._container, topLeftOffset); 12072 } 12073 }, 12074 12075 _reset: function () { 12076 this._update(); 12077 this._updateTransform(this._center, this._zoom); 12078 12079 for (var id in this._layers) { 12080 this._layers[id]._reset(); 12081 } 12082 }, 12083 12084 _onZoomEnd: function () { 12085 for (var id in this._layers) { 12086 this._layers[id]._project(); 12087 } 12088 }, 12089 12090 _updatePaths: function () { 12091 for (var id in this._layers) { 12092 this._layers[id]._update(); 12093 } 12094 }, 12095 12096 _update: function () { 12097 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 12098 // Subclasses are responsible of firing the 'update' event. 12099 var p = this.options.padding, 12100 size = this._map.getSize(), 12101 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 12102 12103 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 12104 12105 this._center = this._map.getCenter(); 12106 this._zoom = this._map.getZoom(); 12107 } 12108}); 12109 12110/* 12111 * @class Canvas 12112 * @inherits Renderer 12113 * @aka L.Canvas 12114 * 12115 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 12116 * Inherits `Renderer`. 12117 * 12118 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not 12119 * available in all web browsers, notably IE8, and overlapping geometries might 12120 * not display properly in some edge cases. 12121 * 12122 * @example 12123 * 12124 * Use Canvas by default for all paths in the map: 12125 * 12126 * ```js 12127 * var map = L.map('map', { 12128 * renderer: L.canvas() 12129 * }); 12130 * ``` 12131 * 12132 * Use a Canvas renderer with extra padding for specific vector geometries: 12133 * 12134 * ```js 12135 * var map = L.map('map'); 12136 * var myRenderer = L.canvas({ padding: 0.5 }); 12137 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12138 * var circle = L.circle( center, { renderer: myRenderer } ); 12139 * ``` 12140 */ 12141 12142var Canvas = Renderer.extend({ 12143 getEvents: function () { 12144 var events = Renderer.prototype.getEvents.call(this); 12145 events.viewprereset = this._onViewPreReset; 12146 return events; 12147 }, 12148 12149 _onViewPreReset: function () { 12150 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 12151 this._postponeUpdatePaths = true; 12152 }, 12153 12154 onAdd: function () { 12155 Renderer.prototype.onAdd.call(this); 12156 12157 // Redraw vectors since canvas is cleared upon removal, 12158 // in case of removing the renderer itself from the map. 12159 this._draw(); 12160 }, 12161 12162 _initContainer: function () { 12163 var container = this._container = document.createElement('canvas'); 12164 12165 on(container, 'mousemove', this._onMouseMove, this); 12166 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); 12167 on(container, 'mouseout', this._handleMouseOut, this); 12168 12169 this._ctx = container.getContext('2d'); 12170 }, 12171 12172 _destroyContainer: function () { 12173 cancelAnimFrame(this._redrawRequest); 12174 delete this._ctx; 12175 remove(this._container); 12176 off(this._container); 12177 delete this._container; 12178 }, 12179 12180 _updatePaths: function () { 12181 if (this._postponeUpdatePaths) { return; } 12182 12183 var layer; 12184 this._redrawBounds = null; 12185 for (var id in this._layers) { 12186 layer = this._layers[id]; 12187 layer._update(); 12188 } 12189 this._redraw(); 12190 }, 12191 12192 _update: function () { 12193 if (this._map._animatingZoom && this._bounds) { return; } 12194 12195 Renderer.prototype._update.call(this); 12196 12197 var b = this._bounds, 12198 container = this._container, 12199 size = b.getSize(), 12200 m = retina ? 2 : 1; 12201 12202 setPosition(container, b.min); 12203 12204 // set canvas size (also clearing it); use double size on retina 12205 container.width = m * size.x; 12206 container.height = m * size.y; 12207 container.style.width = size.x + 'px'; 12208 container.style.height = size.y + 'px'; 12209 12210 if (retina) { 12211 this._ctx.scale(2, 2); 12212 } 12213 12214 // translate so we use the same path coordinates after canvas element moves 12215 this._ctx.translate(-b.min.x, -b.min.y); 12216 12217 // Tell paths to redraw themselves 12218 this.fire('update'); 12219 }, 12220 12221 _reset: function () { 12222 Renderer.prototype._reset.call(this); 12223 12224 if (this._postponeUpdatePaths) { 12225 this._postponeUpdatePaths = false; 12226 this._updatePaths(); 12227 } 12228 }, 12229 12230 _initPath: function (layer) { 12231 this._updateDashArray(layer); 12232 this._layers[stamp(layer)] = layer; 12233 12234 var order = layer._order = { 12235 layer: layer, 12236 prev: this._drawLast, 12237 next: null 12238 }; 12239 if (this._drawLast) { this._drawLast.next = order; } 12240 this._drawLast = order; 12241 this._drawFirst = this._drawFirst || this._drawLast; 12242 }, 12243 12244 _addPath: function (layer) { 12245 this._requestRedraw(layer); 12246 }, 12247 12248 _removePath: function (layer) { 12249 var order = layer._order; 12250 var next = order.next; 12251 var prev = order.prev; 12252 12253 if (next) { 12254 next.prev = prev; 12255 } else { 12256 this._drawLast = prev; 12257 } 12258 if (prev) { 12259 prev.next = next; 12260 } else { 12261 this._drawFirst = next; 12262 } 12263 12264 delete layer._order; 12265 12266 delete this._layers[stamp(layer)]; 12267 12268 this._requestRedraw(layer); 12269 }, 12270 12271 _updatePath: function (layer) { 12272 // Redraw the union of the layer's old pixel 12273 // bounds and the new pixel bounds. 12274 this._extendRedrawBounds(layer); 12275 layer._project(); 12276 layer._update(); 12277 // The redraw will extend the redraw bounds 12278 // with the new pixel bounds. 12279 this._requestRedraw(layer); 12280 }, 12281 12282 _updateStyle: function (layer) { 12283 this._updateDashArray(layer); 12284 this._requestRedraw(layer); 12285 }, 12286 12287 _updateDashArray: function (layer) { 12288 if (typeof layer.options.dashArray === 'string') { 12289 var parts = layer.options.dashArray.split(/[, ]+/), 12290 dashArray = [], 12291 dashValue, 12292 i; 12293 for (i = 0; i < parts.length; i++) { 12294 dashValue = Number(parts[i]); 12295 // Ignore dash array containing invalid lengths 12296 if (isNaN(dashValue)) { return; } 12297 dashArray.push(dashValue); 12298 } 12299 layer.options._dashArray = dashArray; 12300 } else { 12301 layer.options._dashArray = layer.options.dashArray; 12302 } 12303 }, 12304 12305 _requestRedraw: function (layer) { 12306 if (!this._map) { return; } 12307 12308 this._extendRedrawBounds(layer); 12309 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 12310 }, 12311 12312 _extendRedrawBounds: function (layer) { 12313 if (layer._pxBounds) { 12314 var padding = (layer.options.weight || 0) + 1; 12315 this._redrawBounds = this._redrawBounds || new Bounds(); 12316 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 12317 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 12318 } 12319 }, 12320 12321 _redraw: function () { 12322 this._redrawRequest = null; 12323 12324 if (this._redrawBounds) { 12325 this._redrawBounds.min._floor(); 12326 this._redrawBounds.max._ceil(); 12327 } 12328 12329 this._clear(); // clear layers in redraw bounds 12330 this._draw(); // draw layers 12331 12332 this._redrawBounds = null; 12333 }, 12334 12335 _clear: function () { 12336 var bounds = this._redrawBounds; 12337 if (bounds) { 12338 var size = bounds.getSize(); 12339 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 12340 } else { 12341 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 12342 } 12343 }, 12344 12345 _draw: function () { 12346 var layer, bounds = this._redrawBounds; 12347 this._ctx.save(); 12348 if (bounds) { 12349 var size = bounds.getSize(); 12350 this._ctx.beginPath(); 12351 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 12352 this._ctx.clip(); 12353 } 12354 12355 this._drawing = true; 12356 12357 for (var order = this._drawFirst; order; order = order.next) { 12358 layer = order.layer; 12359 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 12360 layer._updatePath(); 12361 } 12362 } 12363 12364 this._drawing = false; 12365 12366 this._ctx.restore(); // Restore state before clipping. 12367 }, 12368 12369 _updatePoly: function (layer, closed) { 12370 if (!this._drawing) { return; } 12371 12372 var i, j, len2, p, 12373 parts = layer._parts, 12374 len = parts.length, 12375 ctx = this._ctx; 12376 12377 if (!len) { return; } 12378 12379 ctx.beginPath(); 12380 12381 for (i = 0; i < len; i++) { 12382 for (j = 0, len2 = parts[i].length; j < len2; j++) { 12383 p = parts[i][j]; 12384 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 12385 } 12386 if (closed) { 12387 ctx.closePath(); 12388 } 12389 } 12390 12391 this._fillStroke(ctx, layer); 12392 12393 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 12394 }, 12395 12396 _updateCircle: function (layer) { 12397 12398 if (!this._drawing || layer._empty()) { return; } 12399 12400 var p = layer._point, 12401 ctx = this._ctx, 12402 r = Math.max(Math.round(layer._radius), 1), 12403 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 12404 12405 if (s !== 1) { 12406 ctx.save(); 12407 ctx.scale(1, s); 12408 } 12409 12410 ctx.beginPath(); 12411 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 12412 12413 if (s !== 1) { 12414 ctx.restore(); 12415 } 12416 12417 this._fillStroke(ctx, layer); 12418 }, 12419 12420 _fillStroke: function (ctx, layer) { 12421 var options = layer.options; 12422 12423 if (options.fill) { 12424 ctx.globalAlpha = options.fillOpacity; 12425 ctx.fillStyle = options.fillColor || options.color; 12426 ctx.fill(options.fillRule || 'evenodd'); 12427 } 12428 12429 if (options.stroke && options.weight !== 0) { 12430 if (ctx.setLineDash) { 12431 ctx.setLineDash(layer.options && layer.options._dashArray || []); 12432 } 12433 ctx.globalAlpha = options.opacity; 12434 ctx.lineWidth = options.weight; 12435 ctx.strokeStyle = options.color; 12436 ctx.lineCap = options.lineCap; 12437 ctx.lineJoin = options.lineJoin; 12438 ctx.stroke(); 12439 } 12440 }, 12441 12442 // Canvas obviously doesn't have mouse events for individual drawn objects, 12443 // so we emulate that by calculating what's under the mouse on mousemove/click manually 12444 12445 _onClick: function (e) { 12446 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 12447 12448 for (var order = this._drawFirst; order; order = order.next) { 12449 layer = order.layer; 12450 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) { 12451 clickedLayer = layer; 12452 } 12453 } 12454 if (clickedLayer) { 12455 fakeStop(e); 12456 this._fireEvent([clickedLayer], e); 12457 } 12458 }, 12459 12460 _onMouseMove: function (e) { 12461 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 12462 12463 var point = this._map.mouseEventToLayerPoint(e); 12464 this._handleMouseHover(e, point); 12465 }, 12466 12467 12468 _handleMouseOut: function (e) { 12469 var layer = this._hoveredLayer; 12470 if (layer) { 12471 // if we're leaving the layer, fire mouseout 12472 removeClass(this._container, 'leaflet-interactive'); 12473 this._fireEvent([layer], e, 'mouseout'); 12474 this._hoveredLayer = null; 12475 this._mouseHoverThrottled = false; 12476 } 12477 }, 12478 12479 _handleMouseHover: function (e, point) { 12480 if (this._mouseHoverThrottled) { 12481 return; 12482 } 12483 12484 var layer, candidateHoveredLayer; 12485 12486 for (var order = this._drawFirst; order; order = order.next) { 12487 layer = order.layer; 12488 if (layer.options.interactive && layer._containsPoint(point)) { 12489 candidateHoveredLayer = layer; 12490 } 12491 } 12492 12493 if (candidateHoveredLayer !== this._hoveredLayer) { 12494 this._handleMouseOut(e); 12495 12496 if (candidateHoveredLayer) { 12497 addClass(this._container, 'leaflet-interactive'); // change cursor 12498 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 12499 this._hoveredLayer = candidateHoveredLayer; 12500 } 12501 } 12502 12503 if (this._hoveredLayer) { 12504 this._fireEvent([this._hoveredLayer], e); 12505 } 12506 12507 this._mouseHoverThrottled = true; 12508 setTimeout(L.bind(function () { 12509 this._mouseHoverThrottled = false; 12510 }, this), 32); 12511 }, 12512 12513 _fireEvent: function (layers, e, type) { 12514 this._map._fireDOMEvent(e, type || e.type, layers); 12515 }, 12516 12517 _bringToFront: function (layer) { 12518 var order = layer._order; 12519 12520 if (!order) { return; } 12521 12522 var next = order.next; 12523 var prev = order.prev; 12524 12525 if (next) { 12526 next.prev = prev; 12527 } else { 12528 // Already last 12529 return; 12530 } 12531 if (prev) { 12532 prev.next = next; 12533 } else if (next) { 12534 // Update first entry unless this is the 12535 // single entry 12536 this._drawFirst = next; 12537 } 12538 12539 order.prev = this._drawLast; 12540 this._drawLast.next = order; 12541 12542 order.next = null; 12543 this._drawLast = order; 12544 12545 this._requestRedraw(layer); 12546 }, 12547 12548 _bringToBack: function (layer) { 12549 var order = layer._order; 12550 12551 if (!order) { return; } 12552 12553 var next = order.next; 12554 var prev = order.prev; 12555 12556 if (prev) { 12557 prev.next = next; 12558 } else { 12559 // Already first 12560 return; 12561 } 12562 if (next) { 12563 next.prev = prev; 12564 } else if (prev) { 12565 // Update last entry unless this is the 12566 // single entry 12567 this._drawLast = prev; 12568 } 12569 12570 order.prev = null; 12571 12572 order.next = this._drawFirst; 12573 this._drawFirst.prev = order; 12574 this._drawFirst = order; 12575 12576 this._requestRedraw(layer); 12577 } 12578}); 12579 12580// @factory L.canvas(options?: Renderer options) 12581// Creates a Canvas renderer with the given options. 12582function canvas$1(options) { 12583 return canvas ? new Canvas(options) : null; 12584} 12585 12586/* 12587 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 12588 */ 12589 12590 12591var vmlCreate = (function () { 12592 try { 12593 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 12594 return function (name) { 12595 return document.createElement('<lvml:' + name + ' class="lvml">'); 12596 }; 12597 } catch (e) { 12598 return function (name) { 12599 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 12600 }; 12601 } 12602})(); 12603 12604 12605/* 12606 * @class SVG 12607 * 12608 * 12609 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 12610 * with old versions of Internet Explorer. 12611 */ 12612 12613// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences 12614var vmlMixin = { 12615 12616 _initContainer: function () { 12617 this._container = create$1('div', 'leaflet-vml-container'); 12618 }, 12619 12620 _update: function () { 12621 if (this._map._animatingZoom) { return; } 12622 Renderer.prototype._update.call(this); 12623 this.fire('update'); 12624 }, 12625 12626 _initPath: function (layer) { 12627 var container = layer._container = vmlCreate('shape'); 12628 12629 addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 12630 12631 container.coordsize = '1 1'; 12632 12633 layer._path = vmlCreate('path'); 12634 container.appendChild(layer._path); 12635 12636 this._updateStyle(layer); 12637 this._layers[stamp(layer)] = layer; 12638 }, 12639 12640 _addPath: function (layer) { 12641 var container = layer._container; 12642 this._container.appendChild(container); 12643 12644 if (layer.options.interactive) { 12645 layer.addInteractiveTarget(container); 12646 } 12647 }, 12648 12649 _removePath: function (layer) { 12650 var container = layer._container; 12651 remove(container); 12652 layer.removeInteractiveTarget(container); 12653 delete this._layers[stamp(layer)]; 12654 }, 12655 12656 _updateStyle: function (layer) { 12657 var stroke = layer._stroke, 12658 fill = layer._fill, 12659 options = layer.options, 12660 container = layer._container; 12661 12662 container.stroked = !!options.stroke; 12663 container.filled = !!options.fill; 12664 12665 if (options.stroke) { 12666 if (!stroke) { 12667 stroke = layer._stroke = vmlCreate('stroke'); 12668 } 12669 container.appendChild(stroke); 12670 stroke.weight = options.weight + 'px'; 12671 stroke.color = options.color; 12672 stroke.opacity = options.opacity; 12673 12674 if (options.dashArray) { 12675 stroke.dashStyle = isArray(options.dashArray) ? 12676 options.dashArray.join(' ') : 12677 options.dashArray.replace(/( *, *)/g, ' '); 12678 } else { 12679 stroke.dashStyle = ''; 12680 } 12681 stroke.endcap = options.lineCap.replace('butt', 'flat'); 12682 stroke.joinstyle = options.lineJoin; 12683 12684 } else if (stroke) { 12685 container.removeChild(stroke); 12686 layer._stroke = null; 12687 } 12688 12689 if (options.fill) { 12690 if (!fill) { 12691 fill = layer._fill = vmlCreate('fill'); 12692 } 12693 container.appendChild(fill); 12694 fill.color = options.fillColor || options.color; 12695 fill.opacity = options.fillOpacity; 12696 12697 } else if (fill) { 12698 container.removeChild(fill); 12699 layer._fill = null; 12700 } 12701 }, 12702 12703 _updateCircle: function (layer) { 12704 var p = layer._point.round(), 12705 r = Math.round(layer._radius), 12706 r2 = Math.round(layer._radiusY || r); 12707 12708 this._setPath(layer, layer._empty() ? 'M0 0' : 12709 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 12710 }, 12711 12712 _setPath: function (layer, path) { 12713 layer._path.v = path; 12714 }, 12715 12716 _bringToFront: function (layer) { 12717 toFront(layer._container); 12718 }, 12719 12720 _bringToBack: function (layer) { 12721 toBack(layer._container); 12722 } 12723}; 12724 12725var create$2 = vml ? vmlCreate : svgCreate; 12726 12727/* 12728 * @class SVG 12729 * @inherits Renderer 12730 * @aka L.SVG 12731 * 12732 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 12733 * Inherits `Renderer`. 12734 * 12735 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not 12736 * available in all web browsers, notably Android 2.x and 3.x. 12737 * 12738 * Although SVG is not available on IE7 and IE8, these browsers support 12739 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 12740 * (a now deprecated technology), and the SVG renderer will fall back to VML in 12741 * this case. 12742 * 12743 * @example 12744 * 12745 * Use SVG by default for all paths in the map: 12746 * 12747 * ```js 12748 * var map = L.map('map', { 12749 * renderer: L.svg() 12750 * }); 12751 * ``` 12752 * 12753 * Use a SVG renderer with extra padding for specific vector geometries: 12754 * 12755 * ```js 12756 * var map = L.map('map'); 12757 * var myRenderer = L.svg({ padding: 0.5 }); 12758 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12759 * var circle = L.circle( center, { renderer: myRenderer } ); 12760 * ``` 12761 */ 12762 12763var SVG = Renderer.extend({ 12764 12765 getEvents: function () { 12766 var events = Renderer.prototype.getEvents.call(this); 12767 events.zoomstart = this._onZoomStart; 12768 return events; 12769 }, 12770 12771 _initContainer: function () { 12772 this._container = create$2('svg'); 12773 12774 // makes it possible to click through svg root; we'll reset it back in individual paths 12775 this._container.setAttribute('pointer-events', 'none'); 12776 12777 this._rootGroup = create$2('g'); 12778 this._container.appendChild(this._rootGroup); 12779 }, 12780 12781 _destroyContainer: function () { 12782 remove(this._container); 12783 off(this._container); 12784 delete this._container; 12785 delete this._rootGroup; 12786 delete this._svgSize; 12787 }, 12788 12789 _onZoomStart: function () { 12790 // Drag-then-pinch interactions might mess up the center and zoom. 12791 // In this case, the easiest way to prevent this is re-do the renderer 12792 // bounds and padding when the zooming starts. 12793 this._update(); 12794 }, 12795 12796 _update: function () { 12797 if (this._map._animatingZoom && this._bounds) { return; } 12798 12799 Renderer.prototype._update.call(this); 12800 12801 var b = this._bounds, 12802 size = b.getSize(), 12803 container = this._container; 12804 12805 // set size of svg-container if changed 12806 if (!this._svgSize || !this._svgSize.equals(size)) { 12807 this._svgSize = size; 12808 container.setAttribute('width', size.x); 12809 container.setAttribute('height', size.y); 12810 } 12811 12812 // movement: update container viewBox so that we don't have to change coordinates of individual layers 12813 setPosition(container, b.min); 12814 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 12815 12816 this.fire('update'); 12817 }, 12818 12819 // methods below are called by vector layers implementations 12820 12821 _initPath: function (layer) { 12822 var path = layer._path = create$2('path'); 12823 12824 // @namespace Path 12825 // @option className: String = null 12826 // Custom class name set on an element. Only for SVG renderer. 12827 if (layer.options.className) { 12828 addClass(path, layer.options.className); 12829 } 12830 12831 if (layer.options.interactive) { 12832 addClass(path, 'leaflet-interactive'); 12833 } 12834 12835 this._updateStyle(layer); 12836 this._layers[stamp(layer)] = layer; 12837 }, 12838 12839 _addPath: function (layer) { 12840 if (!this._rootGroup) { this._initContainer(); } 12841 this._rootGroup.appendChild(layer._path); 12842 layer.addInteractiveTarget(layer._path); 12843 }, 12844 12845 _removePath: function (layer) { 12846 remove(layer._path); 12847 layer.removeInteractiveTarget(layer._path); 12848 delete this._layers[stamp(layer)]; 12849 }, 12850 12851 _updatePath: function (layer) { 12852 layer._project(); 12853 layer._update(); 12854 }, 12855 12856 _updateStyle: function (layer) { 12857 var path = layer._path, 12858 options = layer.options; 12859 12860 if (!path) { return; } 12861 12862 if (options.stroke) { 12863 path.setAttribute('stroke', options.color); 12864 path.setAttribute('stroke-opacity', options.opacity); 12865 path.setAttribute('stroke-width', options.weight); 12866 path.setAttribute('stroke-linecap', options.lineCap); 12867 path.setAttribute('stroke-linejoin', options.lineJoin); 12868 12869 if (options.dashArray) { 12870 path.setAttribute('stroke-dasharray', options.dashArray); 12871 } else { 12872 path.removeAttribute('stroke-dasharray'); 12873 } 12874 12875 if (options.dashOffset) { 12876 path.setAttribute('stroke-dashoffset', options.dashOffset); 12877 } else { 12878 path.removeAttribute('stroke-dashoffset'); 12879 } 12880 } else { 12881 path.setAttribute('stroke', 'none'); 12882 } 12883 12884 if (options.fill) { 12885 path.setAttribute('fill', options.fillColor || options.color); 12886 path.setAttribute('fill-opacity', options.fillOpacity); 12887 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 12888 } else { 12889 path.setAttribute('fill', 'none'); 12890 } 12891 }, 12892 12893 _updatePoly: function (layer, closed) { 12894 this._setPath(layer, pointsToPath(layer._parts, closed)); 12895 }, 12896 12897 _updateCircle: function (layer) { 12898 var p = layer._point, 12899 r = Math.max(Math.round(layer._radius), 1), 12900 r2 = Math.max(Math.round(layer._radiusY), 1) || r, 12901 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 12902 12903 // drawing a circle with two half-arcs 12904 var d = layer._empty() ? 'M0 0' : 12905 'M' + (p.x - r) + ',' + p.y + 12906 arc + (r * 2) + ',0 ' + 12907 arc + (-r * 2) + ',0 '; 12908 12909 this._setPath(layer, d); 12910 }, 12911 12912 _setPath: function (layer, path) { 12913 layer._path.setAttribute('d', path); 12914 }, 12915 12916 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 12917 _bringToFront: function (layer) { 12918 toFront(layer._path); 12919 }, 12920 12921 _bringToBack: function (layer) { 12922 toBack(layer._path); 12923 } 12924}); 12925 12926if (vml) { 12927 SVG.include(vmlMixin); 12928} 12929 12930// @namespace SVG 12931// @factory L.svg(options?: Renderer options) 12932// Creates a SVG renderer with the given options. 12933function svg$1(options) { 12934 return svg || vml ? new SVG(options) : null; 12935} 12936 12937Map.include({ 12938 // @namespace Map; @method getRenderer(layer: Path): Renderer 12939 // Returns the instance of `Renderer` that should be used to render the given 12940 // `Path`. It will ensure that the `renderer` options of the map and paths 12941 // are respected, and that the renderers do exist on the map. 12942 getRenderer: function (layer) { 12943 // @namespace Path; @option renderer: Renderer 12944 // Use this specific instance of `Renderer` for this path. Takes 12945 // precedence over the map's [default renderer](#map-renderer). 12946 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 12947 12948 if (!renderer) { 12949 renderer = this._renderer = this._createRenderer(); 12950 } 12951 12952 if (!this.hasLayer(renderer)) { 12953 this.addLayer(renderer); 12954 } 12955 return renderer; 12956 }, 12957 12958 _getPaneRenderer: function (name) { 12959 if (name === 'overlayPane' || name === undefined) { 12960 return false; 12961 } 12962 12963 var renderer = this._paneRenderers[name]; 12964 if (renderer === undefined) { 12965 renderer = this._createRenderer({pane: name}); 12966 this._paneRenderers[name] = renderer; 12967 } 12968 return renderer; 12969 }, 12970 12971 _createRenderer: function (options) { 12972 // @namespace Map; @option preferCanvas: Boolean = false 12973 // Whether `Path`s should be rendered on a `Canvas` renderer. 12974 // By default, all `Path`s are rendered in a `SVG` renderer. 12975 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options); 12976 } 12977}); 12978 12979/* 12980 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. 12981 */ 12982 12983/* 12984 * @class Rectangle 12985 * @aka L.Rectangle 12986 * @inherits Polygon 12987 * 12988 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 12989 * 12990 * @example 12991 * 12992 * ```js 12993 * // define rectangle geographical bounds 12994 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 12995 * 12996 * // create an orange rectangle 12997 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 12998 * 12999 * // zoom the map to the rectangle bounds 13000 * map.fitBounds(bounds); 13001 * ``` 13002 * 13003 */ 13004 13005 13006var Rectangle = Polygon.extend({ 13007 initialize: function (latLngBounds, options) { 13008 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 13009 }, 13010 13011 // @method setBounds(latLngBounds: LatLngBounds): this 13012 // Redraws the rectangle with the passed bounds. 13013 setBounds: function (latLngBounds) { 13014 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 13015 }, 13016 13017 _boundsToLatLngs: function (latLngBounds) { 13018 latLngBounds = toLatLngBounds(latLngBounds); 13019 return [ 13020 latLngBounds.getSouthWest(), 13021 latLngBounds.getNorthWest(), 13022 latLngBounds.getNorthEast(), 13023 latLngBounds.getSouthEast() 13024 ]; 13025 } 13026}); 13027 13028 13029// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 13030function rectangle(latLngBounds, options) { 13031 return new Rectangle(latLngBounds, options); 13032} 13033 13034SVG.create = create$2; 13035SVG.pointsToPath = pointsToPath; 13036 13037GeoJSON.geometryToLayer = geometryToLayer; 13038GeoJSON.coordsToLatLng = coordsToLatLng; 13039GeoJSON.coordsToLatLngs = coordsToLatLngs; 13040GeoJSON.latLngToCoords = latLngToCoords; 13041GeoJSON.latLngsToCoords = latLngsToCoords; 13042GeoJSON.getFeature = getFeature; 13043GeoJSON.asFeature = asFeature; 13044 13045/* 13046 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 13047 * (zoom to a selected bounding box), enabled by default. 13048 */ 13049 13050// @namespace Map 13051// @section Interaction Options 13052Map.mergeOptions({ 13053 // @option boxZoom: Boolean = true 13054 // Whether the map can be zoomed to a rectangular area specified by 13055 // dragging the mouse while pressing the shift key. 13056 boxZoom: true 13057}); 13058 13059var BoxZoom = Handler.extend({ 13060 initialize: function (map) { 13061 this._map = map; 13062 this._container = map._container; 13063 this._pane = map._panes.overlayPane; 13064 this._resetStateTimeout = 0; 13065 map.on('unload', this._destroy, this); 13066 }, 13067 13068 addHooks: function () { 13069 on(this._container, 'mousedown', this._onMouseDown, this); 13070 }, 13071 13072 removeHooks: function () { 13073 off(this._container, 'mousedown', this._onMouseDown, this); 13074 }, 13075 13076 moved: function () { 13077 return this._moved; 13078 }, 13079 13080 _destroy: function () { 13081 remove(this._pane); 13082 delete this._pane; 13083 }, 13084 13085 _resetState: function () { 13086 this._resetStateTimeout = 0; 13087 this._moved = false; 13088 }, 13089 13090 _clearDeferredResetState: function () { 13091 if (this._resetStateTimeout !== 0) { 13092 clearTimeout(this._resetStateTimeout); 13093 this._resetStateTimeout = 0; 13094 } 13095 }, 13096 13097 _onMouseDown: function (e) { 13098 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 13099 13100 // Clear the deferred resetState if it hasn't executed yet, otherwise it 13101 // will interrupt the interaction and orphan a box element in the container. 13102 this._clearDeferredResetState(); 13103 this._resetState(); 13104 13105 disableTextSelection(); 13106 disableImageDrag(); 13107 13108 this._startPoint = this._map.mouseEventToContainerPoint(e); 13109 13110 on(document, { 13111 contextmenu: stop, 13112 mousemove: this._onMouseMove, 13113 mouseup: this._onMouseUp, 13114 keydown: this._onKeyDown 13115 }, this); 13116 }, 13117 13118 _onMouseMove: function (e) { 13119 if (!this._moved) { 13120 this._moved = true; 13121 13122 this._box = create$1('div', 'leaflet-zoom-box', this._container); 13123 addClass(this._container, 'leaflet-crosshair'); 13124 13125 this._map.fire('boxzoomstart'); 13126 } 13127 13128 this._point = this._map.mouseEventToContainerPoint(e); 13129 13130 var bounds = new Bounds(this._point, this._startPoint), 13131 size = bounds.getSize(); 13132 13133 setPosition(this._box, bounds.min); 13134 13135 this._box.style.width = size.x + 'px'; 13136 this._box.style.height = size.y + 'px'; 13137 }, 13138 13139 _finish: function () { 13140 if (this._moved) { 13141 remove(this._box); 13142 removeClass(this._container, 'leaflet-crosshair'); 13143 } 13144 13145 enableTextSelection(); 13146 enableImageDrag(); 13147 13148 off(document, { 13149 contextmenu: stop, 13150 mousemove: this._onMouseMove, 13151 mouseup: this._onMouseUp, 13152 keydown: this._onKeyDown 13153 }, this); 13154 }, 13155 13156 _onMouseUp: function (e) { 13157 if ((e.which !== 1) && (e.button !== 1)) { return; } 13158 13159 this._finish(); 13160 13161 if (!this._moved) { return; } 13162 // Postpone to next JS tick so internal click event handling 13163 // still see it as "moved". 13164 this._clearDeferredResetState(); 13165 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 13166 13167 var bounds = new LatLngBounds( 13168 this._map.containerPointToLatLng(this._startPoint), 13169 this._map.containerPointToLatLng(this._point)); 13170 13171 this._map 13172 .fitBounds(bounds) 13173 .fire('boxzoomend', {boxZoomBounds: bounds}); 13174 }, 13175 13176 _onKeyDown: function (e) { 13177 if (e.keyCode === 27) { 13178 this._finish(); 13179 } 13180 } 13181}); 13182 13183// @section Handlers 13184// @property boxZoom: Handler 13185// Box (shift-drag with mouse) zoom handler. 13186Map.addInitHook('addHandler', 'boxZoom', BoxZoom); 13187 13188/* 13189 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 13190 */ 13191 13192// @namespace Map 13193// @section Interaction Options 13194 13195Map.mergeOptions({ 13196 // @option doubleClickZoom: Boolean|String = true 13197 // Whether the map can be zoomed in by double clicking on it and 13198 // zoomed out by double clicking while holding shift. If passed 13199 // `'center'`, double-click zoom will zoom to the center of the 13200 // view regardless of where the mouse was. 13201 doubleClickZoom: true 13202}); 13203 13204var DoubleClickZoom = Handler.extend({ 13205 addHooks: function () { 13206 this._map.on('dblclick', this._onDoubleClick, this); 13207 }, 13208 13209 removeHooks: function () { 13210 this._map.off('dblclick', this._onDoubleClick, this); 13211 }, 13212 13213 _onDoubleClick: function (e) { 13214 var map = this._map, 13215 oldZoom = map.getZoom(), 13216 delta = map.options.zoomDelta, 13217 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 13218 13219 if (map.options.doubleClickZoom === 'center') { 13220 map.setZoom(zoom); 13221 } else { 13222 map.setZoomAround(e.containerPoint, zoom); 13223 } 13224 } 13225}); 13226 13227// @section Handlers 13228// 13229// Map properties include interaction handlers that allow you to control 13230// interaction behavior in runtime, enabling or disabling certain features such 13231// as dragging or touch zoom (see `Handler` methods). For example: 13232// 13233// ```js 13234// map.doubleClickZoom.disable(); 13235// ``` 13236// 13237// @property doubleClickZoom: Handler 13238// Double click zoom handler. 13239Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); 13240 13241/* 13242 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 13243 */ 13244 13245// @namespace Map 13246// @section Interaction Options 13247Map.mergeOptions({ 13248 // @option dragging: Boolean = true 13249 // Whether the map be draggable with mouse/touch or not. 13250 dragging: true, 13251 13252 // @section Panning Inertia Options 13253 // @option inertia: Boolean = * 13254 // If enabled, panning of the map will have an inertia effect where 13255 // the map builds momentum while dragging and continues moving in 13256 // the same direction for some time. Feels especially nice on touch 13257 // devices. Enabled by default unless running on old Android devices. 13258 inertia: !android23, 13259 13260 // @option inertiaDeceleration: Number = 3000 13261 // The rate with which the inertial movement slows down, in pixels/second². 13262 inertiaDeceleration: 3400, // px/s^2 13263 13264 // @option inertiaMaxSpeed: Number = Infinity 13265 // Max speed of the inertial movement, in pixels/second. 13266 inertiaMaxSpeed: Infinity, // px/s 13267 13268 // @option easeLinearity: Number = 0.2 13269 easeLinearity: 0.2, 13270 13271 // TODO refactor, move to CRS 13272 // @option worldCopyJump: Boolean = false 13273 // With this option enabled, the map tracks when you pan to another "copy" 13274 // of the world and seamlessly jumps to the original one so that all overlays 13275 // like markers and vector layers are still visible. 13276 worldCopyJump: false, 13277 13278 // @option maxBoundsViscosity: Number = 0.0 13279 // If `maxBounds` is set, this option will control how solid the bounds 13280 // are when dragging the map around. The default value of `0.0` allows the 13281 // user to drag outside the bounds at normal speed, higher values will 13282 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 13283 // solid, preventing the user from dragging outside the bounds. 13284 maxBoundsViscosity: 0.0 13285}); 13286 13287var Drag = Handler.extend({ 13288 addHooks: function () { 13289 if (!this._draggable) { 13290 var map = this._map; 13291 13292 this._draggable = new Draggable(map._mapPane, map._container); 13293 13294 this._draggable.on({ 13295 dragstart: this._onDragStart, 13296 drag: this._onDrag, 13297 dragend: this._onDragEnd 13298 }, this); 13299 13300 this._draggable.on('predrag', this._onPreDragLimit, this); 13301 if (map.options.worldCopyJump) { 13302 this._draggable.on('predrag', this._onPreDragWrap, this); 13303 map.on('zoomend', this._onZoomEnd, this); 13304 13305 map.whenReady(this._onZoomEnd, this); 13306 } 13307 } 13308 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 13309 this._draggable.enable(); 13310 this._positions = []; 13311 this._times = []; 13312 }, 13313 13314 removeHooks: function () { 13315 removeClass(this._map._container, 'leaflet-grab'); 13316 removeClass(this._map._container, 'leaflet-touch-drag'); 13317 this._draggable.disable(); 13318 }, 13319 13320 moved: function () { 13321 return this._draggable && this._draggable._moved; 13322 }, 13323 13324 moving: function () { 13325 return this._draggable && this._draggable._moving; 13326 }, 13327 13328 _onDragStart: function () { 13329 var map = this._map; 13330 13331 map._stop(); 13332 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 13333 var bounds = toLatLngBounds(this._map.options.maxBounds); 13334 13335 this._offsetLimit = toBounds( 13336 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 13337 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 13338 .add(this._map.getSize())); 13339 13340 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 13341 } else { 13342 this._offsetLimit = null; 13343 } 13344 13345 map 13346 .fire('movestart') 13347 .fire('dragstart'); 13348 13349 if (map.options.inertia) { 13350 this._positions = []; 13351 this._times = []; 13352 } 13353 }, 13354 13355 _onDrag: function (e) { 13356 if (this._map.options.inertia) { 13357 var time = this._lastTime = +new Date(), 13358 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 13359 13360 this._positions.push(pos); 13361 this._times.push(time); 13362 13363 this._prunePositions(time); 13364 } 13365 13366 this._map 13367 .fire('move', e) 13368 .fire('drag', e); 13369 }, 13370 13371 _prunePositions: function (time) { 13372 while (this._positions.length > 1 && time - this._times[0] > 50) { 13373 this._positions.shift(); 13374 this._times.shift(); 13375 } 13376 }, 13377 13378 _onZoomEnd: function () { 13379 var pxCenter = this._map.getSize().divideBy(2), 13380 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 13381 13382 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 13383 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 13384 }, 13385 13386 _viscousLimit: function (value, threshold) { 13387 return value - (value - threshold) * this._viscosity; 13388 }, 13389 13390 _onPreDragLimit: function () { 13391 if (!this._viscosity || !this._offsetLimit) { return; } 13392 13393 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 13394 13395 var limit = this._offsetLimit; 13396 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 13397 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 13398 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 13399 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 13400 13401 this._draggable._newPos = this._draggable._startPos.add(offset); 13402 }, 13403 13404 _onPreDragWrap: function () { 13405 // TODO refactor to be able to adjust map pane position after zoom 13406 var worldWidth = this._worldWidth, 13407 halfWidth = Math.round(worldWidth / 2), 13408 dx = this._initialWorldOffset, 13409 x = this._draggable._newPos.x, 13410 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 13411 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 13412 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 13413 13414 this._draggable._absPos = this._draggable._newPos.clone(); 13415 this._draggable._newPos.x = newX; 13416 }, 13417 13418 _onDragEnd: function (e) { 13419 var map = this._map, 13420 options = map.options, 13421 13422 noInertia = !options.inertia || this._times.length < 2; 13423 13424 map.fire('dragend', e); 13425 13426 if (noInertia) { 13427 map.fire('moveend'); 13428 13429 } else { 13430 this._prunePositions(+new Date()); 13431 13432 var direction = this._lastPos.subtract(this._positions[0]), 13433 duration = (this._lastTime - this._times[0]) / 1000, 13434 ease = options.easeLinearity, 13435 13436 speedVector = direction.multiplyBy(ease / duration), 13437 speed = speedVector.distanceTo([0, 0]), 13438 13439 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 13440 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 13441 13442 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 13443 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 13444 13445 if (!offset.x && !offset.y) { 13446 map.fire('moveend'); 13447 13448 } else { 13449 offset = map._limitOffset(offset, map.options.maxBounds); 13450 13451 requestAnimFrame(function () { 13452 map.panBy(offset, { 13453 duration: decelerationDuration, 13454 easeLinearity: ease, 13455 noMoveStart: true, 13456 animate: true 13457 }); 13458 }); 13459 } 13460 } 13461 } 13462}); 13463 13464// @section Handlers 13465// @property dragging: Handler 13466// Map dragging handler (by both mouse and touch). 13467Map.addInitHook('addHandler', 'dragging', Drag); 13468 13469/* 13470 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 13471 */ 13472 13473// @namespace Map 13474// @section Keyboard Navigation Options 13475Map.mergeOptions({ 13476 // @option keyboard: Boolean = true 13477 // Makes the map focusable and allows users to navigate the map with keyboard 13478 // arrows and `+`/`-` keys. 13479 keyboard: true, 13480 13481 // @option keyboardPanDelta: Number = 80 13482 // Amount of pixels to pan when pressing an arrow key. 13483 keyboardPanDelta: 80 13484}); 13485 13486var Keyboard = Handler.extend({ 13487 13488 keyCodes: { 13489 left: [37], 13490 right: [39], 13491 down: [40], 13492 up: [38], 13493 zoomIn: [187, 107, 61, 171], 13494 zoomOut: [189, 109, 54, 173] 13495 }, 13496 13497 initialize: function (map) { 13498 this._map = map; 13499 13500 this._setPanDelta(map.options.keyboardPanDelta); 13501 this._setZoomDelta(map.options.zoomDelta); 13502 }, 13503 13504 addHooks: function () { 13505 var container = this._map._container; 13506 13507 // make the container focusable by tabbing 13508 if (container.tabIndex <= 0) { 13509 container.tabIndex = '0'; 13510 } 13511 13512 on(container, { 13513 focus: this._onFocus, 13514 blur: this._onBlur, 13515 mousedown: this._onMouseDown 13516 }, this); 13517 13518 this._map.on({ 13519 focus: this._addHooks, 13520 blur: this._removeHooks 13521 }, this); 13522 }, 13523 13524 removeHooks: function () { 13525 this._removeHooks(); 13526 13527 off(this._map._container, { 13528 focus: this._onFocus, 13529 blur: this._onBlur, 13530 mousedown: this._onMouseDown 13531 }, this); 13532 13533 this._map.off({ 13534 focus: this._addHooks, 13535 blur: this._removeHooks 13536 }, this); 13537 }, 13538 13539 _onMouseDown: function () { 13540 if (this._focused) { return; } 13541 13542 var body = document.body, 13543 docEl = document.documentElement, 13544 top = body.scrollTop || docEl.scrollTop, 13545 left = body.scrollLeft || docEl.scrollLeft; 13546 13547 this._map._container.focus(); 13548 13549 window.scrollTo(left, top); 13550 }, 13551 13552 _onFocus: function () { 13553 this._focused = true; 13554 this._map.fire('focus'); 13555 }, 13556 13557 _onBlur: function () { 13558 this._focused = false; 13559 this._map.fire('blur'); 13560 }, 13561 13562 _setPanDelta: function (panDelta) { 13563 var keys = this._panKeys = {}, 13564 codes = this.keyCodes, 13565 i, len; 13566 13567 for (i = 0, len = codes.left.length; i < len; i++) { 13568 keys[codes.left[i]] = [-1 * panDelta, 0]; 13569 } 13570 for (i = 0, len = codes.right.length; i < len; i++) { 13571 keys[codes.right[i]] = [panDelta, 0]; 13572 } 13573 for (i = 0, len = codes.down.length; i < len; i++) { 13574 keys[codes.down[i]] = [0, panDelta]; 13575 } 13576 for (i = 0, len = codes.up.length; i < len; i++) { 13577 keys[codes.up[i]] = [0, -1 * panDelta]; 13578 } 13579 }, 13580 13581 _setZoomDelta: function (zoomDelta) { 13582 var keys = this._zoomKeys = {}, 13583 codes = this.keyCodes, 13584 i, len; 13585 13586 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 13587 keys[codes.zoomIn[i]] = zoomDelta; 13588 } 13589 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 13590 keys[codes.zoomOut[i]] = -zoomDelta; 13591 } 13592 }, 13593 13594 _addHooks: function () { 13595 on(document, 'keydown', this._onKeyDown, this); 13596 }, 13597 13598 _removeHooks: function () { 13599 off(document, 'keydown', this._onKeyDown, this); 13600 }, 13601 13602 _onKeyDown: function (e) { 13603 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 13604 13605 var key = e.keyCode, 13606 map = this._map, 13607 offset; 13608 13609 if (key in this._panKeys) { 13610 if (!map._panAnim || !map._panAnim._inProgress) { 13611 offset = this._panKeys[key]; 13612 if (e.shiftKey) { 13613 offset = toPoint(offset).multiplyBy(3); 13614 } 13615 13616 map.panBy(offset); 13617 13618 if (map.options.maxBounds) { 13619 map.panInsideBounds(map.options.maxBounds); 13620 } 13621 } 13622 } else if (key in this._zoomKeys) { 13623 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 13624 13625 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) { 13626 map.closePopup(); 13627 13628 } else { 13629 return; 13630 } 13631 13632 stop(e); 13633 } 13634}); 13635 13636// @section Handlers 13637// @section Handlers 13638// @property keyboard: Handler 13639// Keyboard navigation handler.
13640Map.addInitHook('addHandler', 'keyboard', Keyboard); 13641 13642/* 13643 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 13644 */ 13645 13646// @namespace Map 13647// @section Interaction Options 13648Map.mergeOptions({ 13649 // @section Mousewheel options 13650 // @option scrollWheelZoom: Boolean|String = true 13651 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 13652 // it will zoom to the center of the view regardless of where the mouse was. 13653 scrollWheelZoom: true, 13654 13655 // @option wheelDebounceTime: Number = 40 13656 // Limits the rate at which a wheel can fire (in milliseconds). By default 13657 // user can't zoom via wheel more often than once per 40 ms. 13658 wheelDebounceTime: 40, 13659 13660 // @option wheelPxPerZoomLevel: Number = 60 13661 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 13662 // mean a change of one full zoom level. Smaller values will make wheel-zooming 13663 // faster (and vice versa). 13664 wheelPxPerZoomLevel: 60 13665}); 13666 13667var ScrollWheelZoom = Handler.extend({ 13668 addHooks: function () { 13669 on(this._map._container, 'mousewheel', this._onWheelScroll, this); 13670 13671 this._delta = 0; 13672 }, 13673 13674 removeHooks: function () { 13675 off(this._map._container, 'mousewheel', this._onWheelScroll, this); 13676 }, 13677 13678 _onWheelScroll: function (e) { 13679 var delta = getWheelDelta(e); 13680 13681 var debounce = this._map.options.wheelDebounceTime; 13682 13683 this._delta += delta; 13684 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 13685 13686 if (!this._startTime) { 13687 this._startTime = +new Date(); 13688 } 13689 13690 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 13691 13692 clearTimeout(this._timer); 13693 this._timer = setTimeout(bind(this._performZoom, this), left); 13694 13695 stop(e); 13696 }, 13697 13698 _performZoom: function () { 13699 var map = this._map, 13700 zoom = map.getZoom(), 13701 snap = this._map.options.zoomSnap || 0; 13702 13703 map._stop(); // stop panning and fly animations if any 13704 13705 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 13706 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 13707 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 13708 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 13709 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 13710 13711 this._delta = 0; 13712 this._startTime = null; 13713 13714 if (!delta) { return; } 13715 13716 if (map.options.scrollWheelZoom === 'center') { 13717 map.setZoom(zoom + delta); 13718 } else { 13719 map.setZoomAround(this._lastMousePos, zoom + delta); 13720 } 13721 } 13722}); 13723 13724// @section Handlers 13725// @property scrollWheelZoom: Handler 13726// Scroll wheel zoom handler. 13727Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); 13728 13729/* 13730 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. 13731 */ 13732 13733// @namespace Map 13734// @section Interaction Options 13735Map.mergeOptions({ 13736 // @section Touch interaction options 13737 // @option tap: Boolean = true 13738 // Enables mobile hacks for supporting instant taps (fixing 200ms click 13739 // delay on iOS/Android) and touch holds (fired as `contextmenu` events). 13740 tap: true, 13741 13742 // @option tapTolerance: Number = 15 13743 // The max number of pixels a user can shift his finger during touch 13744 // for it to be considered a valid tap. 13745 tapTolerance: 15 13746}); 13747 13748var Tap = Handler.extend({ 13749 addHooks: function () { 13750 on(this._map._container, 'touchstart', this._onDown, this); 13751 }, 13752 13753 removeHooks: function () { 13754 off(this._map._container, 'touchstart', this._onDown, this); 13755 }, 13756 13757 _onDown: function (e) { 13758 if (!e.touches) { return; } 13759 13760 preventDefault(e); 13761 13762 this._fireClick = true; 13763 13764 // don't simulate click or track longpress if more than 1 touch 13765 if (e.touches.length > 1) { 13766 this._fireClick = false; 13767 clearTimeout(this._holdTimeout); 13768 return; 13769 } 13770 13771 var first = e.touches[0], 13772 el = first.target; 13773 13774 this._startPos = this._newPos = new Point(first.clientX, first.clientY); 13775 13776 // if touching a link, highlight it 13777 if (el.tagName && el.tagName.toLowerCase() === 'a') { 13778 addClass(el, 'leaflet-active'); 13779 } 13780 13781 // simulate long hold but setting a timeout 13782 this._holdTimeout = setTimeout(bind(function () { 13783 if (this._isTapValid()) { 13784 this._fireClick = false; 13785 this._onUp(); 13786 this._simulateEvent('contextmenu', first); 13787 } 13788 }, this), 1000); 13789 13790 this._simulateEvent('mousedown', first); 13791 13792 on(document, { 13793 touchmove: this._onMove, 13794 touchend: this._onUp 13795 }, this); 13796 }, 13797 13798 _onUp: function (e) { 13799 clearTimeout(this._holdTimeout); 13800 13801 off(document, { 13802 touchmove: this._onMove, 13803 touchend: this._onUp 13804 }, this); 13805 13806 if (this._fireClick && e && e.changedTouches) { 13807 13808 var first = e.changedTouches[0], 13809 el = first.target; 13810 13811 if (el && el.tagName && el.tagName.toLowerCase() === 'a') { 13812 removeClass(el, 'leaflet-active'); 13813 } 13814 13815 this._simulateEvent('mouseup', first); 13816 13817 // simulate click if the touch didn't move too much 13818 if (this._isTapValid()) { 13819 this._simulateEvent('click', first); 13820 } 13821 } 13822 }, 13823 13824 _isTapValid: function () { 13825 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 13826 }, 13827 13828 _onMove: function (e) { 13829 var first = e.touches[0]; 13830 this._newPos = new Point(first.clientX, first.clientY); 13831 this._simulateEvent('mousemove', first); 13832 }, 13833 13834 _simulateEvent: function (type, e) { 13835 var simulatedEvent = document.createEvent('MouseEvents'); 13836 13837 simulatedEvent._simulated = true; 13838 e.target._simulatedClick = true; 13839 13840 simulatedEvent.initMouseEvent( 13841 type, true, true, window, 1, 13842 e.screenX, e.screenY, 13843 e.clientX, e.clientY, 13844 false, false, false, false, 0, null); 13845 13846 e.target.dispatchEvent(simulatedEvent); 13847 } 13848}); 13849 13850// @section Handlers 13851// @property tap: Handler 13852// Mobile touch hacks (quick tap and touch hold) handler. 13853if (touch && !pointer) { 13854 Map.addInitHook('addHandler', 'tap', Tap); 13855} 13856 13857/* 13858 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supp
vendor: 4,276 bytes, lines 13858-13998
13858orted mobile browsers. 13859 */ 13860 13861// @namespace Map 13862// @section Interaction Options 13863Map.mergeOptions({ 13864 // @section Touch interaction options 13865 // @option touchZoom: Boolean|String = * 13866 // Whether the map can be zoomed by touch-dragging with two fingers. If 13867 // passed `'center'`, it will zoom to the center of the view regardless of 13868 // where the touch events (fingers) were. Enabled for touch-capable web 13869 // browsers except for old Androids. 13870 touchZoom: touch && !android23, 13871 13872 // @option bounceAtZoomLimits: Boolean = true 13873 // Set it to false if you don't want the map to zoom beyond min/max zoom 13874 // and then bounce back when pinch-zooming. 13875 bounceAtZoomLimits: true 13876}); 13877 13878var TouchZoom = Handler.extend({ 13879 addHooks: function () { 13880 addClass(this._map._container, 'leaflet-touch-zoom'); 13881 on(this._map._container, 'touchstart', this._onTouchStart, this); 13882 }, 13883 13884 removeHooks: function () { 13885 removeClass(this._map._container, 'leaflet-touch-zoom'); 13886 off(this._map._container, 'touchstart', this._onTouchStart, this); 13887 }, 13888 13889 _onTouchStart: function (e) { 13890 var map = this._map; 13891 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 13892 13893 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 13894 p2 = map.mouseEventToContainerPoint(e.touches[1]); 13895 13896 this._centerPoint = map.getSize()._divideBy(2); 13897 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 13898 if (map.options.touchZoom !== 'center') { 13899 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 13900 } 13901 13902 this._startDist = p1.distanceTo(p2); 13903 this._startZoom = map.getZoom(); 13904 13905 this._moved = false; 13906 this._zooming = true; 13907 13908 map._stop(); 13909 13910 on(document, 'touchmove', this._onTouchMove, this); 13911 on(document, 'touchend', this._onTouchEnd, this); 13912 13913 preventDefault(e); 13914 }, 13915 13916 _onTouchMove: function (e) { 13917 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 13918 13919 var map = this._map, 13920 p1 = map.mouseEventToContainerPoint(e.touches[0]), 13921 p2 = map.mouseEventToContainerPoint(e.touches[1]), 13922 scale = p1.distanceTo(p2) / this._startDist; 13923 13924 this._zoom = map.getScaleZoom(scale, this._startZoom); 13925 13926 if (!map.options.bounceAtZoomLimits && ( 13927 (this._zoom < map.getMinZoom() && scale < 1) || 13928 (this._zoom > map.getMaxZoom() && scale > 1))) { 13929 this._zoom = map._limitZoom(this._zoom); 13930 } 13931 13932 if (map.options.touchZoom === 'center') { 13933 this._center = this._startLatLng; 13934 if (scale === 1) { return; } 13935 } else { 13936 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 13937 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 13938 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 13939 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 13940 } 13941 13942 if (!this._moved) { 13943 map._moveStart(true, false); 13944 this._moved = true; 13945 } 13946 13947 cancelAnimFrame(this._animRequest); 13948 13949 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); 13950 this._animRequest = requestAnimFrame(moveFn, this, true); 13951 13952 preventDefault(e); 13953 }, 13954 13955 _onTouchEnd: function () { 13956 if (!this._moved || !this._zooming) { 13957 this._zooming = false; 13958 return; 13959 } 13960 13961 this._zooming = false; 13962 cancelAnimFrame(this._animRequest); 13963 13964 off(document, 'touchmove', this._onTouchMove); 13965 off(document, 'touchend', this._onTouchEnd); 13966 13967 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 13968 if (this._map.options.zoomAnimation) { 13969 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 13970 } else { 13971 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 13972 } 13973 } 13974}); 13975 13976// @section Handlers 13977// @property touchZoom: Handler 13978// Touch zoom handler. 13979Map.addInitHook('addHandler', 'touchZoom', TouchZoom); 13980 13981Map.BoxZoom = BoxZoom; 13982Map.DoubleClickZoom = DoubleClickZoom; 13983Map.Drag = Drag; 13984Map.Keyboard = Keyboard; 13985Map.ScrollWheelZoom = ScrollWheelZoom; 13986Map.Tap = Tap; 13987Map.TouchZoom = TouchZoom; 13988 13989Object.freeze = freeze; 13990 13991exports.version = version; 13992exports.Control = Control; 13993exports.control = control; 13994exports.Browser = Browser; 13995exports.Evented = Evented; 13996exports.Mixin = Mixin; 13997exports.Util = Util; 13998exports.Class = Class;
13999exports.Handler = Handler; 14000exports.extend = extend; 14001exports.bind = bind; 14002exports.stamp = stamp; 14003exports.setOptions = setOptions; 14004exports.DomEvent = DomEvent; 14005exports.DomUtil = DomUtil; 14006exports.PosAnimation = PosAnimation; 14007exports.Draggable = Draggable; 14008exports.LineUtil = LineUtil; 14009exports.PolyUtil = PolyUtil; 14010exports.Point = Point; 14011exports.point = toPoint; 14012exports.Bounds = Bounds; 14013exports.bounds = toBounds; 14014exports.Transformation = Transformation; 14015exports.transformation = toTransformation; 14016exports.Projection = index; 14017exports.LatLng = LatLng; 14018exports.latLng = toLatLng; 14019exports.LatLngBounds = LatLngBounds; 14020exports.latLngBounds = toLatLngBounds; 14021exports.CRS = CRS; 14022exports.GeoJSON = GeoJSON; 14023exports.geoJSON = geoJSON; 14024exports.geoJson = geoJson; 14025exports.Layer = Layer; 14026exports.LayerGroup = LayerGroup; 14027exports.layerGroup = layerGroup; 14028exports.FeatureGroup = FeatureGroup; 14029exports.featureGroup = featureGroup; 14030exports.ImageOverlay = ImageOverlay; 14031exports.imageOverlay = imageOverlay; 14032exports.VideoOverlay = VideoOverlay; 14033exports.videoOverlay = videoOverlay; 14034exports.SVGOverlay = SVGOverlay; 14035exports.svgOverlay = svgOverlay; 14036exports.DivOverlay = DivOverlay; 14037exports.Popup = Popup; 14038exports.popup = popup; 14039exports.Tooltip = Tooltip; 14040exports.tooltip = tooltip; 14041exports.Icon = Icon; 14042exports.icon = icon; 14043exports.DivIcon = DivIcon; 14044exports.divIcon = divIcon; 14045exports.Marker = Marker; 14046exports.marker = marker; 14047exports.TileLayer = TileLayer; 14048exports.tileLayer = tileLayer; 14049exports.GridLayer = GridLayer; 14050exports.gridLayer = gridLayer; 14051exports.SVG = SVG; 14052exports.svg = svg$1; 14053exports.Renderer = Renderer; 14054exports.Canvas = Canvas; 14055exports.canvas = canvas$1; 14056exports.Path = Path; 14057exports.CircleMarker = CircleMarker; 14058exports.circleMarker = circleMarker; 14059exports.Circle = Circle; 14060exports.circle = circle; 14061exports.Polyline = Polyline; 14062exports.polyline = polyline; 14063exports.Polygon = Polygon; 14064exports.polygon = polygon; 14065exports.Rectangle = Rectangle; 14066exports.rectangle = rectangle; 14067exports.Map = Map; 14068exports.map = createMap; 14069 14070var oldL = window.L; 14071exports.noConflict = function() { 14072 window.L = oldL; 14073 return this; 14074} 14075 14076// Always export us to window global (see #2364) 14077window.L = exports; 14078 14079}))); 14080//# 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.