1/* @preserve 2 * Leaflet 1.3.4+Detached: 0e566b2ad5e696ba9f79a9d48a7e51c8f4892441.0e566b2, a JS library for interactive maps. http://leafletjs.com 3 * (c) 2010-2018 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.3.4+HEAD.0e566b2"; 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. 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,558 bytes, lines 1602-1878
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 SphericalMercator = { 1652 1653 R: 6378137, 1654 MAX_LATITUDE: 85.0511287798, 1655 1656 project: function (latlng) { 1657 var d = Math.PI / 180, 1658 max = this.MAX_LATITUDE, 1659 lat = Math.max(Math.min(max, latlng.lat), -max), 1660 sin = Math.sin(lat * d); 1661 1662 return new Point( 1663 this.R * latlng.lng * d, 1664 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 1665 }, 1666 1667 unproject: function (point) { 1668 var d = 180 / Math.PI; 1669 1670 return new LatLng( 1671 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 1672 point.x * d / this.R); 1673 }, 1674 1675 bounds: (function () { 1676 var d = 6378137 * Math.PI; 1677 return new Bounds([-d, -d], [d, d]); 1678 })() 1679}; 1680 1681/* 1682 * @class Transformation 1683 * @aka L.Transformation 1684 * 1685 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1686 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1687 * the reverse. Used by Leaflet in its projections code. 1688 * 1689 * @example 1690 * 1691 * ```js 1692 * var transformation = L.transformation(2, 5, -1, 10), 1693 * p = L.point(1, 2), 1694 * p2 = transformation.transform(p), // L.point(7, 8) 1695 * p3 = transformation.untransform(p2); // L.point(1, 2) 1696 * ``` 1697 */ 1698 1699 1700// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1701// Creates a `Transformation` object with the given coefficients. 1702function Transformation(a, b, c, d) { 1703 if (isArray(a)) { 1704 // use array properties 1705 this._a = a[0]; 1706 this._b = a[1]; 1707 this._c = a[2]; 1708 this._d = a[3]; 1709 return; 1710 } 1711 this._a = a; 1712 this._b = b; 1713 this._c = c; 1714 this._d = d; 1715} 1716 1717Transformation.prototype = { 1718 // @method transform(point: Point, scale?: Number): Point 1719 // Returns a transformed point, optionally multiplied by the given scale. 1720 // Only accepts actual `L.Point` instances, not arrays. 1721 transform: function (point, scale) { // (Point, Number) -> Point 1722 return this._transform(point.clone(), scale); 1723 }, 1724 1725 // destructive transform (faster) 1726 _transform: function (point, scale) { 1727 scale = scale || 1; 1728 point.x = scale * (this._a * point.x + this._b); 1729 point.y = scale * (this._c * point.y + this._d); 1730 return point; 1731 }, 1732 1733 // @method untransform(point: Point, scale?: Number): Point 1734 // Returns the reverse transformation of the given point, optionally divided 1735 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1736 untransform: function (point, scale) { 1737 scale = scale || 1; 1738 return new Point( 1739 (point.x / scale - this._b) / this._a, 1740 (point.y / scale - this._d) / this._c); 1741 } 1742}; 1743 1744// factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1745 1746// @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1747// Instantiates a Transformation object with the given coefficients. 1748 1749// @alternative 1750// @factory L.transformation(coefficients: Array): Transformation 1751// Expects an coefficients array of the form 1752// `[a: Number, b: Number, c: Number, d: Number]`. 1753 1754function toTransformation(a, b, c, d) { 1755 return new Transformation(a, b, c, d); 1756} 1757 1758/* 1759 * @namespace CRS 1760 * @crs L.CRS.EPSG3857 1761 * 1762 * The most common CRS for online maps, used by almost all free and commercial 1763 * tile providers. Uses Spherical Mercator projection. Set in by default in 1764 * Map's `crs` option. 1765 */ 1766 1767var EPSG3857 = extend({}, Earth, { 1768 code: 'EPSG:3857', 1769 projection: SphericalMercator, 1770 1771 transformation: (function () { 1772 var scale = 0.5 / (Math.PI * SphericalMercator.R); 1773 return toTransformation(scale, 0.5, -scale, 0.5); 1774 }()) 1775}); 1776 1777var EPSG900913 = extend({}, EPSG3857, { 1778 code: 'EPSG:900913' 1779}); 1780 1781// @namespace SVG; @section 1782// There are several static functions which can be called without instantiating L.SVG: 1783 1784// @function create(name: String): SVGElement 1785// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 1786// corresponding to the class name passed. For example, using 'line' will return 1787// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 1788function svgCreate(name) { 1789 return document.createElementNS('http://www.w3.org/2000/svg', name); 1790} 1791 1792// @function pointsToPath(rings: Point[], closed: Boolean): String 1793// Generates a SVG path string for multiple rings, with each ring turning 1794// into "M..L..L.." instructions 1795function pointsToPath(rings, closed) { 1796 var str = '', 1797 i, j, len, len2, points, p; 1798 1799 for (i = 0, len = rings.length; i < len; i++) { 1800 points = rings[i]; 1801 1802 for (j = 0, len2 = points.length; j < len2; j++) { 1803 p = points[j]; 1804 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 1805 } 1806 1807 // closes the ring for polygons; "x" is VML syntax 1808 str += closed ? (svg ? 'z' : 'x') : ''; 1809 } 1810 1811 // SVG complains about empty path strings 1812 return str || 'M0 0'; 1813} 1814 1815/* 1816 * @namespace Browser 1817 * @aka L.Browser 1818 * 1819 * A namespace with static properties for browser/feature detection used by Leaflet internally. 1820 * 1821 * @example 1822 * 1823 * ```js 1824 * if (L.Browser.ielt9) { 1825 * alert('Upgrade your browser, dude!'); 1826 * } 1827 * ``` 1828 */ 1829 1830var style$1 = document.documentElement.style; 1831 1832// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 1833var ie = 'ActiveXObject' in window; 1834 1835// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 1836var ielt9 = ie && !document.addEventListener; 1837 1838// @property edge: Boolean; `true` for the Edge web browser. 1839var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 1840 1841// @property webkit: Boolean; 1842// `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 1843var webkit = userAgentContains('webkit'); 1844 1845// @property android: Boolean 1846// `true` for any browser running on an Android platform. 1847var android = userAgentContains('android'); 1848 1849// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3. 1850var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 1851 1852/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 1853var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 1854// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome) 1855var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 1856 1857// @property opera: Boolean; `true` for the Opera browser 1858var opera = !!window.opera; 1859 1860// @property chrome: Boolean; `true` for the Chrome browser. 1861var chrome = userAgentContains('chrome'); 1862 1863// @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 1864var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 1865 1866// @property safari: Boolean; `true` for the Safari browser. 1867var safari = !chrome && userAgentContains('safari'); 1868 1869var phantom = userAgentContains('phantom'); 1870 1871// @property opera12: Boolean 1872// `true` for the Opera browser supporting CSS transforms (version 12 or later). 1873var opera12 = 'OTransition' in style$1; 1874 1875// @property win: Boolean; `true` when the browser is running in a Windows platform 1876var win = navigator.platform.indexOf('Win') === 0; 1877 1878// @property ie3d: Boolean;
1878 `true` for all Internet Explorer versions supporting CSS transforms. 1879var ie3d = ie && ('transition' in style$1); 1880 1881// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 1882var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 1883 1884// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 1885var gecko3d = 'MozPerspective' in style$1; 1886 1887// @property any3d: Boolean 1888// `true` for all browsers supporting CSS transforms. 1889var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1890 1891// @property mobile: Boolean; `true` for all browsers running in a mobile device. 1892var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 1893 1894// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 1895var mobileWebkit = mobile && webkit; 1896 1897// @property mobileWebkit3d: Boolean 1898// `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 1899var mobileWebkit3d = mobile && webkit3d; 1900 1901// @property msPointer: Boolean 1902// `true` for browsers implementing the Microsoft touch events model (notably IE10). 1903var msPointer = !window.PointerEvent && window.MSPointerEvent; 1904 1905// @property pointer: Boolean 1906// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 1907var pointer = !!(window.PointerEvent || msPointer); 1908 1909// @property touch: Boolean 1910// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 1911// This does not necessarily mean that the browser is running in a computer with 1912// a touchscreen, it only means that the browser is capable of understanding 1913// touch events. 1914var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || 1915 (window.DocumentTouch && document instanceof window.DocumentTouch)); 1916 1917// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 1918var mobileOpera = mobile && opera; 1919 1920// @property mobileGecko: Boolean 1921// `true` for gecko-based browsers running in a mobile device. 1922var mobileGecko = mobile && gecko; 1923 1924// @property retina: Boolean 1925// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 1926var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 1927 1928 1929// @property canvas: Boolean 1930// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 1931var canvas = (function () { 1932 return !!document.createElement('canvas').getContext; 1933}()); 1934 1935// @property svg: Boolean 1936// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 1937var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect); 1938 1939// @property vml: Boolean 1940// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 1941var vml = !svg && (function () { 1942 try { 1943 var div = document.createElement('div'); 1944 div.innerHTML = '<v:shape adj="1"/>'; 1945 1946 var shape = div.firstChild; 1947 shape.style.behavior = 'url(#default#VML)'; 1948 1949 return shape && (typeof shape.adj === 'object'); 1950 1951 } catch (e) { 1952 return false; 1953 } 1954}()); 1955 1956 1957function userAgentContains(str) { 1958 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 1959} 1960 1961 1962var Browser = (Object.freeze || Object)({ 1963 ie: ie, 1964 ielt9: ielt9, 1965 edge: edge, 1966 webkit: webkit, 1967 android: android, 1968 android23: android23, 1969 androidStock: androidStock, 1970 opera: opera, 1971 chrome: chrome, 1972 gecko: gecko, 1973 safari: safari, 1974 phantom: phantom, 1975 opera12: opera12, 1976 win: win, 1977 ie3d: ie3d, 1978 webkit3d: webkit3d, 1979 gecko3d: gecko3d, 1980 any3d: any3d, 1981 mobile: mobile, 1982 mobileWebkit: mobileWebkit, 1983 mobileWebkit3d: mobileWebkit3d, 1984 msPointer: msPointer, 1985 pointer: pointer, 1986 touch: touch, 1987 mobileOpera: mobileOpera, 1988 mobileGecko: mobileGecko, 1989 retina: retina, 1990 canvas: canvas, 1991 svg: svg, 1992 vml: vml 1993}); 1994 1995/* 1996 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 1997 */ 1998 1999 2000var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2001var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'; 2002var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'; 2003var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'; 2004var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION']; 2005 2006var _pointers = {}; 2007var _pointerDocListener = false; 2008 2009// DomEvent.DoubleTap needs to know about this 2010var _pointersCount = 0; 2011 2012// Provides a touch events wrapper for (ms)pointer events. 2013// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2014 2015function addPointerListener(obj, type, handler, id) { 2016 if (type === 'touchstart') { 2017 _addPointerStart(obj, handler, id); 2018 2019 } else if (type === 'touchmove') { 2020 _addPointerMove(obj, handler, id); 2021 2022 } else if (type === 'touchend') { 2023 _addPointerEnd(obj, handler, id); 2024 } 2025 2026 return this; 2027} 2028 2029function removePointerListener(obj, type, id) { 2030 var handler = obj['_leaflet_' + type + id]; 2031 2032 if (type === 'touchstart') { 2033 obj.removeEventListener(POINTER_DOWN, handler, false); 2034 2035 } else if (type === 'touchmove') { 2036 obj.removeEventListener(POINTER_MOVE, handler, false); 2037 2038 } else if (type === 'touchend') { 2039 obj.removeEventListener(POINTER_UP, handler, false); 2040 obj.removeEventListener(POINTER_CANCEL, handler, false); 2041 } 2042 2043 return this; 2044} 2045 2046function _addPointerStart(obj, handler, id) { 2047 var onDown = bind(function (e) { 2048 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { 2049 // In IE11, some touch events needs to fire for form controls, or 2050 // the controls will stop working. We keep a whitelist of tag names that 2051 // need these events. For other target tags, we prevent default on the event. 2052 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { 2053 preventDefault(e); 2054 } else { 2055 return; 2056 } 2057 } 2058 2059 _handlePointer(e, handler); 2060 }); 2061 2062 obj['_leaflet_touchstart' + id] = onDown; 2063 obj.addEventListener(POINTER_DOWN, onDown, false); 2064 2065 // need to keep track of what pointers and how many are active to provide e.touches emulation 2066 if (!_pointerDocListener) { 2067 // we listen documentElement as any drags that end by moving the touch off the screen get fired there 2068 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2069 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2070 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true); 2071 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2072 2073 _pointerDocListener = true; 2074 } 2075} 2076 2077function _globalPointerDown(e) { 2078 _pointers[e.pointerId] = e; 2079 _pointersCount++; 2080} 2081 2082function _globalPointerMove(e) { 2083 if (_pointers[e.pointerId]) { 2084 _pointers[e.pointerId] = e; 2085 } 2086} 2087 2088function _globalPointerUp(e) { 2089 delete _pointers[e.pointerId]; 2090 _pointersCount--; 2091} 2092 2093function _handlePointer(e, handler) { 2094 e.touches = []; 2095 for (var i in _pointers) { 2096 e.touches.push(_pointers[i]); 2097 } 2098 e.changedTouches = [e]; 2099 2100 handler(e); 2101} 2102 2103function _addPointerMove(obj, handler, id) { 2104 var onMove = function (e) { 2105 // don't fire touch moves when mouse isn't down 2106 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } 2107 2108 _handlePointer(e, handler); 2109 }; 2110 2111 obj['_leaflet_touchmove' + id] = onMove; 2112 obj.addEventListener(POINTER_MOVE, onMove, false); 2113} 2114 2115function _addPointerEnd(obj, handler, id) { 2116 var onUp = function (e) { 2117 _handlePointer(e, handler); 2118 }; 2119 2120 obj['_leaflet_touchend' + id] = onUp; 2121 obj.addEventListener(POINTER_UP, onUp, false); 2122 obj.addEventListener(POINTER_CANCEL, onUp, false); 2123} 2124 2125/* 2126 * Extends the event handling code with double tap support for mobile browsers. 2127 */ 2128 2129var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart'; 2130var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend'; 2131var _pre = '_leaflet_'; 2132 2133// inspired by Zepto touch code by Thomas Fuchs 2134function addDoubleTapListener(obj, handler, id) { 2135 var last, touch$$1, 2136 doubleTap = false, 2137 delay = 250; 2138 2139 function onTouchStart(e) { 2140 var count; 2141 2142 if (pointer) { 2143 if ((!edge) || e.pointerType === 'mouse') { return; } 2144 count = _pointersCount; 2145 } else {
2146 count = e.touches.length; 2147 } 2148 2149 if (count > 1) { return; } 2150 2151 var now = Date.now(), 2152 delta = now - (last || now); 2153 2154 touch$$1 = e.touches ? e.touches[0] : e; 2155 doubleTap = (delta > 0 && delta <= delay); 2156 last = now; 2157 } 2158 2159 function onTouchEnd(e) { 2160 if (doubleTap && !touch$$1.cancelBubble) { 2161 if (pointer) { 2162 if ((!edge) || e.pointerType === 'mouse') { return; } 2163 // work around .type being readonly with MSPointer* events 2164 var newTouch = {}, 2165 prop, i; 2166 2167 for (i in touch$$1) { 2168 prop = touch$$1[i]; 2169 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop; 2170 } 2171 touch$$1 = newTouch; 2172 } 2173 touch$$1.type = 'dblclick'; 2174 handler(touch$$1); 2175 last = null; 2176 } 2177 } 2178 2179 obj[_pre + _touchstart + id] = onTouchStart; 2180 obj[_pre + _touchend + id] = onTouchEnd; 2181 obj[_pre + 'dblclick' + id] = handler; 2182 2183 obj.addEventListener(_touchstart, onTouchStart, false); 2184 obj.addEventListener(_touchend, onTouchEnd, false); 2185 2186 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), 2187 // the browser doesn't fire touchend/pointerup events but does fire 2188 // native dblclicks. See #4127. 2189 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. 2190 obj.addEventListener('dblclick', handler, false); 2191 2192 return this; 2193} 2194 2195function removeDoubleTapListener(obj, id) { 2196 var touchstart = obj[_pre + _touchstart + id], 2197 touchend = obj[_pre + _touchend + id], 2198 dblclick = obj[_pre + 'dblclick' + id]; 2199 2200 obj.removeEventListener(_touchstart, touchstart, false); 2201 obj.removeEventListener(_touchend, touchend, false); 2202 if (!edge) { 2203 obj.removeEventListener('dblclick', dblclick, false); 2204 } 2205 2206 return this; 2207} 2208 2209/* 2210 * @namespace DomUtil 2211 * 2212 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2213 * tree, used by Leaflet internally. 2214 * 2215 * Most functions expecting or returning a `HTMLElement` also work for 2216 * SVG elements. The only difference is that classes refer to CSS classes 2217 * in HTML and SVG classes in SVG. 2218 */ 2219 2220 2221// @property TRANSFORM: String 2222// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2223var TRANSFORM = testProp( 2224 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2225 2226// webkitTransition comes first because some browser versions that drop vendor prefix don't do 2227// the same for the transitionend event, in particular the Android 4.1 stock browser 2228 2229// @property TRANSITION: String 2230// Vendor-prefixed transition style name. 2231var TRANSITION = testProp( 2232 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2233 2234// @property TRANSITION_END: String 2235// Vendor-prefixed transitionend event name. 2236var TRANSITION_END = 2237 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2238 2239 2240// @function get(id: String|HTMLElement): HTMLElement 2241// Returns an element given its DOM id, or returns the element itself 2242// if it was passed directly. 2243function get(id) { 2244 return typeof id === 'string' ? document.getElementById(id) : id; 2245} 2246 2247// @function getStyle(el: HTMLElement, styleAttrib: String): String 2248// Returns the value for a certain style attribute on an element, 2249// including computed values or values set through CSS. 2250function getStyle(el, style) { 2251 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2252 2253 if ((!value || value === 'auto') && document.defaultView) { 2254 var css = document.defaultView.getComputedStyle(el, null); 2255 value = css ? css[style] : null; 2256 } 2257 return value === 'auto' ? null : value; 2258} 2259 2260// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2261// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2262function create$1(tagName, className, container) { 2263 var el = document.createElement(tagName); 2264 el.className = className || ''; 2265 2266 if (container) { 2267 container.appendChild(el); 2268 } 2269 return el; 2270} 2271 2272// @function remove(el: HTMLElement) 2273// Removes `el` from its parent element 2274function remove(el) { 2275 var parent = el.parentNode; 2276 if (parent) { 2277 parent.removeChild(el); 2278 } 2279} 2280 2281// @function empty(el: HTMLElement) 2282// Removes all of `el`'s children elements from `el` 2283function empty(el) { 2284 while (el.firstChild) { 2285 el.removeChild(el.firstChild); 2286 } 2287} 2288 2289// @function toFront(el: HTMLElement)
vendor: 9,278 bytes, lines 2290-2586
2290// Makes `el` the last child of its parent, so it renders in front of the other children. 2291function toFront(el) { 2292 var parent = el.parentNode; 2293 if (parent.lastChild !== el) { 2294 parent.appendChild(el); 2295 } 2296} 2297 2298// @function toBack(el: HTMLElement) 2299// Makes `el` the first child of its parent, so it renders behind the other children. 2300function toBack(el) { 2301 var parent = el.parentNode; 2302 if (parent.firstChild !== el) { 2303 parent.insertBefore(el, parent.firstChild); 2304 } 2305} 2306 2307// @function hasClass(el: HTMLElement, name: String): Boolean 2308// Returns `true` if the element's class attribute contains `name`. 2309function hasClass(el, name) { 2310 if (el.classList !== undefined) { 2311 return el.classList.contains(name); 2312 } 2313 var className = getClass(el); 2314 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2315} 2316 2317// @function addClass(el: HTMLElement, name: String) 2318// Adds `name` to the element's class attribute. 2319function addClass(el, name) { 2320 if (el.classList !== undefined) { 2321 var classes = splitWords(name); 2322 for (var i = 0, len = classes.length; i < len; i++) { 2323 el.classList.add(classes[i]); 2324 } 2325 } else if (!hasClass(el, name)) { 2326 var className = getClass(el); 2327 setClass(el, (className ? className + ' ' : '') + name); 2328 } 2329} 2330 2331// @function removeClass(el: HTMLElement, name: String) 2332// Removes `name` from the element's class attribute. 2333function removeClass(el, name) { 2334 if (el.classList !== undefined) { 2335 el.classList.remove(name); 2336 } else { 2337 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2338 } 2339} 2340 2341// @function setClass(el: HTMLElement, name: String) 2342// Sets the element's class. 2343function setClass(el, name) { 2344 if (el.className.baseVal === undefined) { 2345 el.className = name; 2346 } else { 2347 // in case of SVG element 2348 el.className.baseVal = name; 2349 } 2350} 2351 2352// @function getClass(el: HTMLElement): String 2353// Returns the element's class. 2354function getClass(el) { 2355 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2356} 2357 2358// @function setOpacity(el: HTMLElement, opacity: Number) 2359// Set the opacity of an element (including old IE support). 2360// `opacity` must be a number from `0` to `1`. 2361function setOpacity(el, value) { 2362 if ('opacity' in el.style) { 2363 el.style.opacity = value; 2364 } else if ('filter' in el.style) { 2365 _setOpacityIE(el, value); 2366 } 2367} 2368 2369function _setOpacityIE(el, value) { 2370 var filter = false, 2371 filterName = 'DXImageTransform.Microsoft.Alpha'; 2372 2373 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2374 try { 2375 filter = el.filters.item(filterName); 2376 } catch (e) { 2377 // don't set opacity to 1 if we haven't already set an opacity, 2378 // it isn't needed and breaks transparent pngs. 2379 if (value === 1) { return; } 2380 } 2381 2382 value = Math.round(value * 100); 2383 2384 if (filter) { 2385 filter.Enabled = (value !== 100); 2386 filter.Opacity = value; 2387 } else { 2388 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2389 } 2390} 2391 2392// @function testProp(props: String[]): String|false 2393// Goes through the array of style names and returns the first name 2394// that is a valid style name for an element. If no such name is found, 2395// it returns false. Useful for vendor-prefixed styles like `transform`. 2396function testProp(props) { 2397 var style = document.documentElement.style; 2398 2399 for (var i = 0; i < props.length; i++) { 2400 if (props[i] in style) { 2401 return props[i]; 2402 } 2403 } 2404 return false; 2405} 2406 2407// @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2408// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2409// and optionally scaled by `scale`. Does not have an effect if the 2410// browser doesn't support 3D CSS transforms. 2411function setTransform(el, offset, scale) { 2412 var pos = offset || new Point(0, 0); 2413 2414 el.style[TRANSFORM] = 2415 (ie3d ? 2416 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2417 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2418 (scale ? ' scale(' + scale + ')' : ''); 2419} 2420 2421// @function setPosition(el: HTMLElement, position: Point) 2422// Sets the position of `el` to coordinates specified by `position`, 2423// using CSS translate or top/left positioning depending on the browser 2424// (used by Leaflet internally to position its layers). 2425function setPosition(el, point) { 2426 2427 /*eslint-disable */ 2428 el._leaflet_pos = point; 2429 /* eslint-enable */ 2430 2431 if (any3d) { 2432 setTransform(el, point); 2433 } else { 2434 el.style.left = point.x + 'px'; 2435 el.style.top = point.y + 'px'; 2436 } 2437} 2438 2439// @function getPosition(el: HTMLElement): Point 2440// Returns the coordinates of an element previously positioned with setPosition. 2441function getPosition(el) { 2442 // this method is only used for elements previously positioned using setPosition, 2443 // so it's safe to cache the position for performance 2444 2445 return el._leaflet_pos || new Point(0, 0); 2446} 2447 2448// @function disableTextSelection() 2449// Prevents the user from generating `selectstart` DOM events, usually generated 2450// when the user drags the mouse through a page with text. Used internally 2451// by Leaflet to override the behaviour of any click-and-drag interaction on 2452// the map. Affects drag interactions on the whole document. 2453 2454// @function enableTextSelection() 2455// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2456var disableTextSelection; 2457var enableTextSelection; 2458var _userSelect; 2459if ('onselectstart' in document) { 2460 disableTextSelection = function () { 2461 on(window, 'selectstart', preventDefault); 2462 }; 2463 enableTextSelection = function () { 2464 off(window, 'selectstart', preventDefault); 2465 }; 2466} else { 2467 var userSelectProperty = testProp( 2468 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2469 2470 disableTextSelection = function () { 2471 if (userSelectProperty) { 2472 var style = document.documentElement.style; 2473 _userSelect = style[userSelectProperty]; 2474 style[userSelectProperty] = 'none'; 2475 } 2476 }; 2477 enableTextSelection = function () { 2478 if (userSelectProperty) { 2479 document.documentElement.style[userSelectProperty] = _userSelect; 2480 _userSelect = undefined; 2481 } 2482 }; 2483} 2484 2485// @function disableImageDrag() 2486// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2487// for `dragstart` DOM events, usually generated when the user drags an image. 2488function disableImageDrag() { 2489 on(window, 'dragstart', preventDefault); 2490} 2491 2492// @function enableImageDrag() 2493// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2494function enableImageDrag() { 2495 off(window, 'dragstart', preventDefault); 2496} 2497 2498var _outlineElement; 2499var _outlineStyle; 2500// @function preventOutline(el: HTMLElement) 2501// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2502// of the element `el` invisible. Used internally by Leaflet to prevent 2503// focusable elements from displaying an outline when the user performs a 2504// drag interaction on them. 2505function preventOutline(element) { 2506 while (element.tabIndex === -1) { 2507 element = element.parentNode; 2508 } 2509 if (!element.style) { return; } 2510 restoreOutline(); 2511 _outlineElement = element; 2512 _outlineStyle = element.style.outline; 2513 element.style.outline = 'none'; 2514 on(window, 'keydown', restoreOutline); 2515} 2516 2517// @function restoreOutline() 2518// Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2519function restoreOutline() { 2520 if (!_outlineElement) { return; } 2521 _outlineElement.style.outline = _outlineStyle; 2522 _outlineElement = undefined; 2523 _outlineStyle = undefined; 2524 off(window, 'keydown', restoreOutline); 2525} 2526 2527// @function getSizedParentNode(el: HTMLElement): HTMLElement 2528// Finds the closest parent node which size (width and height) is not null. 2529function getSizedParentNode(element) { 2530 do { 2531 element = element.parentNode; 2532 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2533 return element; 2534} 2535 2536// @function getScale(el: HTMLElement): Object 2537// Computes the CSS scale currently applied on the element. 2538// Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2539// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2540function getScale(element) { 2541 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2542 2543 return { 2544 x: rect.width / element.offsetWidth || 1, 2545 y: rect.height / element.offsetHeight || 1, 2546 boundingClientRect: rect 2547 }; 2548} 2549 2550 2551var DomUtil = (Object.freeze || Object)({ 2552 TRANSFORM: TRANSFORM, 2553 TRANSITION: TRANSITION, 2554 TRANSITION_END: TRANSITION_END, 2555 get: get, 2556 getStyle: getStyle, 2557 create: create$1, 2558 remove: remove, 2559 empty: empty, 2560 toFront: toFront, 2561 toBack: toBack, 2562 hasClass: hasClass, 2563 addClass: addClass, 2564 removeClass: removeClass, 2565 setClass: setClass, 2566 getClass: getClass, 2567 setOpacity: setOpacity, 2568 testProp: testProp, 2569 setTransform: setTransform, 2570 setPosition: setPosition, 2571 getPosition: getPosition, 2572 disableTextSelection: disableTextSelection, 2573 enableTextSelection: enableTextSelection, 2574 disableImageDrag: disableImageDrag, 2575 enableImageDrag: enableImageDrag, 2576 preventOutline: preventOutline, 2577 restoreOutline: restoreOutline, 2578 getSizedParentNode: getSizedParentNode, 2579 getScale: getScale 2580}); 2581 2582/* 2583 * @namespace DomEvent 2584 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2585 */ 2586
2587// Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2588 2589// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2590// Adds a listener function (`fn`) to a particular DOM event type of the 2591// element `el`. You can optionally specify the context of the listener 2592// (object the `this` keyword will point to). You can also pass several 2593// space-separated types (e.g. `'click dblclick'`). 2594 2595// @alternative 2596// @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2597// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2598function on(obj, types, fn, context) { 2599 2600 if (typeof types === 'object') { 2601 for (var type in types) { 2602 addOne(obj, type, types[type], fn); 2603 } 2604 } else { 2605 types = splitWords(types); 2606 2607 for (var i = 0, len = types.length; i < len; i++) { 2608 addOne(obj, types[i], fn, context); 2609 } 2610 } 2611 2612 return this; 2613} 2614 2615var eventsKey = '_leaflet_events'; 2616 2617// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2618// Removes a previously added listener function. 2619// Note that if you passed a custom context to on, you must pass the same 2620// context to `off` in order to remove the listener. 2621 2622// @alternative 2623// @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2624// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2625function off(obj, types, fn, context) { 2626 2627 if (typeof types === 'object') { 2628 for (var type in types) { 2629 removeOne(obj, type, types[type], fn); 2630 } 2631 } else if (types) { 2632 types = splitWords(types); 2633 2634 for (var i = 0, len = types.length; i < len; i++) { 2635 removeOne(obj, types[i], fn, context); 2636 } 2637 } else { 2638 for (var j in obj[eventsKey]) { 2639 removeOne(obj, j, obj[eventsKey][j]); 2640 } 2641 delete obj[eventsKey]; 2642 } 2643 2644 return this; 2645} 2646 2647function addOne(obj, type, fn, context) { 2648 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2649 2650 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 2651 2652 var handler = function (e) { 2653 return fn.call(context || obj, e || window.event); 2654 }; 2655 2656 var originalHandler = handler; 2657 2658 if (pointer && type.indexOf('touch') === 0) { 2659 // Needs DomEvent.Pointer.js 2660 addPointerListener(obj, type, handler, id); 2661 2662 } else if (touch && (type === 'dblclick') && addDoubleTapListener && 2663 !(pointer && chrome)) { 2664 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener 2665 // See #5180 2666 addDoubleTapListener(obj, handler, id); 2667 2668 } else if ('addEventListener' in obj) { 2669 2670 if (type === 'mousewheel') { 2671 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); 2672 2673 } else if ((type === 'mouseenter') || (type === 'mouseleave')) { 2674 handler = function (e) { 2675 e = e || window.event; 2676 if (isExternalTarget(obj, e)) { 2677 originalHandler(e); 2678 } 2679 }; 2680 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); 2681 2682 } else { 2683 if (type === 'click' && android) { 2684 handler = function (e) { 2685 filterClick(e, originalHandler); 2686 }; 2687 } 2688 obj.addEventListener(type, handler, false); 2689 } 2690 2691 } else if ('attachEvent' in obj) { 2692 obj.attachEvent('on' + type, handler); 2693 } 2694 2695 obj[eventsKey] = obj[eventsKey] || {}; 2696 obj[eventsKey][id] = handler; 2697} 2698 2699function removeOne(obj, type, fn, context) { 2700 2701 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''), 2702 handler = obj[eventsKey] && obj[eventsKey][id]; 2703 2704 if (!handler) { return this; } 2705 2706 if (pointer && type.indexOf('touch') === 0) { 2707 removePointerListener(obj, type, id); 2708 2709 } else if (touch && (type === 'dblclick') && removeDoubleTapListener && 2710 !(pointer && chrome)) { 2711 removeDoubleTapListener(obj, id); 2712 2713 } else if ('removeEventListener' in obj) { 2714 2715 if (type === 'mousewheel') { 2716 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); 2717 2718 } else { 2719 obj.removeEventListener( 2720 type === 'mouseenter' ? 'mouseover' : 2721 type === 'mouseleave' ? 'mouseout' : type, handler, false); 2722 } 2723 2724 } else if ('detachEvent' in obj) { 2725 obj.detachEvent('on' + type, handler); 2726 } 2727 2728 obj[eventsKey][id] = null; 2729} 2730 2731// @function stopPropagation(ev: DOMEvent): this 2732// Stop the given event from propagation to parent elements. Used inside the listener functions: 2733// ```js 2734// L.DomEvent.on(div, 'click', function (ev) { 2735// L.DomEvent.stopPropagation(ev); 2736// }); 2737// ``` 2738function stopPropagation(e) { 2739 2740 if (e.stopPropagation) { 2741 e.stopPropagation(); 2742 } else if (e.originalEvent) { // In case of Leaflet event. 2743 e.originalEvent._stopped = true; 2744 } else { 2745 e.cancelBubble = true; 2746 } 2747 skipped(e); 2748 2749 return this; 2750} 2751 2752// @function disableScrollPropagation(el: HTMLElement): this 2753// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). 2754function disableScrollPropagation(el) { 2755 addOne(el, 'mousewheel', stopPropagation); 2756 return this; 2757} 2758 2759// @function disableClickPropagation(el: HTMLElement): this 2760// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, 2761// `'mousedown'` and `'touchstart'` events (plus browser variants). 2762function disableClickPropagation(el) { 2763 on(el, 'mousedown touchstart dblclick', stopPropagation); 2764 addOne(el, 'click', fakeStop); 2765 return this; 2766} 2767 2768// @function preventDefault(ev: DOMEvent): this 2769// Prevents the default action of the DOM Event `ev` from happening (such as 2770// following a link in the href of the a element, or doing a POST request 2771// with page reload when a `<form>` is submitted). 2772// Use it inside listener functions. 2773function preventDefault(e) { 2774 if (e.preventDefault) { 2775 e.preventDefault(); 2776 } else { 2777 e.returnValue = false;
2778 } 2779 return this; 2780} 2781 2782// @function stop(ev: DOMEvent): this 2783// Does `stopPropagation` and `preventDefault` at the same time. 2784function stop(e) { 2785 preventDefault(e); 2786 stopPropagation(e); 2787 return this; 2788} 2789 2790// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 2791// Gets normalized mouse position from a DOM event relative to the 2792// `container` (border excluded) or to the whole page if not specified. 2793function getMousePosition(e, container) { 2794 if (!container) { 2795 return new Point(e.clientX, e.clientY); 2796 } 2797 2798 var scale = getScale(container), 2799 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 2800 2801 return new Point( 2802 // offset.left/top values are in page scale (like clientX/Y), 2803 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 2804 (e.clientX - offset.left) / scale.x - container.clientLeft, 2805 (e.clientY - offset.top) / scale.y - container.clientTop 2806 ); 2807} 2808 2809// Chrome on Win scrolls double the pixels as in other platforms (see #4538), 2810// and Firefox scrolls device pixels, not CSS pixels 2811var wheelPxFactor = 2812 (win && chrome) ? 2 * window.devicePixelRatio : 2813 gecko ? window.devicePixelRatio : 1; 2814 2815// @function getWheelDelta(ev: DOMEvent): Number 2816// Gets normalized wheel delta from a mousewheel DOM event, in vertical 2817// pixels scrolled (negative if scrolling down). 2818// Events from pointing devices without precise scrolling are mapped to 2819// a best guess of 60 pixels. 2820function getWheelDelta(e) { 2821 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 2822 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 2823 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 2824 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 2825 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2826 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 2827 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 2828 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 2829 0; 2830} 2831 2832var skipEvents = {}; 2833 2834function fakeStop(e) { 2835 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) 2836 skipEvents[e.type] = true; 2837} 2838 2839function skipped(e) { 2840 var events = skipEvents[e.type]; 2841 // reset when checking, as it's only used in map container and propagates outside of the map 2842 skipEvents[e.type] = false; 2843 return events; 2844} 2845 2846// check if element really left/entered the event target (for mouseenter/mouseleave) 2847function isExternalTarget(el, e) { 2848 2849 var related = e.relatedTarget; 2850 2851 if (!related) { return true; } 2852 2853 try { 2854 while (related && (related !== el)) { 2855 related = related.parentNode; 2856 } 2857 } catch (err) { 2858 return false; 2859 } 2860 return (related !== el); 2861} 2862 2863var lastClick; 2864 2865// this is a horrible workaround for a bug in Android where a single touch triggers two click events 2866function filterClick(e, handler) { 2867 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), 2868 elapsed = lastClick && (timeStamp - lastClick); 2869 2870 // are they closer together than 500ms yet more than 100ms? 2871 // Android typically triggers them ~300ms apart while multiple listeners 2872 // on the same event should be triggered far faster; 2873 // or check if click is simulated on the element, and if it is, reject any non-simulated events 2874 2875 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { 2876 stop(e); 2877 return; 2878 } 2879 lastClick = timeStamp; 2880 2881 handler(e); 2882} 2883 2884 2885 2886 2887var DomEvent = (Object.freeze || Object)({ 2888 on: on, 2889 off: off, 2890 stopPropagation: stopPropagation, 2891 disableScrollPropagation: disableScrollPropagation, 2892 disableClickPropagation: disableClickPropagation, 2893 preventDefault: preventDefault, 2894 stop: stop, 2895 getMousePosition: getMousePosition, 2896 getWheelDelta: getWheelDelta, 2897 fakeStop: fakeStop, 2898 skipped: skipped, 2899 isExternalTarget: isExternalTarget, 2900 addListener: on, 2901 removeListener: off 2902}); 2903 2904/* 2905 * @class PosAnimation 2906 * @aka L.PosAnimation 2907 * @inherits Evented 2908 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 2909 * 2910 * @example 2911 * ```js 2912 * var fx = new L.PosAnimation(); 2913 * fx.run(el, [300, 500], 0.5); 2914 * ``` 2915 * 2916 * @constructor L.PosAnimation() 2917 * Creates a `PosAnimation` object. 2918 * 2919 */ 2920 2921var PosAnimation = Evented.extend({ 2922 2923 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 2924 // Run an animation of a given element to a new position, optionally setting 2925 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 2926 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), 2927 // `0.5` by default). 2928 run: function (el, newPos, duration, easeLinearity) { 2929 this.stop(); 2930 2931 this._el = el; 2932 this._inProgress = true; 2933 this._duration = duration || 0.25; 2934 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 2935 2936 this._startPos = getPosition(el); 2937 this._offset = newPos.subtract(this._startPos); 2938 this._startTime = +new Date(); 2939 2940 // @event start: Event 2941 // Fired when the animation starts 2942 this.fire('start'); 2943 2944 this._animate(); 2945 }, 2946 2947 // @method stop() 2948 // Stops the animation (if currently running). 2949 stop: function () { 2950 if (!this._inProgress) { return; } 2951 2952 this._step(true); 2953 this._complete(); 2954 }, 2955 2956 _animate: function () { 2957 // animation loop 2958 this._animId = requestAnimFrame(this._animate, this); 2959 this._step(); 2960 }, 2961 2962 _step: function (round) { 2963 var elapsed = (+new Date()) - this._startTime, 2964 duration = this._duration * 1000; 2965 2966 if (elapsed < duration) { 2967 this._runFrame(this._easeOut(elapsed / duration), round); 2968 } else { 2969 this._runFrame(1); 2970 this._complete(); 2971 } 2972 }, 2973 2974 _runFrame: function (progress, round) { 2975 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 2976 if (round) { 2977 pos._round(); 2978 } 2979 setPosition(this._el, pos); 2980 2981 // @event step: Event 2982 // Fired continuously during the animation. 2983 this.fire('step'); 2984 }, 2985 2986 _complete: function () {
vendor: 18,113 bytes, lines 2987-3559
2987 cancelAnimFrame(this._animId); 2988 2989 this._inProgress = false; 2990 // @event end: Event 2991 // Fired when the animation ends. 2992 this.fire('end'); 2993 }, 2994 2995 _easeOut: function (t) { 2996 return 1 - Math.pow(1 - t, this._easeOutPower); 2997 } 2998}); 2999 3000/* 3001 * @class Map 3002 * @aka L.Map 3003 * @inherits Evented 3004 * 3005 * The central class of the API â it is used to create a map on a page and manipulate it. 3006 * 3007 * @example 3008 * 3009 * ```js 3010 * // initialize the map on the "map" div with a given center and zoom 3011 * var map = L.map('map', { 3012 * center: [51.505, -0.09], 3013 * zoom: 13 3014 * }); 3015 * ``` 3016 * 3017 */ 3018 3019var Map = Evented.extend({ 3020 3021 options: { 3022 // @section Map State Options 3023 // @option crs: CRS = L.CRS.EPSG3857 3024 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 3025 // sure what it means. 3026 crs: EPSG3857, 3027 3028 // @option center: LatLng = undefined 3029 // Initial geographic center of the map 3030 center: undefined, 3031 3032 // @option zoom: Number = undefined 3033 // Initial map zoom level 3034 zoom: undefined, 3035 3036 // @option minZoom: Number = * 3037 // Minimum zoom level of the map. 3038 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3039 // the lowest of their `minZoom` options will be used instead. 3040 minZoom: undefined, 3041 3042 // @option maxZoom: Number = * 3043 // Maximum zoom level of the map. 3044 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3045 // the highest of their `maxZoom` options will be used instead. 3046 maxZoom: undefined, 3047 3048 // @option layers: Layer[] = [] 3049 // Array of layers that will be added to the map initially 3050 layers: [], 3051 3052 // @option maxBounds: LatLngBounds = null 3053 // When this option is set, the map restricts the view to the given 3054 // geographical bounds, bouncing the user back if the user tries to pan 3055 // outside the view. To set the restriction dynamically, use 3056 // [`setMaxBounds`](#map-setmaxbounds) method. 3057 maxBounds: undefined, 3058 3059 // @option renderer: Renderer = * 3060 // The default method for drawing vector layers on the map. `L.SVG` 3061 // or `L.Canvas` by default depending on browser support. 3062 renderer: undefined, 3063 3064 3065 // @section Animation Options 3066 // @option zoomAnimation: Boolean = true 3067 // Whether the map zoom animation is enabled. By default it's enabled 3068 // in all browsers that support CSS3 Transitions except Android. 3069 zoomAnimation: true, 3070 3071 // @option zoomAnimationThreshold: Number = 4 3072 // Won't animate zoom if the zoom difference exceeds this value. 3073 zoomAnimationThreshold: 4, 3074 3075 // @option fadeAnimation: Boolean = true 3076 // Whether the tile fade animation is enabled. By default it's enabled 3077 // in all browsers that support CSS3 Transitions except Android. 3078 fadeAnimation: true, 3079 3080 // @option markerZoomAnimation: Boolean = true 3081 // Whether markers animate their zoom with the zoom animation, if disabled 3082 // they will disappear for the length of the animation. By default it's 3083 // enabled in all browsers that support CSS3 Transitions except Android. 3084 markerZoomAnimation: true, 3085 3086 // @option transform3DLimit: Number = 2^23 3087 // Defines the maximum size of a CSS translation transform. The default 3088 // value should not be changed unless a web browser positions layers in 3089 // the wrong place after doing a large `panBy`. 3090 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3091 3092 // @section Interaction Options 3093 // @option zoomSnap: Number = 1 3094 // Forces the map's zoom level to always be a multiple of this, particularly 3095 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3096 // By default, the zoom level snaps to the nearest integer; lower values 3097 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3098 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3099 zoomSnap: 1, 3100 3101 // @option zoomDelta: Number = 1 3102 // Controls how much the map's zoom level will change after a 3103 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3104 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3105 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3106 zoomDelta: 1, 3107 3108 // @option trackResize: Boolean = true 3109 // Whether the map automatically handles browser window resize to update itself. 3110 trackResize: true 3111 }, 3112 3113 initialize: function (id, options) { // (HTMLElement or String, Object) 3114 options = setOptions(this, options); 3115 3116 this._initContainer(id); 3117 this._initLayout(); 3118 3119 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3120 this._onResize = bind(this._onResize, this); 3121 3122 this._initEvents(); 3123 3124 if (options.maxBounds) { 3125 this.setMaxBounds(options.maxBounds); 3126 } 3127 3128 if (options.zoom !== undefined) { 3129 this._zoom = this._limitZoom(options.zoom); 3130 } 3131 3132 if (options.center && options.zoom !== undefined) { 3133 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3134 } 3135 3136 this._handlers = []; 3137 this._layers = {}; 3138 this._zoomBoundLayers = {}; 3139 this._sizeChanged = true; 3140 3141 this.callInitHooks(); 3142 3143 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3144 this._zoomAnimated = TRANSITION && any3d && !mobileOpera && 3145 this.options.zoomAnimation; 3146 3147 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3148 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3149 if (this._zoomAnimated) { 3150 this._createAnimProxy(); 3151 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3152 } 3153 3154 this._addLayers(this.options.layers); 3155 }, 3156 3157 3158 // @section Methods for modifying map state 3159 3160 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3161 // Sets the view of the map (geographical center and zoom) with the given 3162 // animation options. 3163 setView: function (center, zoom, options) { 3164 3165 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3166 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3167 options = options || {}; 3168 3169 this._stop(); 3170 3171 if (this._loaded && !options.reset && options !== true) { 3172 3173 if (options.animate !== undefined) { 3174 options.zoom = extend({animate: options.animate}, options.zoom); 3175 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3176 } 3177 3178 // try animating pan or zoom 3179 var moved = (this._zoom !== zoom) ? 3180 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3181 this._tryAnimatedPan(center, options.pan); 3182 3183 if (moved) { 3184 // prevent resize handler call, the view will refresh after animation anyway 3185 clearTimeout(this._sizeTimer); 3186 return this; 3187 } 3188 } 3189 3190 // animation didn't start, just reset the map view 3191 this._resetView(center, zoom); 3192 3193 return this; 3194 }, 3195 3196 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3197 // Sets the zoom of the map. 3198 setZoom: function (zoom, options) { 3199 if (!this._loaded) { 3200 this._zoom = zoom; 3201 return this; 3202 } 3203 return this.setView(this.getCenter(), zoom, {zoom: options}); 3204 }, 3205 3206 // @method zoomIn(delta?: Number, options?: Zoom options): this 3207 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3208 zoomIn: function (delta, options) { 3209 delta = delta || (any3d ? this.options.zoomDelta : 1); 3210 return this.setZoom(this._zoom + delta, options); 3211 }, 3212 3213 // @method zoomOut(delta?: Number, options?: Zoom options): this 3214 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3215 zoomOut: function (delta, options) { 3216 delta = delta || (any3d ? this.options.zoomDelta : 1); 3217 return this.setZoom(this._zoom - delta, options); 3218 }, 3219 3220 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3221 // Zooms the map while keeping a specified geographical point on the map 3222 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3223 // @alternative 3224 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3225 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3226 setZoomAround: function (latlng, zoom, options) { 3227 var scale = this.getZoomScale(zoom), 3228 viewHalf = this.getSize().divideBy(2), 3229 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3230 3231 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3232 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3233 3234 return this.setView(newCenter, zoom, {zoom: options}); 3235 }, 3236 3237 _getBoundsCenterZoom: function (bounds, options) { 3238 3239 options = options || {}; 3240 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3241 3242 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3243 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3244 3245 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3246 3247 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3248 3249 if (zoom === Infinity) { 3250 return { 3251 center: bounds.getCenter(), 3252 zoom: zoom 3253 }; 3254 } 3255 3256 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3257 3258 swPoint = this.project(bounds.getSouthWest(), zoom), 3259 nePoint = this.project(bounds.getNorthEast(), zoom), 3260 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3261 3262 return { 3263 center: center, 3264 zoom: zoom 3265 }; 3266 }, 3267 3268 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3269 // Sets a map view that contains the given geographical bounds with the 3270 // maximum zoom level possible. 3271 fitBounds: function (bounds, options) { 3272 3273 bounds = toLatLngBounds(bounds); 3274 3275 if (!bounds.isValid()) { 3276 throw new Error('Bounds are not valid.'); 3277 } 3278 3279 var target = this._getBoundsCenterZoom(bounds, options); 3280 return this.setView(target.center, target.zoom, options); 3281 }, 3282 3283 // @method fitWorld(options?: fitBounds options): this 3284 // Sets a map view that mostly contains the whole world with the maximum 3285 // zoom level possible. 3286 fitWorld: function (options) { 3287 return this.fitBounds([[-90, -180], [90, 180]], options); 3288 }, 3289 3290 // @method panTo(latlng: LatLng, options?: Pan options): this 3291 // Pans the map to a given center. 3292 panTo: function (center, options) { // (LatLng) 3293 return this.setView(center, this._zoom, {pan: options}); 3294 }, 3295 3296 // @method panBy(offset: Point, options?: Pan options): this 3297 // Pans the map by a given number of pixels (animated). 3298 panBy: function (offset, options) { 3299 offset = toPoint(offset).round(); 3300 options = options || {}; 3301 3302 if (!offset.x && !offset.y) { 3303 return this.fire('moveend'); 3304 } 3305 // If we pan too far, Chrome gets issues with tiles 3306 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3307 if (options.animate !== true && !this.getSize().contains(offset)) { 3308 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3309 return this; 3310 } 3311 3312 if (!this._panAnim) { 3313 this._panAnim = new PosAnimation(); 3314 3315 this._panAnim.on({ 3316 'step': this._onPanTransitionStep, 3317 'end': this._onPanTransitionEnd 3318 }, this); 3319 } 3320 3321 // don't fire movestart if animating inertia 3322 if (!options.noMoveStart) { 3323 this.fire('movestart'); 3324 } 3325 3326 // animate pan unless animate: false specified 3327 if (options.animate !== false) { 3328 addClass(this._mapPane, 'leaflet-pan-anim'); 3329 3330 var newPos = this._getMapPanePos().subtract(offset).round(); 3331 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3332 } else { 3333 this._rawPanBy(offset); 3334 this.fire('move').fire('moveend'); 3335 } 3336 3337 return this; 3338 }, 3339 3340 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3341 // Sets the view of the map (geographical center and zoom) performing a smooth 3342 // pan-zoom animation. 3343 flyTo: function (targetCenter, targetZoom, options) { 3344 3345 options = options || {}; 3346 if (options.animate === false || !any3d) { 3347 return this.setView(targetCenter, targetZoom, options); 3348 } 3349 3350 this._stop(); 3351 3352 var from = this.project(this.getCenter()), 3353 to = this.project(targetCenter), 3354 size = this.getSize(), 3355 startZoom = this._zoom; 3356 3357 targetCenter = toLatLng(targetCenter); 3358 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3359 3360 var w0 = Math.max(size.x, size.y), 3361 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3362 u1 = (to.distanceTo(from)) || 1, 3363 rho = 1.42, 3364 rho2 = rho * rho; 3365 3366 function r(i) { 3367 var s1 = i ? -1 : 1, 3368 s2 = i ? w1 : w0, 3369 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3370 b1 = 2 * s2 * rho2 * u1, 3371 b = t1 / b1, 3372 sq = Math.sqrt(b * b + 1) - b; 3373 3374 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3375 // thus triggering an infinite loop in flyTo 3376 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3377 3378 return log; 3379 } 3380 3381 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3382 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3383 function tanh(n) { return sinh(n) / cosh(n); } 3384 3385 var r0 = r(0); 3386 3387 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3388 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3389 3390 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3391 3392 var start = Date.now(), 3393 S = (r(1) - r0) / rho, 3394 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3395 3396 function frame() { 3397 var t = (Date.now() - start) / duration, 3398 s = easeOut(t) * S; 3399 3400 if (t <= 1) { 3401 this._flyToFrame = requestAnimFrame(frame, this); 3402 3403 this._move( 3404 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3405 this.getScaleZoom(w0 / w(s), startZoom), 3406 {flyTo: true}); 3407 3408 } else { 3409 this 3410 ._move(targetCenter, targetZoom) 3411 ._moveEnd(true); 3412 } 3413 } 3414 3415 this._moveStart(true, options.noMoveStart); 3416 3417 frame.call(this); 3418 return this; 3419 }, 3420 3421 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3422 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3423 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3424 flyToBounds: function (bounds, options) { 3425 var target = this._getBoundsCenterZoom(bounds, options); 3426 return this.flyTo(target.center, target.zoom, options); 3427 }, 3428 3429 // @method setMaxBounds(bounds: Bounds): this 3430 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3431 setMaxBounds: function (bounds) { 3432 bounds = toLatLngBounds(bounds); 3433 3434 if (!bounds.isValid()) { 3435 this.options.maxBounds = null; 3436 return this.off('moveend', this._panInsideMaxBounds); 3437 } else if (this.options.maxBounds) { 3438 this.off('moveend', this._panInsideMaxBounds); 3439 } 3440 3441 this.options.maxBounds = bounds; 3442 3443 if (this._loaded) { 3444 this._panInsideMaxBounds(); 3445 } 3446 3447 return this.on('moveend', this._panInsideMaxBounds); 3448 }, 3449 3450 // @method setMinZoom(zoom: Number): this 3451 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3452 setMinZoom: function (zoom) { 3453 var oldZoom = this.options.minZoom; 3454 this.options.minZoom = zoom; 3455 3456 if (this._loaded && oldZoom !== zoom) { 3457 this.fire('zoomlevelschange'); 3458 3459 if (this.getZoom() < this.options.minZoom) { 3460 return this.setZoom(zoom); 3461 } 3462 } 3463 3464 return this; 3465 }, 3466 3467 // @method setMaxZoom(zoom: Number): this 3468 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3469 setMaxZoom: function (zoom) { 3470 var oldZoom = this.options.maxZoom; 3471 this.options.maxZoom = zoom; 3472 3473 if (this._loaded && oldZoom !== zoom) { 3474 this.fire('zoomlevelschange'); 3475 3476 if (this.getZoom() > this.options.maxZoom) { 3477 return this.setZoom(zoom); 3478 } 3479 } 3480 3481 return this; 3482 }, 3483 3484 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3485 // 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. 3486 panInsideBounds: function (bounds, options) { 3487 this._enforcingBounds = true; 3488 var center = this.getCenter(), 3489 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3490 3491 if (!center.equals(newCenter)) { 3492 this.panTo(newCenter, options); 3493 } 3494 3495 this._enforcingBounds = false; 3496 return this; 3497 }, 3498 3499 // @method invalidateSize(options: Zoom/pan options): this 3500 // Checks if the map container size changed and updates the map if so â 3501 // call it after you've changed the map size dynamically, also animating 3502 // pan by default. If `options.pan` is `false`, panning will not occur. 3503 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3504 // that it doesn't happen often even if the method is called many 3505 // times in a row. 3506 3507 // @alternative 3508 // @method invalidateSize(animate: Boolean): this 3509 // Checks if the map container size changed and updates the map if so â 3510 // call it after you've changed the map size dynamically, also animating 3511 // pan by default. 3512 invalidateSize: function (options) { 3513 if (!this._loaded) { return this; } 3514 3515 options = extend({ 3516 animate: false, 3517 pan: true 3518 }, options === true ? {animate: true} : options); 3519 3520 var oldSize = this.getSize(); 3521 this._sizeChanged = true; 3522 this._lastCenter = null; 3523 3524 var newSize = this.getSize(), 3525 oldCenter = oldSize.divideBy(2).round(), 3526 newCenter = newSize.divideBy(2).round(), 3527 offset = oldCenter.subtract(newCenter); 3528 3529 if (!offset.x && !offset.y) { return this; } 3530 3531 if (options.animate && options.pan) { 3532 this.panBy(offset); 3533 3534 } else { 3535 if (options.pan) { 3536 this._rawPanBy(offset); 3537 } 3538 3539 this.fire('move'); 3540 3541 if (options.debounceMoveend) { 3542 clearTimeout(this._sizeTimer); 3543 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3544 } else { 3545 this.fire('moveend'); 3546 } 3547 } 3548 3549 // @section Map state change events 3550 // @event resize: ResizeEvent 3551 // Fired when the map is resized. 3552 return this.fire('resize', { 3553 oldSize: oldSize, 3554 newSize: newSize 3555 }); 3556 }, 3557 3558 // @section Methods for modifying map state 3559 // @method stop(): this
3560 // Stops the currently running `panTo` or `flyTo` animation, if any. 3561 stop: function () { 3562 this.setZoom(this._limitZoom(this._zoom)); 3563 if (!this.options.zoomSnap) { 3564 this.fire('viewreset'); 3565 } 3566 return this._stop(); 3567 }, 3568 3569 // @section Geolocation methods 3570 // @method locate(options?: Locate options): this 3571 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3572 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3573 // and optionally sets the map view to the user's location with respect to 3574 // detection accuracy (or to the world view if geolocation failed). 3575 // Note that, if your page doesn't use HTTPS, this method will fail in 3576 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3577 // See `Locate options` for more details. 3578 locate: function (options) { 3579 3580 options = this._locateOptions = extend({ 3581 timeout: 10000, 3582 watch: false 3583 // setView: false 3584 // maxZoom: <Number> 3585 // maximumAge: 0
vendor: 21,622 bytes, lines 3586-4255
3586 // enableHighAccuracy: false 3587 }, options); 3588 3589 if (!('geolocation' in navigator)) { 3590 this._handleGeolocationError({ 3591 code: 0, 3592 message: 'Geolocation not supported.' 3593 }); 3594 return this; 3595 } 3596 3597 var onResponse = bind(this._handleGeolocationResponse, this), 3598 onError = bind(this._handleGeolocationError, this); 3599 3600 if (options.watch) { 3601 this._locationWatchId = 3602 navigator.geolocation.watchPosition(onResponse, onError, options); 3603 } else { 3604 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 3605 } 3606 return this; 3607 }, 3608 3609 // @method stopLocate(): this 3610 // Stops watching location previously initiated by `map.locate({watch: true})` 3611 // and aborts resetting the map view if map.locate was called with 3612 // `{setView: true}`. 3613 stopLocate: function () { 3614 if (navigator.geolocation && navigator.geolocation.clearWatch) { 3615 navigator.geolocation.clearWatch(this._locationWatchId); 3616 } 3617 if (this._locateOptions) { 3618 this._locateOptions.setView = false; 3619 } 3620 return this; 3621 }, 3622 3623 _handleGeolocationError: function (error) { 3624 var c = error.code, 3625 message = error.message || 3626 (c === 1 ? 'permission denied' : 3627 (c === 2 ? 'position unavailable' : 'timeout')); 3628 3629 if (this._locateOptions.setView && !this._loaded) { 3630 this.fitWorld(); 3631 } 3632 3633 // @section Location events 3634 // @event locationerror: ErrorEvent 3635 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 3636 this.fire('locationerror', { 3637 code: c, 3638 message: 'Geolocation error: ' + message + '.' 3639 }); 3640 }, 3641 3642 _handleGeolocationResponse: function (pos) { 3643 var lat = pos.coords.latitude, 3644 lng = pos.coords.longitude, 3645 latlng = new LatLng(lat, lng), 3646 bounds = latlng.toBounds(pos.coords.accuracy * 2), 3647 options = this._locateOptions; 3648 3649 if (options.setView) { 3650 var zoom = this.getBoundsZoom(bounds); 3651 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 3652 } 3653 3654 var data = { 3655 latlng: latlng, 3656 bounds: bounds, 3657 timestamp: pos.timestamp 3658 }; 3659 3660 for (var i in pos.coords) { 3661 if (typeof pos.coords[i] === 'number') { 3662 data[i] = pos.coords[i]; 3663 } 3664 } 3665 3666 // @event locationfound: LocationEvent 3667 // Fired when geolocation (using the [`locate`](#map-locate) method) 3668 // went successfully. 3669 this.fire('locationfound', data); 3670 }, 3671 3672 // TODO Appropriate docs section? 3673 // @section Other Methods 3674 // @method addHandler(name: String, HandlerClass: Function): this 3675 // Adds a new `Handler` to the map, given its name and constructor function. 3676 addHandler: function (name, HandlerClass) { 3677 if (!HandlerClass) { return this; } 3678 3679 var handler = this[name] = new HandlerClass(this); 3680 3681 this._handlers.push(handler); 3682 3683 if (this.options[name]) { 3684 handler.enable(); 3685 } 3686 3687 return this; 3688 }, 3689 3690 // @method remove(): this 3691 // Destroys the map and clears all related event listeners. 3692 remove: function () { 3693 3694 this._initEvents(true); 3695 3696 if (this._containerId !== this._container._leaflet_id) { 3697 throw new Error('Map container is being reused by another instance'); 3698 } 3699 3700 try { 3701 // throws error in IE6-8 3702 delete this._container._leaflet_id; 3703 delete this._containerId; 3704 } catch (e) { 3705 /*eslint-disable */ 3706 this._container._leaflet_id = undefined; 3707 /* eslint-enable */ 3708 this._containerId = undefined; 3709 } 3710 3711 if (this._locationWatchId !== undefined) { 3712 this.stopLocate(); 3713 } 3714 3715 this._stop(); 3716 3717 remove(this._mapPane); 3718 3719 if (this._clearControlPos) { 3720 this._clearControlPos(); 3721 } 3722 if (this._resizeRequest) { 3723 cancelAnimFrame(this._resizeRequest); 3724 this._resizeRequest = null; 3725 } 3726 3727 this._clearHandlers(); 3728 3729 if (this._loaded) { 3730 // @section Map state change events 3731 // @event unload: Event 3732 // Fired when the map is destroyed with [remove](#map-remove) method. 3733 this.fire('unload'); 3734 } 3735 3736 var i; 3737 for (i in this._layers) { 3738 this._layers[i].remove(); 3739 } 3740 for (i in this._panes) { 3741 remove(this._panes[i]); 3742 } 3743 3744 this._layers = []; 3745 this._panes = []; 3746 delete this._mapPane; 3747 delete this._renderer; 3748 3749 return this; 3750 }, 3751 3752 // @section Other Methods 3753 // @method createPane(name: String, container?: HTMLElement): HTMLElement 3754 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 3755 // then returns it. The pane is created as a child of `container`, or 3756 // as a child of the main map pane if not set. 3757 createPane: function (name, container) { 3758 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3759 pane = create$1('div', className, container || this._mapPane); 3760 3761 if (name) { 3762 this._panes[name] = pane; 3763 } 3764 return pane; 3765 }, 3766 3767 // @section Methods for Getting Map State 3768 3769 // @method getCenter(): LatLng 3770 // Returns the geographical center of the map view 3771 getCenter: function () { 3772 this._checkIfLoaded(); 3773 3774 if (this._lastCenter && !this._moved()) { 3775 return this._lastCenter; 3776 } 3777 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3778 }, 3779 3780 // @method getZoom(): Number 3781 // Returns the current zoom level of the map view 3782 getZoom: function () { 3783 return this._zoom; 3784 }, 3785 3786 // @method getBounds(): LatLngBounds 3787 // Returns the geographical bounds visible in the current map view 3788 getBounds: function () { 3789 var bounds = this.getPixelBounds(), 3790 sw = this.unproject(bounds.getBottomLeft()), 3791 ne = this.unproject(bounds.getTopRight()); 3792 3793 return new LatLngBounds(sw, ne); 3794 }, 3795 3796 // @method getMinZoom(): Number 3797 // 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. 3798 getMinZoom: function () { 3799 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3800 }, 3801 3802 // @method getMaxZoom(): Number 3803 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3804 getMaxZoom: function () { 3805 return this.options.maxZoom === undefined ? 3806 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3807 this.options.maxZoom; 3808 }, 3809 3810 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 3811 // Returns the maximum zoom level on which the given bounds fit to the map 3812 // view in its entirety. If `inside` (optional) is set to `true`, the method 3813 // instead returns the minimum zoom level on which the map view fits into 3814 // the given bounds in its entirety. 3815 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3816 bounds = toLatLngBounds(bounds); 3817 padding = toPoint(padding || [0, 0]); 3818 3819 var zoom = this.getZoom() || 0, 3820 min = this.getMinZoom(), 3821 max = this.getMaxZoom(), 3822 nw = bounds.getNorthWest(), 3823 se = bounds.getSouthEast(), 3824 size = this.getSize().subtract(padding), 3825 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3826 snap = any3d ? this.options.zoomSnap : 1, 3827 scalex = size.x / boundsSize.x, 3828 scaley = size.y / boundsSize.y, 3829 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 3830 3831 zoom = this.getScaleZoom(scale, zoom); 3832 3833 if (snap) { 3834 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3835 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3836 } 3837 3838 return Math.max(min, Math.min(max, zoom)); 3839 }, 3840 3841 // @method getSize(): Point 3842 // Returns the current size of the map container (in pixels). 3843 getSize: function () { 3844 if (!this._size || this._sizeChanged) { 3845 this._size = new Point( 3846 this._container.clientWidth || 0, 3847 this._container.clientHeight || 0); 3848 3849 this._sizeChanged = false; 3850 } 3851 return this._size.clone(); 3852 }, 3853 3854 // @method getPixelBounds(): Bounds 3855 // Returns the bounds of the current map view in projected pixel 3856 // coordinates (sometimes useful in layer and overlay implementations). 3857 getPixelBounds: function (center, zoom) { 3858 var topLeftPoint = this._getTopLeftPoint(center, zoom); 3859 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 3860 }, 3861 3862 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 3863 // the map pane? "left point of the map layer" can be confusing, specially 3864 // since there can be negative offsets. 3865 // @method getPixelOrigin(): Point 3866 // Returns the projected pixel coordinates of the top left point of 3867 // the map layer (useful in custom layer and overlay implementations). 3868 getPixelOrigin: function () { 3869 this._checkIfLoaded(); 3870 return this._pixelOrigin; 3871 }, 3872 3873 // @method getPixelWorldBounds(zoom?: Number): Bounds 3874 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 3875 // If `zoom` is omitted, the map's current zoom level is used. 3876 getPixelWorldBounds: function (zoom) { 3877 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 3878 }, 3879 3880 // @section Other Methods 3881 3882 // @method getPane(pane: String|HTMLElement): HTMLElement 3883 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 3884 getPane: function (pane) { 3885 return typeof pane === 'string' ? this._panes[pane] : pane; 3886 }, 3887 3888 // @method getPanes(): Object 3889 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 3890 // the panes as values. 3891 getPanes: function () { 3892 return this._panes; 3893 }, 3894 3895 // @method getContainer: HTMLElement 3896 // Returns the HTML element that contains the map. 3897 getContainer: function () { 3898 return this._container; 3899 }, 3900 3901 3902 // @section Conversion Methods 3903 3904 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 3905 // Returns the scale factor to be applied to a map transition from zoom level 3906 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 3907 getZoomScale: function (toZoom, fromZoom) { 3908 // TODO replace with universal implementation after refactoring projections 3909 var crs = this.options.crs; 3910 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3911 return crs.scale(toZoom) / crs.scale(fromZoom); 3912 }, 3913 3914 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 3915 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 3916 // level and everything is scaled by a factor of `scale`. Inverse of 3917 // [`getZoomScale`](#map-getZoomScale). 3918 getScaleZoom: function (scale, fromZoom) { 3919 var crs = this.options.crs; 3920 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3921 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 3922 return isNaN(zoom) ? Infinity : zoom; 3923 }, 3924 3925 // @method project(latlng: LatLng, zoom: Number): Point 3926 // Projects a geographical coordinate `LatLng` according to the projection 3927 // of the map's CRS, then scales it according to `zoom` and the CRS's 3928 // `Transformation`. The result is pixel coordinate relative to 3929 // the CRS origin. 3930 project: function (latlng, zoom) { 3931 zoom = zoom === undefined ? this._zoom : zoom; 3932 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 3933 }, 3934 3935 // @method unproject(point: Point, zoom: Number): LatLng 3936 // Inverse of [`project`](#map-project). 3937 unproject: function (point, zoom) { 3938 zoom = zoom === undefined ? this._zoom : zoom; 3939 return this.options.crs.pointToLatLng(toPoint(point), zoom); 3940 }, 3941 3942 // @method layerPointToLatLng(point: Point): LatLng 3943 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 3944 // returns the corresponding geographical coordinate (for the current zoom level). 3945 layerPointToLatLng: function (point) { 3946 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 3947 return this.unproject(projectedPoint); 3948 }, 3949 3950 // @method latLngToLayerPoint(latlng: LatLng): Point 3951 // Given a geographical coordinate, returns the corresponding pixel coordinate 3952 // relative to the [origin pixel](#map-getpixelorigin). 3953 latLngToLayerPoint: function (latlng) { 3954 var projectedPoint = this.project(toLatLng(latlng))._round(); 3955 return projectedPoint._subtract(this.getPixelOrigin()); 3956 }, 3957 3958 // @method wrapLatLng(latlng: LatLng): LatLng 3959 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 3960 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 3961 // CRS's bounds. 3962 // By default this means longitude is wrapped around the dateline so its 3963 // value is between -180 and +180 degrees. 3964 wrapLatLng: function (latlng) { 3965 return this.options.crs.wrapLatLng(toLatLng(latlng)); 3966 }, 3967 3968 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 3969 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 3970 // its center is within the CRS's bounds. 3971 // By default this means the center longitude is wrapped around the dateline so its 3972 // value is between -180 and +180 degrees, and the majority of the bounds 3973 // overlaps the CRS's bounds. 3974 wrapLatLngBounds: function (latlng) { 3975 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 3976 }, 3977 3978 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 3979 // Returns the distance between two geographical coordinates according to 3980 // the map's CRS. By default this measures distance in meters. 3981 distance: function (latlng1, latlng2) { 3982 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 3983 }, 3984 3985 // @method containerPointToLayerPoint(point: Point): Point 3986 // Given a pixel coordinate relative to the map container, returns the corresponding 3987 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 3988 containerPointToLayerPoint: function (point) { // (Point) 3989 return toPoint(point).subtract(this._getMapPanePos()); 3990 }, 3991 3992 // @method layerPointToContainerPoint(point: Point): Point 3993 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 3994 // returns the corresponding pixel coordinate relative to the map container. 3995 layerPointToContainerPoint: function (point) { // (Point) 3996 return toPoint(point).add(this._getMapPanePos()); 3997 }, 3998 3999 // @method containerPointToLatLng(point: Point): LatLng 4000 // Given a pixel coordinate relative to the map container, returns 4001 // the corresponding geographical coordinate (for the current zoom level). 4002 containerPointToLatLng: function (point) { 4003 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4004 return this.layerPointToLatLng(layerPoint); 4005 }, 4006 4007 // @method latLngToContainerPoint(latlng: LatLng): Point 4008 // Given a geographical coordinate, returns the corresponding pixel coordinate 4009 // relative to the map container. 4010 latLngToContainerPoint: function (latlng) { 4011 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4012 }, 4013 4014 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4015 // Given a MouseEvent object, returns the pixel coordinate relative to the 4016 // map container where the event took place. 4017 mouseEventToContainerPoint: function (e) { 4018 return getMousePosition(e, this._container); 4019 }, 4020 4021 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4022 // Given a MouseEvent object, returns the pixel coordinate relative to 4023 // the [origin pixel](#map-getpixelorigin) where the event took place. 4024 mouseEventToLayerPoint: function (e) { 4025 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4026 }, 4027 4028 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4029 // Given a MouseEvent object, returns geographical coordinate where the 4030 // event took place. 4031 mouseEventToLatLng: function (e) { // (MouseEvent) 4032 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4033 }, 4034 4035 4036 // map initialization methods 4037 4038 _initContainer: function (id) { 4039 var container = this._container = get(id); 4040 4041 if (!container) { 4042 throw new Error('Map container not found.'); 4043 } else if (container._leaflet_id) { 4044 throw new Error('Map container is already initialized.'); 4045 } 4046 4047 on(container, 'scroll', this._onScroll, this); 4048 this._containerId = stamp(container); 4049 }, 4050 4051 _initLayout: function () { 4052 var container = this._container; 4053 4054 this._fadeAnimated = this.options.fadeAnimation && any3d; 4055 4056 addClass(container, 'leaflet-container' + 4057 (touch ? ' leaflet-touch' : '') + 4058 (retina ? ' leaflet-retina' : '') + 4059 (ielt9 ? ' leaflet-oldie' : '') + 4060 (safari ? ' leaflet-safari' : '') + 4061 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4062 4063 var position = getStyle(container, 'position'); 4064 4065 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { 4066 container.style.position = 'relative'; 4067 } 4068 4069 this._initPanes(); 4070 4071 if (this._initControlPos) { 4072 this._initControlPos(); 4073 } 4074 }, 4075 4076 _initPanes: function () { 4077 var panes = this._panes = {}; 4078 this._paneRenderers = {}; 4079 4080 // @section 4081 // 4082 // Panes are DOM elements used to control the ordering of layers on the map. You 4083 // can access panes with [`map.getPane`](#map-getpane) or 4084 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4085 // [`map.createPane`](#map-createpane) method. 4086 // 4087 // Every map has the following default panes that differ only in zIndex. 4088 // 4089 // @pane mapPane: HTMLElement = 'auto' 4090 // Pane that contains all other map panes 4091 4092 this._mapPane = this.createPane('mapPane', this._container); 4093 setPosition(this._mapPane, new Point(0, 0)); 4094 4095 // @pane tilePane: HTMLElement = 200 4096 // Pane for `GridLayer`s and `TileLayer`s 4097 this.createPane('tilePane'); 4098 // @pane overlayPane: HTMLElement = 400 4099 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4100 this.createPane('shadowPane'); 4101 // @pane shadowPane: HTMLElement = 500 4102 // Pane for overlay shadows (e.g. `Marker` shadows) 4103 this.createPane('overlayPane'); 4104 // @pane markerPane: HTMLElement = 600 4105 // Pane for `Icon`s of `Marker`s 4106 this.createPane('markerPane'); 4107 // @pane tooltipPane: HTMLElement = 650 4108 // Pane for `Tooltip`s. 4109 this.createPane('tooltipPane'); 4110 // @pane popupPane: HTMLElement = 700 4111 // Pane for `Popup`s. 4112 this.createPane('popupPane'); 4113 4114 if (!this.options.markerZoomAnimation) { 4115 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4116 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4117 } 4118 }, 4119 4120 4121 // private methods that modify map state 4122 4123 // @section Map state change events 4124 _resetView: function (center, zoom) { 4125 setPosition(this._mapPane, new Point(0, 0)); 4126 4127 var loading = !this._loaded; 4128 this._loaded = true; 4129 zoom = this._limitZoom(zoom); 4130 4131 this.fire('viewprereset'); 4132 4133 var zoomChanged = this._zoom !== zoom; 4134 this 4135 ._moveStart(zoomChanged, false) 4136 ._move(center, zoom) 4137 ._moveEnd(zoomChanged); 4138 4139 // @event viewreset: Event 4140 // Fired when the map needs to redraw its content (this usually happens 4141 // on map zoom or load). Very useful for creating custom overlays. 4142 this.fire('viewreset'); 4143 4144 // @event load: Event 4145 // Fired when the map is initialized (when its center and zoom are set 4146 // for the first time). 4147 if (loading) { 4148 this.fire('load'); 4149 } 4150 }, 4151 4152 _moveStart: function (zoomChanged, noMoveStart) { 4153 // @event zoomstart: Event 4154 // Fired when the map zoom is about to change (e.g. before zoom animation). 4155 // @event movestart: Event 4156 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4157 if (zoomChanged) { 4158 this.fire('zoomstart'); 4159 } 4160 if (!noMoveStart) { 4161 this.fire('movestart'); 4162 } 4163 return this; 4164 }, 4165 4166 _move: function (center, zoom, data) { 4167 if (zoom === undefined) { 4168 zoom = this._zoom; 4169 } 4170 var zoomChanged = this._zoom !== zoom; 4171 4172 this._zoom = zoom; 4173 this._lastCenter = center; 4174 this._pixelOrigin = this._getNewPixelOrigin(center); 4175 4176 // @event zoom: Event 4177 // Fired repeatedly during any change in zoom level, including zoom 4178 // and fly animations. 4179 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4180 this.fire('zoom', data); 4181 } 4182 4183 // @event move: Event 4184 // Fired repeatedly during any movement of the map, including pan and 4185 // fly animations. 4186 return this.fire('move', data); 4187 }, 4188 4189 _moveEnd: function (zoomChanged) { 4190 // @event zoomend: Event 4191 // Fired when the map has changed, after any animations. 4192 if (zoomChanged) { 4193 this.fire('zoomend'); 4194 } 4195 4196 // @event moveend: Event 4197 // Fired when the center of the map stops changing (e.g. user stopped 4198 // dragging the map). 4199 return this.fire('moveend'); 4200 }, 4201 4202 _stop: function () { 4203 cancelAnimFrame(this._flyToFrame); 4204 if (this._panAnim) { 4205 this._panAnim.stop(); 4206 } 4207 return this; 4208 }, 4209 4210 _rawPanBy: function (offset) { 4211 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4212 }, 4213 4214 _getZoomSpan: function () { 4215 return this.getMaxZoom() - this.getMinZoom(); 4216 }, 4217 4218 _panInsideMaxBounds: function () { 4219 if (!this._enforcingBounds) { 4220 this.panInsideBounds(this.options.maxBounds); 4221 } 4222 }, 4223 4224 _checkIfLoaded: function () { 4225 if (!this._loaded) { 4226 throw new Error('Set map center and zoom first.'); 4227 } 4228 }, 4229 4230 // DOM event handling 4231 4232 // @section Interaction events 4233 _initEvents: function (remove$$1) { 4234 this._targets = {}; 4235 this._targets[stamp(this._container)] = this; 4236 4237 var onOff = remove$$1 ? off : on; 4238 4239 // @event click: MouseEvent 4240 // Fired when the user clicks (or taps) the map. 4241 // @event dblclick: MouseEvent 4242 // Fired when the user double-clicks (or double-taps) the map. 4243 // @event mousedown: MouseEvent 4244 // Fired when the user pushes the mouse button on the map. 4245 // @event mouseup: MouseEvent 4246 // Fired when the user releases the mouse button on the map. 4247 // @event mouseover: MouseEvent 4248 // Fired when the mouse enters the map. 4249 // @event mouseout: MouseEvent 4250 // Fired when the mouse leaves the map. 4251 // @event mousemove: MouseEvent 4252 // Fired while the mouse moves over the map. 4253 // @event contextmenu: MouseEvent 4254 // Fired when the user pushes the right mouse button on the map, prevents 4255 // default browser context menu from showing if there are listeners on
4256 // this event. Also fired on mobile when the user holds a single touch 4257 // for a second (also called long press). 4258 // @event keypress: KeyboardEvent 4259 // Fired when the user presses a key from the keyboard while the map is focused. 4260 onOff(this._container, 'click dblclick mousedown mouseup ' + 4261 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); 4262 4263 if (this.options.trackResize) { 4264 onOff(window, 'resize', this._onResize, this); 4265 } 4266 4267 if (any3d && this.options.transform3DLimit) { 4268 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4269 } 4270 }, 4271 4272 _onResize: function () { 4273 cancelAnimFrame(this._resizeRequest); 4274 this._resizeRequest = requestAnimFrame( 4275 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4276 }, 4277 4278 _onScroll: function () { 4279 this._container.scrollTop = 0; 4280 this._container.scrollLeft = 0; 4281 }, 4282 4283 _onMoveEnd: function () { 4284 var pos = this._getMapPanePos(); 4285 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4286 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4287 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ 4288 this._resetView(this.getCenter(), this.getZoom()); 4289 } 4290 }, 4291 4292 _findEventTargets: function (e, type) { 4293 var targets = [], 4294 target, 4295 isHover = type === 'mouseout' || type === 'mouseover', 4296 src = e.target || e.srcElement, 4297 dragging = false; 4298 4299 while (src) { 4300 target = this._targets[stamp(src)]; 4301 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { 4302 // Prevent firing click after you just dragged an object. 4303 dragging = true; 4304 break; 4305 } 4306 if (target && target.listens(type, true)) { 4307 if (isHover && !isExternalTarget(src, e)) { break; } 4308 targets.push(target); 4309 if (isHover) { break; } 4310 } 4311 if (src === this._container) { break; } 4312 src = src.parentNode; 4313 } 4314 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) { 4315 targets = [this]; 4316 } 4317 return targets; 4318 }, 4319 4320 _handleDOMEvent: function (e) { 4321 if (!this._loaded || skipped(e)) { return; } 4322 4323 var type = e.type; 4324 4325 if (type === 'mousedown' || type === 'keypress') { 4326 // prevents outline when clicking on keyboard-focusable element 4327 preventOutline(e.target || e.srcElement); 4328 } 4329 4330 this._fireDOMEvent(e, type); 4331 }, 4332 4333 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4334 4335 _fireDOMEvent: function (e, type, targets) { 4336 4337 if (e.type === 'click') { 4338 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4339 // @event preclick: MouseEvent 4340 // Fired before mouse click on the map (sometimes useful when you 4341 // want something to happen on click before any existing click 4342 // handlers start running). 4343 var synth = extend({}, e); 4344 synth.type = 'preclick'; 4345 this._fireDOMEvent(synth, synth.type, targets); 4346 } 4347 4348 if (e._stopped) { return; } 4349 4350 // Find the layer the event is propagating from and its parents. 4351 targets = (targets || []).concat(this._findEventTargets(e, type)); 4352 4353 if (!targets.length) { return; } 4354 4355 var target = targets[0]; 4356 if (type === 'contextmenu' && target.listens(type, true)) { 4357 preventDefault(e); 4358 } 4359 4360 var data = { 4361 originalEvent: e 4362 }; 4363 4364 if (e.type !== 'keypress') { 4365 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4366 data.containerPoint = isMarker ? 4367 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4368 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4369 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4370 } 4371 4372 for (var i = 0; i < targets.length; i++) { 4373 targets[i].fire(type, data, true); 4374 if (data.originalEvent._stopped || 4375 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4376 } 4377 }, 4378 4379 _draggableMoved: function (obj) { 4380 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4381 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4382 }, 4383 4384 _clearHandlers: function () { 4385 for (var i = 0, len = this._handlers.length; i < len; i++) { 4386 this._handlers[i].disable(); 4387 } 4388 }, 4389 4390 // @section Other Methods 4391 4392 // @method whenReady(fn: Function, context?: Object): this 4393 // Runs the given function `fn` when the map gets initialized with 4394 // a view (center and zoom) and at least one layer, or immediately 4395 // if it's already initialized, optionally passing a function context. 4396 whenReady: function (callback, context) { 4397 if (this._loaded) { 4398 callback.call(context || this, {target: this}); 4399 } else { 4400 this.on('load', callback, context); 4401 } 4402 return this; 4403 }, 4404 4405 4406 // private methods for getting map state 4407 4408 _getMapPanePos: function () { 4409 return getPosition(this._mapPane) || new Point(0, 0); 4410 }, 4411 4412 _moved: function () { 4413 var pos = this._getMapPanePos(); 4414 return pos && !pos.equals([0, 0]); 4415 }, 4416 4417 _getTopLeftPoint: function (center, zoom) { 4418 var pixelOrigin = center && zoom !== undefined ? 4419 this._getNewPixelOrigin(center, zoom) : 4420 this.getPixelOrigin(); 4421 return pixelOrigin.subtract(this._getMapPanePos()); 4422 }, 4423 4424 _getNewPixelOrigin: function (center, zoom) {
vendor: 34,248 bytes, lines 4425-5679
4425 var viewHalf = this.getSize()._divideBy(2); 4426 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4427 }, 4428 4429 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4430 var topLeft = this._getNewPixelOrigin(center, zoom); 4431 return this.project(latlng, zoom)._subtract(topLeft); 4432 }, 4433 4434 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4435 var topLeft = this._getNewPixelOrigin(center, zoom); 4436 return toBounds([ 4437 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4438 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4439 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4440 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4441 ]); 4442 }, 4443 4444 // layer point of the current center 4445 _getCenterLayerPoint: function () { 4446 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4447 }, 4448 4449 // offset of the specified place to the current center in pixels 4450 _getCenterOffset: function (latlng) { 4451 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4452 }, 4453 4454 // adjust center for view to get inside bounds 4455 _limitCenter: function (center, zoom, bounds) { 4456 4457 if (!bounds) { return center; } 4458 4459 var centerPoint = this.project(center, zoom), 4460 viewHalf = this.getSize().divideBy(2), 4461 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4462 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4463 4464 // If offset is less than a pixel, ignore. 4465 // This prevents unstable projections from getting into 4466 // an infinite loop of tiny offsets. 4467 if (offset.round().equals([0, 0])) { 4468 return center; 4469 } 4470 4471 return this.unproject(centerPoint.add(offset), zoom); 4472 }, 4473 4474 // adjust offset for view to get inside bounds 4475 _limitOffset: function (offset, bounds) { 4476 if (!bounds) { return offset; } 4477 4478 var viewBounds = this.getPixelBounds(), 4479 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4480 4481 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4482 }, 4483 4484 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4485 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4486 var projectedMaxBounds = toBounds( 4487 this.project(maxBounds.getNorthEast(), zoom), 4488 this.project(maxBounds.getSouthWest(), zoom) 4489 ), 4490 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4491 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4492 4493 dx = this._rebound(minOffset.x, -maxOffset.x), 4494 dy = this._rebound(minOffset.y, -maxOffset.y); 4495 4496 return new Point(dx, dy); 4497 }, 4498 4499 _rebound: function (left, right) { 4500 return left + right > 0 ? 4501 Math.round(left - right) / 2 : 4502 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4503 }, 4504 4505 _limitZoom: function (zoom) { 4506 var min = this.getMinZoom(), 4507 max = this.getMaxZoom(), 4508 snap = any3d ? this.options.zoomSnap : 1; 4509 if (snap) { 4510 zoom = Math.round(zoom / snap) * snap; 4511 } 4512 return Math.max(min, Math.min(max, zoom)); 4513 }, 4514 4515 _onPanTransitionStep: function () { 4516 this.fire('move'); 4517 }, 4518 4519 _onPanTransitionEnd: function () { 4520 removeClass(this._mapPane, 'leaflet-pan-anim'); 4521 this.fire('moveend'); 4522 }, 4523 4524 _tryAnimatedPan: function (center, options) { 4525 // difference between the new and current centers in pixels 4526 var offset = this._getCenterOffset(center)._trunc(); 4527 4528 // don't animate too far unless animate: true specified in options 4529 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4530 4531 this.panBy(offset, options); 4532 4533 return true; 4534 }, 4535 4536 _createAnimProxy: function () { 4537 4538 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4539 this._panes.mapPane.appendChild(proxy); 4540 4541 this.on('zoomanim', function (e) { 4542 var prop = TRANSFORM, 4543 transform = this._proxy.style[prop]; 4544 4545 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4546 4547 // workaround for case when transform is the same and so transitionend event is not fired 4548 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4549 this._onZoomTransitionEnd(); 4550 } 4551 }, this); 4552 4553 this.on('load moveend', function () { 4554 var c = this.getCenter(), 4555 z = this.getZoom(); 4556 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 4557 }, this); 4558 4559 this._on('unload', this._destroyAnimProxy, this); 4560 }, 4561 4562 _destroyAnimProxy: function () { 4563 remove(this._proxy); 4564 delete this._proxy; 4565 }, 4566 4567 _catchTransitionEnd: function (e) { 4568 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 4569 this._onZoomTransitionEnd(); 4570 } 4571 }, 4572 4573 _nothingToAnimate: function () { 4574 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 4575 }, 4576 4577 _tryAnimatedZoom: function (center, zoom, options) { 4578 4579 if (this._animatingZoom) { return true; } 4580 4581 options = options || {}; 4582 4583 // don't animate if disabled, not supported or zoom difference is too large 4584 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 4585 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 4586 4587 // offset is the pixel coords of the zoom origin relative to the current center 4588 var scale = this.getZoomScale(zoom), 4589 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 4590 4591 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 4592 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 4593 4594 requestAnimFrame(function () { 4595 this 4596 ._moveStart(true, false) 4597 ._animateZoom(center, zoom, true); 4598 }, this); 4599 4600 return true; 4601 }, 4602 4603 _animateZoom: function (center, zoom, startAnim, noUpdate) { 4604 if (!this._mapPane) { return; } 4605 4606 if (startAnim) { 4607 this._animatingZoom = true; 4608 4609 // remember what center/zoom to set after animation 4610 this._animateToCenter = center; 4611 this._animateToZoom = zoom; 4612 4613 addClass(this._mapPane, 'leaflet-zoom-anim'); 4614 } 4615 4616 // @event zoomanim: ZoomAnimEvent 4617 // Fired on every frame of a zoom animation 4618 this.fire('zoomanim', { 4619 center: center, 4620 zoom: zoom, 4621 noUpdate: noUpdate 4622 }); 4623 4624 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 4625 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 4626 }, 4627 4628 _onZoomTransitionEnd: function () { 4629 if (!this._animatingZoom) { return; } 4630 4631 if (this._mapPane) { 4632 removeClass(this._mapPane, 'leaflet-zoom-anim'); 4633 } 4634 4635 this._animatingZoom = false; 4636 4637 this._move(this._animateToCenter, this._animateToZoom); 4638 4639 // This anim frame should prevent an obscure iOS webkit tile loading race condition. 4640 requestAnimFrame(function () { 4641 this._moveEnd(true); 4642 }, this); 4643 } 4644}); 4645 4646// @section 4647 4648// @factory L.map(id: String, options?: Map options) 4649// Instantiates a map object given the DOM ID of a `<div>` element 4650// and optionally an object literal with `Map options`. 4651// 4652// @alternative 4653// @factory L.map(el: HTMLElement, options?: Map options) 4654// Instantiates a map object given an instance of a `<div>` HTML element 4655// and optionally an object literal with `Map options`. 4656function createMap(id, options) { 4657 return new Map(id, options); 4658} 4659 4660/* 4661 * @class Control 4662 * @aka L.Control 4663 * @inherits Class 4664 * 4665 * L.Control is a base class for implementing map controls. Handles positioning. 4666 * All other controls extend from this class. 4667 */ 4668 4669var Control = Class.extend({ 4670 // @section 4671 // @aka Control options 4672 options: { 4673 // @option position: String = 'topright' 4674 // The position of the control (one of the map corners). Possible values are `'topleft'`, 4675 // `'topright'`, `'bottomleft'` or `'bottomright'` 4676 position: 'topright' 4677 }, 4678 4679 initialize: function (options) { 4680 setOptions(this, options); 4681 }, 4682 4683 /* @section 4684 * Classes extending L.Control will inherit the following methods: 4685 * 4686 * @method getPosition: string 4687 * Returns the position of the control. 4688 */ 4689 getPosition: function () { 4690 return this.options.position; 4691 }, 4692 4693 // @method setPosition(position: string): this 4694 // Sets the position of the control. 4695 setPosition: function (position) { 4696 var map = this._map; 4697 4698 if (map) { 4699 map.removeControl(this); 4700 } 4701 4702 this.options.position = position; 4703 4704 if (map) { 4705 map.addControl(this); 4706 } 4707 4708 return this; 4709 }, 4710 4711 // @method getContainer: HTMLElement 4712 // Returns the HTMLElement that contains the control. 4713 getContainer: function () { 4714 return this._container; 4715 }, 4716 4717 // @method addTo(map: Map): this 4718 // Adds the control to the given map. 4719 addTo: function (map) { 4720 this.remove(); 4721 this._map = map; 4722 4723 var container = this._container = this.onAdd(map), 4724 pos = this.getPosition(), 4725 corner = map._controlCorners[pos]; 4726 4727 addClass(container, 'leaflet-control'); 4728 4729 if (pos.indexOf('bottom') !== -1) { 4730 corner.insertBefore(container, corner.firstChild); 4731 } else { 4732 corner.appendChild(container); 4733 } 4734 4735 return this; 4736 }, 4737 4738 // @method remove: this 4739 // Removes the control from the map it is currently active on. 4740 remove: function () { 4741 if (!this._map) { 4742 return this; 4743 } 4744 4745 remove(this._container); 4746 4747 if (this.onRemove) { 4748 this.onRemove(this._map); 4749 } 4750 4751 this._map = null; 4752 4753 return this; 4754 }, 4755 4756 _refocusOnMap: function (e) { 4757 // if map exists and event is not a keyboard event 4758 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 4759 this._map.getContainer().focus(); 4760 } 4761 } 4762}); 4763 4764var control = function (options) { 4765 return new Control(options); 4766}; 4767 4768/* @section Extension methods 4769 * @uninheritable 4770 * 4771 * Every control should extend from `L.Control` and (re-)implement the following methods. 4772 * 4773 * @method onAdd(map: Map): HTMLElement 4774 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 4775 * 4776 * @method onRemove(map: Map) 4777 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 4778 */ 4779 4780/* @namespace Map 4781 * @section Methods for Layers and Controls 4782 */ 4783Map.include({ 4784 // @method addControl(control: Control): this 4785 // Adds the given control to the map 4786 addControl: function (control) { 4787 control.addTo(this); 4788 return this; 4789 }, 4790 4791 // @method removeControl(control: Control): this 4792 // Removes the given control from the map 4793 removeControl: function (control) { 4794 control.remove(); 4795 return this; 4796 }, 4797 4798 _initControlPos: function () { 4799 var corners = this._controlCorners = {}, 4800 l = 'leaflet-', 4801 container = this._controlContainer = 4802 create$1('div', l + 'control-container', this._container); 4803 4804 function createCorner(vSide, hSide) { 4805 var className = l + vSide + ' ' + l + hSide; 4806 4807 corners[vSide + hSide] = create$1('div', className, container); 4808 } 4809 4810 createCorner('top', 'left'); 4811 createCorner('top', 'right'); 4812 createCorner('bottom', 'left'); 4813 createCorner('bottom', 'right'); 4814 }, 4815 4816 _clearControlPos: function () { 4817 for (var i in this._controlCorners) { 4818 remove(this._controlCorners[i]); 4819 } 4820 remove(this._controlContainer); 4821 delete this._controlCorners; 4822 delete this._controlContainer; 4823 } 4824}); 4825 4826/* 4827 * @class Control.Layers 4828 * @aka L.Control.Layers 4829 * @inherits Control 4830 * 4831 * 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`. 4832 * 4833 * @example 4834 * 4835 * ```js 4836 * var baseLayers = { 4837 * "Mapbox": mapbox, 4838 * "OpenStreetMap": osm 4839 * }; 4840 * 4841 * var overlays = { 4842 * "Marker": marker, 4843 * "Roads": roadsLayer 4844 * }; 4845 * 4846 * L.control.layers(baseLayers, overlays).addTo(map); 4847 * ``` 4848 * 4849 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 4850 * 4851 * ```js 4852 * { 4853 * "<someName1>": layer1, 4854 * "<someName2>": layer2 4855 * } 4856 * ``` 4857 * 4858 * The layer names can contain HTML, which allows you to add additional styling to the items: 4859 * 4860 * ```js 4861 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 4862 * ``` 4863 */ 4864 4865var Layers = Control.extend({ 4866 // @section 4867 // @aka Control.Layers options 4868 options: { 4869 // @option collapsed: Boolean = true 4870 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. 4871 collapsed: true, 4872 position: 'topright', 4873 4874 // @option autoZIndex: Boolean = true 4875 // 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. 4876 autoZIndex: true, 4877 4878 // @option hideSingleBase: Boolean = false 4879 // If `true`, the base layers in the control will be hidden when there is only one. 4880 hideSingleBase: false, 4881 4882 // @option sortLayers: Boolean = false 4883 // Whether to sort the layers. When `false`, layers will keep the order 4884 // in which they were added to the control. 4885 sortLayers: false, 4886 4887 // @option sortFunction: Function = * 4888 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 4889 // that will be used for sorting the layers, when `sortLayers` is `true`. 4890 // The function receives both the `L.Layer` instances and their names, as in 4891 // `sortFunction(layerA, layerB, nameA, nameB)`. 4892 // By default, it sorts layers alphabetically by their name. 4893 sortFunction: function (layerA, layerB, nameA, nameB) { 4894 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 4895 } 4896 }, 4897 4898 initialize: function (baseLayers, overlays, options) { 4899 setOptions(this, options); 4900 4901 this._layerControlInputs = []; 4902 this._layers = []; 4903 this._lastZIndex = 0; 4904 this._handlingClick = false; 4905 4906 for (var i in baseLayers) { 4907 this._addLayer(baseLayers[i], i); 4908 } 4909 4910 for (i in overlays) { 4911 this._addLayer(overlays[i], i, true); 4912 } 4913 }, 4914 4915 onAdd: function (map) { 4916 this._initLayout(); 4917 this._update(); 4918 4919 this._map = map; 4920 map.on('zoomend', this._checkDisabledLayers, this); 4921 4922 for (var i = 0; i < this._layers.length; i++) { 4923 this._layers[i].layer.on('add remove', this._onLayerChange, this); 4924 } 4925 4926 return this._container; 4927 }, 4928 4929 addTo: function (map) { 4930 Control.prototype.addTo.call(this, map); 4931 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 4932 return this._expandIfNotCollapsed(); 4933 }, 4934 4935 onRemove: function () { 4936 this._map.off('zoomend', this._checkDisabledLayers, this); 4937 4938 for (var i = 0; i < this._layers.length; i++) { 4939 this._layers[i].layer.off('add remove', this._onLayerChange, this); 4940 } 4941 }, 4942 4943 // @method addBaseLayer(layer: Layer, name: String): this 4944 // Adds a base layer (radio button entry) with the given name to the control. 4945 addBaseLayer: function (layer, name) { 4946 this._addLayer(layer, name); 4947 return (this._map) ? this._update() : this; 4948 }, 4949 4950 // @method addOverlay(layer: Layer, name: String): this 4951 // Adds an overlay (checkbox entry) with the given name to the control. 4952 addOverlay: function (layer, name) { 4953 this._addLayer(layer, name, true); 4954 return (this._map) ? this._update() : this; 4955 }, 4956 4957 // @method removeLayer(layer: Layer): this 4958 // Remove the given layer from the control. 4959 removeLayer: function (layer) { 4960 layer.off('add remove', this._onLayerChange, this); 4961 4962 var obj = this._getLayer(stamp(layer)); 4963 if (obj) { 4964 this._layers.splice(this._layers.indexOf(obj), 1); 4965 } 4966 return (this._map) ? this._update() : this; 4967 }, 4968 4969 // @method expand(): this 4970 // Expand the control container if collapsed. 4971 expand: function () { 4972 addClass(this._container, 'leaflet-control-layers-expanded'); 4973 this._form.style.height = null; 4974 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 4975 if (acceptableHeight < this._form.clientHeight) { 4976 addClass(this._form, 'leaflet-control-layers-scrollbar'); 4977 this._form.style.height = acceptableHeight + 'px'; 4978 } else { 4979 removeClass(this._form, 'leaflet-control-layers-scrollbar'); 4980 } 4981 this._checkDisabledLayers(); 4982 return this; 4983 }, 4984 4985 // @method collapse(): this 4986 // Collapse the control container if expanded. 4987 collapse: function () { 4988 removeClass(this._container, 'leaflet-control-layers-expanded'); 4989 return this; 4990 }, 4991 4992 _initLayout: function () { 4993 var className = 'leaflet-control-layers', 4994 container = this._container = create$1('div', className), 4995 collapsed = this.options.collapsed; 4996 4997 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 4998 container.setAttribute('aria-haspopup', true); 4999 5000 disableClickPropagation(container); 5001 disableScrollPropagation(container); 5002 5003 var form = this._form = create$1('form', className + '-list'); 5004 5005 if (collapsed) { 5006 this._map.on('click', this.collapse, this); 5007 5008 if (!android) { 5009 on(container, { 5010 mouseenter: this.expand, 5011 mouseleave: this.collapse 5012 }, this); 5013 } 5014 } 5015 5016 var link = this._layersLink = create$1('a', className + '-toggle', container); 5017 link.href = '#'; 5018 link.title = 'Layers'; 5019 5020 if (touch) { 5021 on(link, 'click', stop); 5022 on(link, 'click', this.expand, this); 5023 } else { 5024 on(link, 'focus', this.expand, this); 5025 } 5026 5027 if (!collapsed) { 5028 this.expand(); 5029 } 5030 5031 this._baseLayersList = create$1('div', className + '-base', form); 5032 this._separator = create$1('div', className + '-separator', form); 5033 this._overlaysList = create$1('div', className + '-overlays', form); 5034 5035 container.appendChild(form); 5036 }, 5037 5038 _getLayer: function (id) { 5039 for (var i = 0; i < this._layers.length; i++) { 5040 5041 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5042 return this._layers[i]; 5043 } 5044 } 5045 }, 5046 5047 _addLayer: function (layer, name, overlay) { 5048 if (this._map) { 5049 layer.on('add remove', this._onLayerChange, this); 5050 } 5051 5052 this._layers.push({ 5053 layer: layer, 5054 name: name, 5055 overlay: overlay 5056 }); 5057 5058 if (this.options.sortLayers) { 5059 this._layers.sort(bind(function (a, b) { 5060 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5061 }, this)); 5062 } 5063 5064 if (this.options.autoZIndex && layer.setZIndex) { 5065 this._lastZIndex++; 5066 layer.setZIndex(this._lastZIndex); 5067 } 5068 5069 this._expandIfNotCollapsed(); 5070 }, 5071 5072 _update: function () { 5073 if (!this._container) { return this; } 5074 5075 empty(this._baseLayersList); 5076 empty(this._overlaysList); 5077 5078 this._layerControlInputs = []; 5079 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5080 5081 for (i = 0; i < this._layers.length; i++) { 5082 obj = this._layers[i]; 5083 this._addItem(obj); 5084 overlaysPresent = overlaysPresent || obj.overlay; 5085 baseLayersPresent = baseLayersPresent || !obj.overlay; 5086 baseLayersCount += !obj.overlay ? 1 : 0; 5087 } 5088 5089 // Hide base layers section if there's only one layer. 5090 if (this.options.hideSingleBase) { 5091 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5092 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5093 } 5094 5095 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5096 5097 return this; 5098 }, 5099 5100 _onLayerChange: function (e) { 5101 if (!this._handlingClick) { 5102 this._update(); 5103 } 5104 5105 var obj = this._getLayer(stamp(e.target)); 5106 5107 // @namespace Map 5108 // @section Layer events 5109 // @event baselayerchange: LayersControlEvent 5110 // Fired when the base layer is changed through the [layer control](#control-layers). 5111 // @event overlayadd: LayersControlEvent 5112 // Fired when an overlay is selected through the [layer control](#control-layers). 5113 // @event overlayremove: LayersControlEvent 5114 // Fired when an overlay is deselected through the [layer control](#control-layers). 5115 // @namespace Control.Layers 5116 var type = obj.overlay ? 5117 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5118 (e.type === 'add' ? 'baselayerchange' : null); 5119 5120 if (type) { 5121 this._map.fire(type, obj); 5122 } 5123 }, 5124 5125 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) 5126 _createRadioElement: function (name, checked) { 5127 5128 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5129 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5130 5131 var radioFragment = document.createElement('div'); 5132 radioFragment.innerHTML = radioHtml; 5133 5134 return radioFragment.firstChild; 5135 }, 5136 5137 _addItem: function (obj) { 5138 var label = document.createElement('label'), 5139 checked = this._map.hasLayer(obj.layer), 5140 input; 5141 5142 if (obj.overlay) { 5143 input = document.createElement('input'); 5144 input.type = 'checkbox'; 5145 input.className = 'leaflet-control-layers-selector'; 5146 input.defaultChecked = checked; 5147 } else { 5148 input = this._createRadioElement('leaflet-base-layers', checked); 5149 } 5150 5151 this._layerControlInputs.push(input); 5152 input.layerId = stamp(obj.layer); 5153 5154 on(input, 'click', this._onInputClick, this); 5155 5156 var name = document.createElement('span'); 5157 name.innerHTML = ' ' + obj.name; 5158 5159 // Helps from preventing layer control flicker when checkboxes are disabled 5160 // https://github.com/Leaflet/Leaflet/issues/2771 5161 var holder = document.createElement('div'); 5162 5163 label.appendChild(holder); 5164 holder.appendChild(input); 5165 holder.appendChild(name); 5166 5167 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5168 container.appendChild(label); 5169 5170 this._checkDisabledLayers(); 5171 return label; 5172 }, 5173 5174 _onInputClick: function () { 5175 var inputs = this._layerControlInputs, 5176 input, layer; 5177 var addedLayers = [], 5178 removedLayers = []; 5179 5180 this._handlingClick = true; 5181 5182 for (var i = inputs.length - 1; i >= 0; i--) { 5183 input = inputs[i]; 5184 layer = this._getLayer(input.layerId).layer; 5185 5186 if (input.checked) { 5187 addedLayers.push(layer); 5188 } else if (!input.checked) { 5189 removedLayers.push(layer); 5190 } 5191 } 5192 5193 // Bugfix issue 2318: Should remove all old layers before readding new ones 5194 for (i = 0; i < removedLayers.length; i++) { 5195 if (this._map.hasLayer(removedLayers[i])) { 5196 this._map.removeLayer(removedLayers[i]); 5197 } 5198 } 5199 for (i = 0; i < addedLayers.length; i++) { 5200 if (!this._map.hasLayer(addedLayers[i])) { 5201 this._map.addLayer(addedLayers[i]); 5202 } 5203 } 5204 5205 this._handlingClick = false; 5206 5207 this._refocusOnMap(); 5208 }, 5209 5210 _checkDisabledLayers: function () { 5211 var inputs = this._layerControlInputs, 5212 input, 5213 layer, 5214 zoom = this._map.getZoom(); 5215 5216 for (var i = inputs.length - 1; i >= 0; i--) { 5217 input = inputs[i]; 5218 layer = this._getLayer(input.layerId).layer; 5219 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5220 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5221 5222 } 5223 }, 5224 5225 _expandIfNotCollapsed: function () { 5226 if (this._map && !this.options.collapsed) { 5227 this.expand(); 5228 } 5229 return this; 5230 }, 5231 5232 _expand: function () { 5233 // Backward compatibility, remove me in 1.1. 5234 return this.expand(); 5235 }, 5236 5237 _collapse: function () { 5238 // Backward compatibility, remove me in 1.1. 5239 return this.collapse(); 5240 } 5241 5242}); 5243 5244 5245// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5246// Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. 5247var layers = function (baseLayers, overlays, options) { 5248 return new Layers(baseLayers, overlays, options); 5249}; 5250 5251/* 5252 * @class Control.Zoom 5253 * @aka L.Control.Zoom 5254 * @inherits Control 5255 * 5256 * 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`. 5257 */ 5258 5259var Zoom = Control.extend({ 5260 // @section 5261 // @aka Control.Zoom options 5262 options: { 5263 position: 'topleft', 5264 5265 // @option zoomInText: String = '+' 5266 // The text set on the 'zoom in' button. 5267 zoomInText: '+', 5268 5269 // @option zoomInTitle: String = 'Zoom in' 5270 // The title set on the 'zoom in' button. 5271 zoomInTitle: 'Zoom in', 5272 5273 // @option zoomOutText: String = '−' 5274 // The text set on the 'zoom out' button. 5275 zoomOutText: '−', 5276 5277 // @option zoomOutTitle: String = 'Zoom out' 5278 // The title set on the 'zoom out' button. 5279 zoomOutTitle: 'Zoom out' 5280 }, 5281 5282 onAdd: function (map) { 5283 var zoomName = 'leaflet-control-zoom', 5284 container = create$1('div', zoomName + ' leaflet-bar'), 5285 options = this.options; 5286 5287 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5288 zoomName + '-in', container, this._zoomIn); 5289 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5290 zoomName + '-out', container, this._zoomOut); 5291 5292 this._updateDisabled(); 5293 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5294 5295 return container; 5296 }, 5297 5298 onRemove: function (map) { 5299 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5300 }, 5301 5302 disable: function () { 5303 this._disabled = true; 5304 this._updateDisabled(); 5305 return this; 5306 }, 5307 5308 enable: function () { 5309 this._disabled = false; 5310 this._updateDisabled(); 5311 return this; 5312 }, 5313 5314 _zoomIn: function (e) { 5315 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5316 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5317 } 5318 }, 5319 5320 _zoomOut: function (e) { 5321 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5322 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5323 } 5324 }, 5325 5326 _createButton: function (html, title, className, container, fn) { 5327 var link = create$1('a', className, container); 5328 link.innerHTML = html; 5329 link.href = '#'; 5330 link.title = title; 5331 5332 /* 5333 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5334 */ 5335 link.setAttribute('role', 'button'); 5336 link.setAttribute('aria-label', title); 5337 5338 disableClickPropagation(link); 5339 on(link, 'click', stop); 5340 on(link, 'click', fn, this); 5341 on(link, 'click', this._refocusOnMap, this); 5342 5343 return link; 5344 }, 5345 5346 _updateDisabled: function () { 5347 var map = this._map, 5348 className = 'leaflet-disabled'; 5349 5350 removeClass(this._zoomInButton, className); 5351 removeClass(this._zoomOutButton, className); 5352 5353 if (this._disabled || map._zoom === map.getMinZoom()) { 5354 addClass(this._zoomOutButton, className); 5355 } 5356 if (this._disabled || map._zoom === map.getMaxZoom()) { 5357 addClass(this._zoomInButton, className); 5358 } 5359 } 5360}); 5361 5362// @namespace Map 5363// @section Control options 5364// @option zoomControl: Boolean = true 5365// Whether a [zoom control](#control-zoom) is added to the map by default. 5366Map.mergeOptions({ 5367 zoomControl: true 5368}); 5369 5370Map.addInitHook(function () { 5371 if (this.options.zoomControl) { 5372 // @section Controls 5373 // @property zoomControl: Control.Zoom 5374 // The default zoom control (only available if the 5375 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5376 this.zoomControl = new Zoom(); 5377 this.addControl(this.zoomControl); 5378 } 5379}); 5380 5381// @namespace Control.Zoom 5382// @factory L.control.zoom(options: Control.Zoom options) 5383// Creates a zoom control 5384var zoom = function (options) { 5385 return new Zoom(options); 5386}; 5387 5388/* 5389 * @class Control.Scale 5390 * @aka L.Control.Scale 5391 * @inherits Control 5392 * 5393 * 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`. 5394 * 5395 * @example 5396 * 5397 * ```js 5398 * L.control.scale().addTo(map); 5399 * ``` 5400 */ 5401 5402var Scale = Control.extend({ 5403 // @section 5404 // @aka Control.Scale options 5405 options: { 5406 position: 'bottomleft', 5407 5408 // @option maxWidth: Number = 100 5409 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5410 maxWidth: 100, 5411 5412 // @option metric: Boolean = True 5413 // Whether to show the metric scale line (m/km). 5414 metric: true, 5415 5416 // @option imperial: Boolean = True 5417 // Whether to show the imperial scale line (mi/ft). 5418 imperial: true 5419 5420 // @option updateWhenIdle: Boolean = false 5421 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5422 }, 5423 5424 onAdd: function (map) { 5425 var className = 'leaflet-control-scale', 5426 container = create$1('div', className), 5427 options = this.options; 5428 5429 this._addScales(options, className + '-line', container); 5430 5431 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5432 map.whenReady(this._update, this); 5433 5434 return container; 5435 }, 5436 5437 onRemove: function (map) { 5438 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5439 }, 5440 5441 _addScales: function (options, className, container) { 5442 if (options.metric) { 5443 this._mScale = create$1('div', className, container); 5444 } 5445 if (options.imperial) { 5446 this._iScale = create$1('div', className, container); 5447 } 5448 }, 5449 5450 _update: function () { 5451 var map = this._map, 5452 y = map.getSize().y / 2; 5453 5454 var maxMeters = map.distance( 5455 map.containerPointToLatLng([0, y]), 5456 map.containerPointToLatLng([this.options.maxWidth, y])); 5457 5458 this._updateScales(maxMeters); 5459 }, 5460 5461 _updateScales: function (maxMeters) { 5462 if (this.options.metric && maxMeters) { 5463 this._updateMetric(maxMeters); 5464 } 5465 if (this.options.imperial && maxMeters) { 5466 this._updateImperial(maxMeters); 5467 } 5468 }, 5469 5470 _updateMetric: function (maxMeters) { 5471 var meters = this._getRoundNum(maxMeters), 5472 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5473 5474 this._updateScale(this._mScale, label, meters / maxMeters); 5475 }, 5476 5477 _updateImperial: function (maxMeters) { 5478 var maxFeet = maxMeters * 3.2808399, 5479 maxMiles, miles, feet; 5480 5481 if (maxFeet > 5280) { 5482 maxMiles = maxFeet / 5280; 5483 miles = this._getRoundNum(maxMiles); 5484 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5485 5486 } else { 5487 feet = this._getRoundNum(maxFeet); 5488 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5489 } 5490 }, 5491 5492 _updateScale: function (scale, text, ratio) { 5493 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5494 scale.innerHTML = text; 5495 }, 5496 5497 _getRoundNum: function (num) { 5498 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5499 d = num / pow10; 5500 5501 d = d >= 10 ? 10 : 5502 d >= 5 ? 5 : 5503 d >= 3 ? 3 : 5504 d >= 2 ? 2 : 1; 5505 5506 return pow10 * d; 5507 } 5508}); 5509 5510 5511// @factory L.control.scale(options?: Control.Scale options) 5512// Creates an scale control with the given options. 5513var scale = function (options) { 5514 return new Scale(options); 5515}; 5516 5517/* 5518 * @class Control.Attribution 5519 * @aka L.Control.Attribution 5520 * @inherits Control 5521 * 5522 * 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. 5523 */ 5524 5525var Attribution = Control.extend({ 5526 // @section 5527 // @aka Control.Attribution options 5528 options: { 5529 position: 'bottomright', 5530 5531 // @option prefix: String = 'Leaflet' 5532 // The HTML text shown before the attributions. Pass `false` to disable. 5533 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' 5534 }, 5535 5536 initialize: function (options) { 5537 setOptions(this, options); 5538 5539 this._attributions = {}; 5540 }, 5541 5542 onAdd: function (map) { 5543 map.attributionControl = this; 5544 this._container = create$1('div', 'leaflet-control-attribution'); 5545 disableClickPropagation(this._container); 5546 5547 // TODO ugly, refactor 5548 for (var i in map._layers) { 5549 if (map._layers[i].getAttribution) { 5550 this.addAttribution(map._layers[i].getAttribution()); 5551 } 5552 } 5553 5554 this._update(); 5555 5556 return this._container; 5557 }, 5558 5559 // @method setPrefix(prefix: String): this 5560 // Sets the text before the attributions. 5561 setPrefix: function (prefix) { 5562 this.options.prefix = prefix; 5563 this._update(); 5564 return this; 5565 }, 5566 5567 // @method addAttribution(text: String): this 5568 // Adds an attribution text (e.g. `'Vector data © Mapbox'`). 5569 addAttribution: function (text) { 5570 if (!text) { return this; } 5571 5572 if (!this._attributions[text]) { 5573 this._attributions[text] = 0; 5574 } 5575 this._attributions[text]++; 5576 5577 this._update(); 5578 5579 return this; 5580 }, 5581 5582 // @method removeAttribution(text: String): this 5583 // Removes an attribution text. 5584 removeAttribution: function (text) { 5585 if (!text) { return this; } 5586 5587 if (this._attributions[text]) { 5588 this._attributions[text]--; 5589 this._update(); 5590 } 5591 5592 return this; 5593 }, 5594 5595 _update: function () { 5596 if (!this._map) { return; } 5597 5598 var attribs = []; 5599 5600 for (var i in this._attributions) { 5601 if (this._attributions[i]) { 5602 attribs.push(i); 5603 } 5604 } 5605 5606 var prefixAndAttribs = []; 5607 5608 if (this.options.prefix) { 5609 prefixAndAttribs.push(this.options.prefix); 5610 } 5611 if (attribs.length) { 5612 prefixAndAttribs.push(attribs.join(', ')); 5613 } 5614 5615 this._container.innerHTML = prefixAndAttribs.join(' | '); 5616 } 5617}); 5618 5619// @namespace Map 5620// @section Control options 5621// @option attributionControl: Boolean = true 5622// Whether a [attribution control](#control-attribution) is added to the map by default. 5623Map.mergeOptions({ 5624 attributionControl: true 5625}); 5626 5627Map.addInitHook(function () { 5628 if (this.options.attributionControl) { 5629 new Attribution().addTo(this); 5630 } 5631}); 5632 5633// @namespace Control.Attribution 5634// @factory L.control.attribution(options: Control.Attribution options) 5635// Creates an attribution control. 5636var attribution = function (options) { 5637 return new Attribution(options); 5638}; 5639 5640Control.Layers = Layers; 5641Control.Zoom = Zoom; 5642Control.Scale = Scale; 5643Control.Attribution = Attribution; 5644 5645control.layers = layers; 5646control.zoom = zoom; 5647control.scale = scale; 5648control.attribution = attribution; 5649 5650/* 5651 L.Handler is a base class for handler classes that are used internally to inject 5652 interaction features like dragging to classes like Map and Marker. 5653*/ 5654 5655// @class Handler 5656// @aka L.Handler 5657// Abstract class for map interaction handlers 5658 5659var Handler = Class.extend({ 5660 initialize: function (map) { 5661 this._map = map; 5662 }, 5663 5664 // @method enable(): this 5665 // Enables the handler 5666 enable: function () { 5667 if (this._enabled) { return this; } 5668 5669 this._enabled = true; 5670 this.addHooks(); 5671 return this; 5672 }, 5673 5674 // @method disable(): this 5675 // Disables the handler 5676 disable: function () { 5677 if (!this._enabled) { return this; } 5678 5679 this._enabled = false;
5680 this.removeHooks(); 5681 return this; 5682 }, 5683 5684 // @method enabled(): Boolean 5685 // Returns `true` if the handler is enabled 5686 enabled: function () { 5687 return !!this._enabled; 5688 } 5689 5690 // @section Extension methods 5691 // Classes inheriting from `Handler` must implement the two following methods: 5692 // @method addHooks() 5693 // Called when the handler is enabled, should add event hooks. 5694 // @method removeHooks() 5695 // Called when the handler is disabled, should remove the event hooks added previously. 5696}); 5697 5698// @section There is static function which can be called without instantiating L.Handler: 5699// @function addTo(map: Map, name: String): this 5700// Adds a new Handler to the given map with the given name. 5701Handler.addTo = function (map, name) { 5702 map.addHandler(name, this); 5703 return this; 5704}; 5705 5706var Mixin = {Events: Events}; 5707 5708/* 5709 * @class Draggable 5710 * @aka L.Draggable 5711 * @inherits Evented 5712 * 5713 * A class for making DOM elements draggable (including touch support). 5714 * Used internally for map and marker dragging. Only works for elements 5715 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 5716 * 5717 * @example 5718 * ```js 5719 * var draggable = new L.Draggable(elementToDrag); 5720 * draggable.enable(); 5721 * ``` 5722 */ 5723 5724var START = touch ? 'touchstart mousedown' : 'mousedown'; 5725var END = { 5726 mousedown: 'mouseup', 5727 touchstart: 'touchend', 5728 pointerdown: 'touchend', 5729 MSPointerDown: 'touchend' 5730}; 5731var MOVE = { 5732 mousedown: 'mousemove', 5733 touchstart: 'touchmove', 5734 pointerdown: 'touchmove', 5735 MSPointerDown: 'touchmove' 5736}; 5737 5738 5739var Draggable = Evented.extend({ 5740 5741 options: { 5742 // @section 5743 // @aka Draggable options 5744 // @option clickTolerance: Number = 3 5745 // The max number of pixels a user can shift the mouse pointer during a click 5746 // for it to be considered a valid click (as opposed to a mouse drag). 5747 clickTolerance: 3 5748 }, 5749 5750 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 5751 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 5752 initialize: function (element, dragStartTarget, preventOutline$$1, options) { 5753 setOptions(this, options); 5754 5755 this._element = element; 5756 this._dragStartTarget = dragStartTarget || element; 5757 this._preventOutline = preventOutline$$1; 5758 }, 5759 5760 // @method enable() 5761 // Enables the dragging ability 5762 enable: function () { 5763 if (this._enabled) { return; } 5764 5765 on(this._dragStartTarget, START, this._onDown, this); 5766 5767 this._enabled = true; 5768 }, 5769 5770 // @method disable() 5771 // Disables the dragging ability 5772 disable: function () { 5773 if (!this._enabled) { return; } 5774 5775 // If we're currently dragging this draggable, 5776 // disabling it counts as first ending the drag. 5777 if (Draggable._dragging === this) { 5778 this.finishDrag(); 5779 } 5780 5781 off(this._dragStartTarget, START, this._onDown, this); 5782 5783 this._enabled = false; 5784 this._moved = false; 5785 }, 5786 5787 _onDown: function (e) { 5788 // Ignore simulated events, since we handle both touch and 5789 // mouse explicitly; otherwise we risk getting duplicates of 5790 // touch events, see #4315. 5791 // Also ignore the event if disabled; this happens in IE11 5792 // under some circumstances, see #3666. 5793 if (e._simulated || !this._enabled) { return; } 5794 5795 this._moved = false; 5796 5797 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 5798 5799 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 5800 Draggable._dragging = this; // Prevent dragging multiple objects at once. 5801 5802 if (this._preventOutline) { 5803 preventOutline(this._element); 5804 } 5805 5806 disableImageDrag(); 5807 disableTextSelection(); 5808 5809 if (this._moving) { return; } 5810 5811 // @event down: Event 5812 // Fired when a drag is about to start. 5813 this.fire('down'); 5814 5815 var first = e.touches ? e.touches[0] : e, 5816 sizedParent = getSizedParentNode(this._element); 5817 5818 this._startPoint = new Point(first.clientX, first.clientY); 5819 5820 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 5821 this._parentScale = getScale(sizedParent); 5822 5823 on(document, MOVE[e.type], this._onMove, this); 5824 on(document, END[e.type], this._onUp, this); 5825 }, 5826 5827 _onMove: function (e) { 5828 // Ignore simulated events, since we handle both touch and 5829 // mouse explicitly; otherwise we risk getting duplicates of 5830 // touch events, see #4315. 5831 // Also ignore the event if disabled; this happens in IE11 5832 // under some circumstances, see #3666. 5833 if (e._simulated || !this._enabled) { return; } 5834 5835 if (e.touches && e.touches.length > 1) { 5836 this._moved = true; 5837 return; 5838 } 5839 5840 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 5841 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 5842 5843 if (!offset.x && !offset.y) { return; } 5844 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 5845 5846 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 5847 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 5848 // and we can use the cached value for the scale. 5849 offset.x /= this._parentScale.x; 5850 offset.y /= this._parentScale.y; 5851 5852 preventDefault(e); 5853 5854 if (!this._moved) { 5855 // @event dragstart: Event 5856 // Fired when a drag starts 5857 this.fire('dragstart'); 5858 5859 this._moved = true; 5860 this._startPos = getPosition(this._element).subtract(offset); 5861 5862 addClass(document.body, 'leaflet-dragging'); 5863 5864 this._lastTarget = e.target || e.srcElement; 5865 // IE and Edge do not give the <use> element, so fetch it 5866 // if necessary 5867 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { 5868 this._lastTarget = this._lastTarget.correspondingUseElement; 5869 } 5870 addClass(this._lastTarget, 'leaflet-drag-target'); 5871 } 5872 5873 this._newPos = this._startPos.add(offset); 5874 this._moving = true; 5875
5876 cancelAnimFrame(this._animRequest); 5877 this._lastEvent = e; 5878 this._animRequest = requestAnimFrame(this._updatePosition, this, true); 5879 }, 5880 5881 _updatePosition: function () { 5882 var e = {originalEvent: this._lastEvent}; 5883 5884 // @event predrag: Event 5885 // Fired continuously during dragging *before* each corresponding 5886 // update of the element's position. 5887 this.fire('predrag', e); 5888 setPosition(this._element, this._newPos); 5889 5890 // @event drag: Event 5891 // Fired continuously during dragging. 5892 this.fire('drag', e); 5893 }, 5894 5895 _onUp: function (e) { 5896 // Ignore simulated events, since we handle both touch and 5897 // mouse explicitly; otherwise we risk getting duplicates of 5898 // touch events, see #4315. 5899 // Also ignore the event if disabled; this happens in IE11 5900 // under some circumstances, see #3666. 5901 if (e._simulated || !this._enabled) { return; } 5902 this.finishDrag(); 5903 }, 5904 5905 finishDrag: function () { 5906 removeClass(document.body, 'leaflet-dragging'); 5907 5908 if (this._lastTarget) { 5909 removeClass(this._lastTarget, 'leaflet-drag-target'); 5910 this._lastTarget = null; 5911 } 5912 5913 for (var i in MOVE) { 5914 off(document, MOVE[i], this._onMove, this); 5915 off(document, END[i], this._onUp, this); 5916 } 5917 5918 enableImageDrag(); 5919 enableTextSelection(); 5920 5921 if (this._moved && this._moving) { 5922 // ensure drag is not fired after dragend 5923 cancelAnimFrame(this._animRequest); 5924 5925 // @event dragend: DragEndEvent 5926 // Fired when the drag ends. 5927 this.fire('dragend', { 5928 distance: this._newPos.distanceTo(this._startPos) 5929 }); 5930 } 5931 5932 this._moving = false; 5933 Draggable._dragging = false; 5934 } 5935 5936}); 5937 5938/* 5939 * @namespace LineUtil 5940 * 5941 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 5942 */ 5943 5944// Simplify polyline with vertex reduction and Douglas-Peucker simplification. 5945// Improves rendering performance dramatically by lessening the number of points to draw. 5946 5947// @function simplify(points: Point[], tolerance: Number): Point[] 5948// Dramatically reduces the number of points in a polyline while retaining 5949// its shape and returns a new array of simplified points, using the 5950// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). 5951// Used for a huge performance boost when processing/displaying Leaflet polylines for 5952// each zoom level and also reducing visual noise. tolerance affects the amount of 5953// simplification (lesser value means higher quality but slower and with more points). 5954// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). 5955function simplify(points, tolerance) { 5956 if (!tolerance || !points.length) { 5957 return points.slice(); 5958 } 5959 5960 var sqTolerance = tolerance * tolerance; 5961 5962 // stage 1: vertex reduction 5963 points = _reducePoints(points, sqTolerance); 5964 5965 // stage 2: Douglas-Peucker simplification 5966 points = _simplifyDP(points, sqTolerance); 5967 5968 return points; 5969} 5970 5971// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 5972// Returns the distance between point `p` and segment `p1` to `p2`. 5973function pointToSegmentDistance(p, p1, p2) { 5974 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 5975} 5976 5977// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 5978// Returns the closest point from a point `p` on a segment `p1` to `p2`. 5979function closestPointOnSegment(p, p1, p2) { 5980 return _sqClosestPointOnSegment(p, p1, p2); 5981} 5982 5983// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm 5984function _simplifyDP(points, sqTolerance) { 5985 5986 var len = points.length, 5987 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 5988 markers = new ArrayConstructor(len); 5989 5990 markers[0] = markers[len - 1] = 1; 5991 5992 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 5993 5994 var i, 5995 newPoints = []; 5996 5997 for (i = 0; i < len; i++) { 5998 if (markers[i]) { 5999 newPoints.push(points[i]); 6000 } 6001 } 6002 6003 return newPoints; 6004} 6005 6006function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6007 6008 var maxSqDist = 0, 6009 index, i, sqDist; 6010 6011 for (i = first + 1; i <= last - 1; i++) { 6012 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6013 6014 if (sqDist > maxSqDist) { 6015 index = i; 6016 maxSqDist = sqDist; 6017 } 6018 } 6019 6020 if (maxSqDist > sqTolerance) { 6021 markers[index] = 1; 6022 6023 _simplifyDPStep(points, markers, sqTolerance, first, index); 6024 _simplifyDPStep(points, markers, sqTolerance, index, last); 6025 } 6026} 6027 6028// reduce points that are too close to each other to a single point 6029function _reducePoints(points, sqTolerance) { 6030 var reducedPoints = [points[0]]; 6031 6032 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6033 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6034 reducedPoints.push(points[i]); 6035 prev = i; 6036 } 6037 } 6038 if (prev < len - 1) { 6039 reducedPoints.push(points[len - 1]); 6040 } 6041 return reducedPoints; 6042} 6043 6044var _lastCode; 6045 6046// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, roun
vendor: 66,777 bytes, lines 6046-8557
6046d?: Boolean): Point[]|Boolean 6047// Clips the segment a to b by rectangular bounds with the 6048// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6049// (modifying the segment points directly!). Used by Leaflet to only show polyline 6050// points that are on the screen or near, increasing performance. 6051function clipSegment(a, b, bounds, useLastCode, round) { 6052 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6053 codeB = _getBitCode(b, bounds), 6054 6055 codeOut, p, newCode; 6056 6057 // save 2nd code to avoid calculating it on the next segment 6058 _lastCode = codeB; 6059 6060 while (true) { 6061 // if a,b is inside the clip window (trivial accept) 6062 if (!(codeA | codeB)) { 6063 return [a, b]; 6064 } 6065 6066 // if a,b is outside the clip window (trivial reject) 6067 if (codeA & codeB) { 6068 return false; 6069 } 6070 6071 // other cases 6072 codeOut = codeA || codeB; 6073 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6074 newCode = _getBitCode(p, bounds); 6075 6076 if (codeOut === codeA) { 6077 a = p; 6078 codeA = newCode; 6079 } else { 6080 b = p; 6081 codeB = newCode; 6082 } 6083 } 6084} 6085 6086function _getEdgeIntersection(a, b, code, bounds, round) { 6087 var dx = b.x - a.x, 6088 dy = b.y - a.y, 6089 min = bounds.min, 6090 max = bounds.max, 6091 x, y; 6092 6093 if (code & 8) { // top 6094 x = a.x + dx * (max.y - a.y) / dy; 6095 y = max.y; 6096 6097 } else if (code & 4) { // bottom 6098 x = a.x + dx * (min.y - a.y) / dy; 6099 y = min.y; 6100 6101 } else if (code & 2) { // right 6102 x = max.x; 6103 y = a.y + dy * (max.x - a.x) / dx; 6104 6105 } else if (code & 1) { // left 6106 x = min.x; 6107 y = a.y + dy * (min.x - a.x) / dx; 6108 } 6109 6110 return new Point(x, y, round); 6111} 6112 6113function _getBitCode(p, bounds) { 6114 var code = 0; 6115 6116 if (p.x < bounds.min.x) { // left 6117 code |= 1; 6118 } else if (p.x > bounds.max.x) { // right 6119 code |= 2; 6120 } 6121 6122 if (p.y < bounds.min.y) { // bottom 6123 code |= 4; 6124 } else if (p.y > bounds.max.y) { // top 6125 code |= 8; 6126 } 6127 6128 return code; 6129} 6130 6131// square distance (to avoid unnecessary Math.sqrt calls) 6132function _sqDist(p1, p2) { 6133 var dx = p2.x - p1.x, 6134 dy = p2.y - p1.y; 6135 return dx * dx + dy * dy; 6136} 6137 6138// return closest point on segment or distance to that point 6139function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6140 var x = p1.x, 6141 y = p1.y, 6142 dx = p2.x - x, 6143 dy = p2.y - y, 6144 dot = dx * dx + dy * dy, 6145 t; 6146 6147 if (dot > 0) { 6148 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6149 6150 if (t > 1) { 6151 x = p2.x; 6152 y = p2.y; 6153 } else if (t > 0) { 6154 x += dx * t; 6155 y += dy * t; 6156 } 6157 } 6158 6159 dx = p.x - x; 6160 dy = p.y - y; 6161 6162 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6163} 6164 6165 6166// @function isFlat(latlngs: LatLng[]): Boolean 6167// Returns true if `latlngs` is a flat array, false is nested. 6168function isFlat(latlngs) { 6169 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6170} 6171 6172function _flat(latlngs) { 6173 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6174 return isFlat(latlngs); 6175} 6176 6177 6178var LineUtil = (Object.freeze || Object)({ 6179 simplify: simplify, 6180 pointToSegmentDistance: pointToSegmentDistance, 6181 closestPointOnSegment: closestPointOnSegment, 6182 clipSegment: clipSegment, 6183 _getEdgeIntersection: _getEdgeIntersection, 6184 _getBitCode: _getBitCode, 6185 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6186 isFlat: isFlat, 6187 _flat: _flat 6188}); 6189 6190/* 6191 * @namespace PolyUtil 6192 * Various utility functions for polygon geometries. 6193 */ 6194 6195/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6196 * 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)). 6197 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6198 * performance. Note that polygon points needs different algorithm for clipping 6199 * than polyline, so there's a separate method for it. 6200 */ 6201function clipPolygon(points, bounds, round) { 6202 var clippedPoints, 6203 edges = [1, 4, 2, 8], 6204 i, j, k, 6205 a, b, 6206 len, edge, p; 6207 6208 for (i = 0, len = points.length; i < len; i++) { 6209 points[i]._code = _getBitCode(points[i], bounds); 6210 } 6211 6212 // for each edge (left, bottom, right, top) 6213 for (k = 0; k < 4; k++) { 6214 edge = edges[k]; 6215 clippedPoints = []; 6216 6217 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6218 a = points[i]; 6219 b = points[j]; 6220 6221 // if a is inside the clip window 6222 if (!(a._code & edge)) { 6223 // if b is outside the clip window (a->b goes out of screen) 6224 if (b._code & edge) { 6225 p = _getEdgeIntersection(b, a, edge, bounds, round); 6226 p._code = _getBitCode(p, bounds); 6227 clippedPoints.push(p); 6228 } 6229 clippedPoints.push(a); 6230 6231 // else if b is inside the clip window (a->b enters the screen) 6232 } else if (!(b._code & edge)) { 6233 p = _getEdgeIntersection(b, a, edge, bounds, round); 6234 p._code = _getBitCode(p, bounds); 6235 clippedPoints.push(p); 6236 } 6237 } 6238 points = clippedPoints; 6239 } 6240 6241 return points; 6242} 6243 6244 6245var PolyUtil = (Object.freeze || Object)({ 6246 clipPolygon: clipPolygon 6247}); 6248 6249/* 6250 * @namespace Projection 6251 * @section 6252 * Leaflet comes with a set of already defined Projections out of the box: 6253 * 6254 * @projection L.Projection.LonLat 6255 * 6256 * Equirectangular, or Plate Carree projection â the most simple projection, 6257 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6258 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6259 * `EPSG:4326` and `Simple` CRS. 6260 */ 6261 6262var LonLat = { 6263 project: function (latlng) { 6264 return new Point(latlng.lng, latlng.lat); 6265 }, 6266 6267 unproject: function (point) { 6268 return new LatLng(point.y, point.x); 6269 }, 6270 6271 bounds: new Bounds([-180, -90], [180, 90]) 6272}; 6273 6274/* 6275 * @namespace Projection 6276 * @projection L.Projection.Mercator 6277 * 6278 * Elliptical Mercator projection â more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS. 6279 */ 6280 6281var Mercator = { 6282 R: 6378137, 6283 R_MINOR: 6356752.314245179, 6284 6285 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6286 6287 project: function (latlng) { 6288 var d = Math.PI / 180, 6289 r = this.R, 6290 y = latlng.lat * d, 6291 tmp = this.R_MINOR / r, 6292 e = Math.sqrt(1 - tmp * tmp), 6293 con = e * Math.sin(y); 6294 6295 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6296 y = -r * Math.log(Math.max(ts, 1E-10)); 6297 6298 return new Point(latlng.lng * d * r, y); 6299 }, 6300 6301 unproject: function (point) { 6302 var d = 180 / Math.PI, 6303 r = this.R, 6304 tmp = this.R_MINOR / r, 6305 e = Math.sqrt(1 - tmp * tmp), 6306 ts = Math.exp(-point.y / r), 6307 phi = Math.PI / 2 - 2 * Math.atan(ts); 6308 6309 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6310 con = e * Math.sin(phi); 6311 con = Math.pow((1 - con) / (1 + con), e / 2); 6312 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6313 phi += dphi; 6314 } 6315 6316 return new LatLng(phi * d, point.x * d / r); 6317 } 6318}; 6319 6320/* 6321 * @class Projection 6322 6323 * An object with methods for projecting geographical coordinates of the world onto 6324 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). 6325 6326 * @property bounds: Bounds 6327 * The bounds (specified in CRS units) where the projection is valid 6328 6329 * @method project(latlng: LatLng): Point 6330 * Projects geographical coordinates into a 2D point. 6331 * Only accepts actual `L.LatLng` instances, not arrays. 6332 6333 * @method unproject(point: Point): LatLng 6334 * The inverse of `project`. Projects a 2D point into a geographical location. 6335 * Only accepts actual `L.Point` instances, not arrays. 6336 6337 * Note that the projection instances do not inherit from Leafet's `Class` object, 6338 * and can't be instantiated. Also, new classes can't inherit from them, 6339 * and methods can't be added to them with the `include` function. 6340 6341 */ 6342 6343 6344 6345 6346var index = (Object.freeze || Object)({ 6347 LonLat: LonLat, 6348 Mercator: Mercator, 6349 SphericalMercator: SphericalMercator 6350}); 6351 6352/* 6353 * @namespace CRS 6354 * @crs L.CRS.EPSG3395 6355 * 6356 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6357 */ 6358var EPSG3395 = extend({}, Earth, { 6359 code: 'EPSG:3395', 6360 projection: Mercator, 6361 6362 transformation: (function () { 6363 var scale = 0.5 / (Math.PI * Mercator.R); 6364 return toTransformation(scale, 0.5, -scale, 0.5); 6365 }()) 6366}); 6367 6368/* 6369 * @namespace CRS 6370 * @crs L.CRS.EPSG4326 6371 * 6372 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6373 * 6374 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6375 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6376 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6377 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 6378 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6379 */ 6380 6381var EPSG4326 = extend({}, Earth, { 6382 code: 'EPSG:4326', 6383 projection: LonLat, 6384 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 6385}); 6386 6387/* 6388 * @namespace CRS 6389 * @crs L.CRS.Simple 6390 * 6391 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6392 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6393 * axis should still be inverted (going from bottom to top). `distance()` returns 6394 * simple euclidean distance. 6395 */ 6396 6397var Simple = extend({}, CRS, { 6398 projection: LonLat, 6399 transformation: toTransformation(1, 0, -1, 0), 6400 6401 scale: function (zoom) { 6402 return Math.pow(2, zoom); 6403 }, 6404 6405 zoom: function (scale) { 6406 return Math.log(scale) / Math.LN2; 6407 }, 6408 6409 distance: function (latlng1, latlng2) { 6410 var dx = latlng2.lng - latlng1.lng, 6411 dy = latlng2.lat - latlng1.lat; 6412 6413 return Math.sqrt(dx * dx + dy * dy); 6414 }, 6415 6416 infinite: true 6417}); 6418 6419CRS.Earth = Earth; 6420CRS.EPSG3395 = EPSG3395; 6421CRS.EPSG3857 = EPSG3857; 6422CRS.EPSG900913 = EPSG900913; 6423CRS.EPSG4326 = EPSG4326; 6424CRS.Simple = Simple; 6425 6426/* 6427 * @class Layer 6428 * @inherits Evented 6429 * @aka L.Layer 6430 * @aka ILayer 6431 * 6432 * A set of methods from the Layer base class that all Leaflet layers use. 6433 * Inherits all methods, options and events from `L.Evented`. 6434 * 6435 * @example 6436 * 6437 * ```js 6438 * var layer = L.Marker(latlng).addTo(map); 6439 * layer.addTo(map); 6440 * layer.remove(); 6441 * ``` 6442 * 6443 * @event add: Event 6444 * Fired after the layer is added to a map 6445 * 6446 * @event remove: Event 6447 * Fired after the layer is removed from a map 6448 */ 6449 6450 6451var Layer = Evented.extend({ 6452 6453 // Classes extending `L.Layer` will inherit the following options: 6454 options: { 6455 // @option pane: String = 'overlayPane' 6456 // 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. 6457 pane: 'overlayPane', 6458 6459 // @option attribution: String = null 6460 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox". 6461 attribution: null, 6462 6463 bubblingMouseEvents: true 6464 }, 6465 6466 /* @section 6467 * Classes extending `L.Layer` will inherit the following methods: 6468 * 6469 * @method addTo(map: Map|LayerGroup): this 6470 * Adds the layer to the given map or layer group. 6471 */ 6472 addTo: function (map) { 6473 map.addLayer(this); 6474 return this; 6475 }, 6476 6477 // @method remove: this 6478 // Removes the layer from the map it is currently active on. 6479 remove: function () { 6480 return this.removeFrom(this._map || this._mapToAdd); 6481 }, 6482 6483 // @method removeFrom(map: Map): this 6484 // Removes the layer from the given map 6485 removeFrom: function (obj) { 6486 if (obj) { 6487 obj.removeLayer(this); 6488 } 6489 return this; 6490 }, 6491 6492 // @method getPane(name? : String): HTMLElement 6493 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 6494 getPane: function (name) { 6495 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 6496 }, 6497 6498 addInteractiveTarget: function (targetEl) { 6499 this._map._targets[stamp(targetEl)] = this; 6500 return this; 6501 }, 6502 6503 removeInteractiveTarget: function (targetEl) { 6504 delete this._map._targets[stamp(targetEl)]; 6505 return this; 6506 }, 6507 6508 // @method getAttribution: String 6509 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 6510 getAttribution: function () { 6511 return this.options.attribution; 6512 }, 6513 6514 _layerAdd: function (e) { 6515 var map = e.target; 6516 6517 // check in case layer gets added and then removed before the map is ready 6518 if (!map.hasLayer(this)) { return; } 6519 6520 this._map = map; 6521 this._zoomAnimated = map._zoomAnimated; 6522 6523 if (this.getEvents) { 6524 var events = this.getEvents(); 6525 map.on(events, this); 6526 this.once('remove', function () { 6527 map.off(events, this); 6528 }, this); 6529 } 6530 6531 this.onAdd(map); 6532 6533 if (this.getAttribution && map.attributionControl) { 6534 map.attributionControl.addAttribution(this.getAttribution()); 6535 } 6536 6537 this.fire('add'); 6538 map.fire('layeradd', {layer: this}); 6539 } 6540}); 6541 6542/* @section Extension methods 6543 * @uninheritable 6544 * 6545 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 6546 * 6547 * @method onAdd(map: Map): this 6548 * 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). 6549 * 6550 * @method onRemove(map: Map): this 6551 * 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). 6552 * 6553 * @method getEvents(): Object 6554 * 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. 6555 * 6556 * @method getAttribution(): String 6557 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 6558 * 6559 * @method beforeAdd(map: Map): this 6560 * 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. 6561 */ 6562 6563 6564/* @namespace Map 6565 * @section Layer events 6566 * 6567 * @event layeradd: LayerEvent 6568 * Fired when a new layer is added to the map. 6569 * 6570 * @event layerremove: LayerEvent 6571 * Fired when some layer is removed from the map 6572 * 6573 * @section Methods for Layers and Controls 6574 */ 6575Map.include({ 6576 // @method addLayer(layer: Layer): this 6577 // Adds the given layer to the map 6578 addLayer: function (layer) { 6579 if (!layer._layerAdd) { 6580 throw new Error('The provided object is not a Layer.'); 6581 } 6582 6583 var id = stamp(layer); 6584 if (this._layers[id]) { return this; } 6585 this._layers[id] = layer; 6586 6587 layer._mapToAdd = this; 6588 6589 if (layer.beforeAdd) { 6590 layer.beforeAdd(this); 6591 } 6592 6593 this.whenReady(layer._layerAdd, layer); 6594 6595 return this; 6596 }, 6597 6598 // @method removeLayer(layer: Layer): this 6599 // Removes the given layer from the map. 6600 removeLayer: function (layer) { 6601 var id = stamp(layer); 6602 6603 if (!this._layers[id]) { return this; } 6604 6605 if (this._loaded) { 6606 layer.onRemove(this); 6607 } 6608 6609 if (layer.getAttribution && this.attributionControl) { 6610 this.attributionControl.removeAttribution(layer.getAttribution()); 6611 } 6612 6613 delete this._layers[id]; 6614 6615 if (this._loaded) { 6616 this.fire('layerremove', {layer: layer}); 6617 layer.fire('remove'); 6618 } 6619 6620 layer._map = layer._mapToAdd = null; 6621 6622 return this; 6623 }, 6624 6625 // @method hasLayer(layer: Layer): Boolean 6626 // Returns `true` if the given layer is currently added to the map 6627 hasLayer: function (layer) { 6628 return !!layer && (stamp(layer) in this._layers); 6629 }, 6630 6631 /* @method eachLayer(fn: Function, context?: Object): this 6632 * Iterates over the layers of the map, optionally specifying context of the iterator function. 6633 * ``` 6634 * map.eachLayer(function(layer){ 6635 * layer.bindPopup('Hello'); 6636 * }); 6637 * ``` 6638 */ 6639 eachLayer: function (method, context) { 6640 for (var i in this._layers) { 6641 method.call(context, this._layers[i]); 6642 } 6643 return this; 6644 }, 6645 6646 _addLayers: function (layers) { 6647 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 6648 6649 for (var i = 0, len = layers.length; i < len; i++) { 6650 this.addLayer(layers[i]); 6651 } 6652 }, 6653 6654 _addZoomLimit: function (layer) { 6655 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 6656 this._zoomBoundLayers[stamp(layer)] = layer; 6657 this._updateZoomLevels(); 6658 } 6659 }, 6660 6661 _removeZoomLimit: function (layer) { 6662 var id = stamp(layer); 6663 6664 if (this._zoomBoundLayers[id]) { 6665 delete this._zoomBoundLayers[id]; 6666 this._updateZoomLevels(); 6667 } 6668 }, 6669 6670 _updateZoomLevels: function () { 6671 var minZoom = Infinity, 6672 maxZoom = -Infinity, 6673 oldZoomSpan = this._getZoomSpan(); 6674 6675 for (var i in this._zoomBoundLayers) { 6676 var options = this._zoomBoundLayers[i].options; 6677 6678 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 6679 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 6680 } 6681 6682 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 6683 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 6684 6685 // @section Map state change events 6686 // @event zoomlevelschange: Event 6687 // Fired when the number of zoomlevels on the map is changed due 6688 // to adding or removing a layer. 6689 if (oldZoomSpan !== this._getZoomSpan()) { 6690 this.fire('zoomlevelschange'); 6691 } 6692 6693 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 6694 this.setZoom(this._layersMaxZoom); 6695 } 6696 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 6697 this.setZoom(this._layersMinZoom); 6698 } 6699 } 6700}); 6701 6702/* 6703 * @class LayerGroup 6704 * @aka L.LayerGroup 6705 * @inherits Layer 6706 * 6707 * Used to group several layers and handle them as one. If you add it to the map, 6708 * any layers added or removed from the group will be added/removed on the map as 6709 * well. Extends `Layer`. 6710 * 6711 * @example 6712 * 6713 * ```js 6714 * L.layerGroup([marker1, marker2]) 6715 * .addLayer(polyline) 6716 * .addTo(map); 6717 * ``` 6718 */ 6719 6720var LayerGroup = Layer.extend({ 6721 6722 initialize: function (layers, options) { 6723 setOptions(this, options); 6724 6725 this._layers = {}; 6726 6727 var i, len; 6728 6729 if (layers) { 6730 for (i = 0, len = layers.length; i < len; i++) { 6731 this.addLayer(layers[i]); 6732 } 6733 } 6734 }, 6735 6736 // @method addLayer(layer: Layer): this 6737 // Adds the given layer to the group. 6738 addLayer: function (layer) { 6739 var id = this.getLayerId(layer); 6740 6741 this._layers[id] = layer; 6742 6743 if (this._map) { 6744 this._map.addLayer(layer); 6745 } 6746 6747 return this; 6748 }, 6749 6750 // @method removeLayer(layer: Layer): this 6751 // Removes the given layer from the group. 6752 // @alternative 6753 // @method removeLayer(id: Number): this 6754 // Removes the layer with the given internal ID from the group. 6755 removeLayer: function (layer) { 6756 var id = layer in this._layers ? layer : this.getLayerId(layer); 6757 6758 if (this._map && this._layers[id]) { 6759 this._map.removeLayer(this._layers[id]); 6760 } 6761 6762 delete this._layers[id]; 6763 6764 return this; 6765 }, 6766 6767 // @method hasLayer(layer: Layer): Boolean 6768 // Returns `true` if the given layer is currently added to the group. 6769 // @alternative 6770 // @method hasLayer(id: Number): Boolean 6771 // Returns `true` if the given internal ID is currently added to the group. 6772 hasLayer: function (layer) { 6773 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); 6774 }, 6775 6776 // @method clearLayers(): this 6777 // Removes all the layers from the group. 6778 clearLayers: function () { 6779 return this.eachLayer(this.removeLayer, this); 6780 }, 6781 6782 // @method invoke(methodName: String, â¦): this 6783 // Calls `methodName` on every layer contained in this group, passing any 6784 // additional parameters. Has no effect if the layers contained do not 6785 // implement `methodName`. 6786 invoke: function (methodName) { 6787 var args = Array.prototype.slice.call(arguments, 1), 6788 i, layer; 6789 6790 for (i in this._layers) { 6791 layer = this._layers[i]; 6792 6793 if (layer[methodName]) { 6794 layer[methodName].apply(layer, args); 6795 } 6796 } 6797 6798 return this; 6799 }, 6800 6801 onAdd: function (map) { 6802 this.eachLayer(map.addLayer, map); 6803 }, 6804 6805 onRemove: function (map) { 6806 this.eachLayer(map.removeLayer, map); 6807 }, 6808 6809 // @method eachLayer(fn: Function, context?: Object): this 6810 // Iterates over the layers of the group, optionally specifying context of the iterator function. 6811 // ```js 6812 // group.eachLayer(function (layer) { 6813 // layer.bindPopup('Hello'); 6814 // }); 6815 // ``` 6816 eachLayer: function (method, context) { 6817 for (var i in this._layers) { 6818 method.call(context, this._layers[i]); 6819 } 6820 return this; 6821 }, 6822 6823 // @method getLayer(id: Number): Layer 6824 // Returns the layer with the given internal ID. 6825 getLayer: function (id) { 6826 return this._layers[id]; 6827 }, 6828 6829 // @method getLayers(): Layer[] 6830 // Returns an array of all the layers added to the group. 6831 getLayers: function () { 6832 var layers = []; 6833 this.eachLayer(layers.push, layers); 6834 return layers; 6835 }, 6836 6837 // @method setZIndex(zIndex: Number): this 6838 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 6839 setZIndex: function (zIndex) { 6840 return this.invoke('setZIndex', zIndex); 6841 }, 6842 6843 // @method getLayerId(layer: Layer): Number 6844 // Returns the internal ID for a layer 6845 getLayerId: function (layer) { 6846 return stamp(layer); 6847 } 6848}); 6849 6850 6851// @factory L.layerGroup(layers?: Layer[], options?: Object) 6852// Create a layer group, optionally given an initial set of layers and an `options` object. 6853var layerGroup = function (layers, options) { 6854 return new LayerGroup(layers, options); 6855}; 6856 6857/* 6858 * @class FeatureGroup 6859 * @aka L.FeatureGroup 6860 * @inherits LayerGroup 6861 * 6862 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 6863 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 6864 * * Events are propagated to the `FeatureGroup`, so if the group has an event 6865 * handler, it will handle events from any of the layers. This includes mouse events 6866 * and custom events. 6867 * * Has `layeradd` and `layerremove` events 6868 * 6869 * @example 6870 * 6871 * ```js 6872 * L.featureGroup([marker1, marker2, polyline]) 6873 * .bindPopup('Hello world!') 6874 * .on('click', function() { alert('Clicked on a member of the group!'); }) 6875 * .addTo(map); 6876 * ``` 6877 */ 6878 6879var FeatureGroup = LayerGroup.extend({ 6880 6881 addLayer: function (layer) { 6882 if (this.hasLayer(layer)) { 6883 return this; 6884 } 6885 6886 layer.addEventParent(this); 6887 6888 LayerGroup.prototype.addLayer.call(this, layer); 6889 6890 // @event layeradd: LayerEvent 6891 // Fired when a layer is added to this `FeatureGroup` 6892 return this.fire('layeradd', {layer: layer}); 6893 }, 6894 6895 removeLayer: function (layer) { 6896 if (!this.hasLayer(layer)) { 6897 return this; 6898 } 6899 if (layer in this._layers) { 6900 layer = this._layers[layer]; 6901 } 6902 6903 layer.removeEventParent(this); 6904 6905 LayerGroup.prototype.removeLayer.call(this, layer); 6906 6907 // @event layerremove: LayerEvent 6908 // Fired when a layer is removed from this `FeatureGroup` 6909 return this.fire('layerremove', {layer: layer}); 6910 }, 6911 6912 // @method setStyle(style: Path options): this 6913 // Sets the given path options to each layer of the group that has a `setStyle` method. 6914 setStyle: function (style) { 6915 return this.invoke('setStyle', style); 6916 }, 6917 6918 // @method bringToFront(): this 6919 // Brings the layer group to the top of all other layers 6920 bringToFront: function () { 6921 return this.invoke('bringToFront'); 6922 }, 6923 6924 // @method bringToBack(): this 6925 // Brings the layer group to the back of all other layers 6926 bringToBack: function () { 6927 return this.invoke('bringToBack'); 6928 }, 6929 6930 // @method getBounds(): LatLngBounds 6931 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 6932 getBounds: function () { 6933 var bounds = new LatLngBounds(); 6934 6935 for (var id in this._layers) { 6936 var layer = this._layers[id]; 6937 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 6938 } 6939 return bounds; 6940 } 6941}); 6942 6943// @factory L.featureGroup(layers: Layer[]) 6944// Create a feature group, optionally given an initial set of layers. 6945var featureGroup = function (layers) { 6946 return new FeatureGroup(layers); 6947}; 6948 6949/* 6950 * @class Icon 6951 * @aka L.Icon 6952 * 6953 * Represents an icon to provide when creating a marker. 6954 * 6955 * @example 6956 * 6957 * ```js 6958 * var myIcon = L.icon({ 6959 * iconUrl: 'my-icon.png', 6960 * iconRetinaUrl: '[email protected]', 6961 * iconSize: [38, 95], 6962 * iconAnchor: [22, 94], 6963 * popupAnchor: [-3, -76], 6964 * shadowUrl: 'my-icon-shadow.png', 6965 * shadowRetinaUrl: '[email protected]', 6966 * shadowSize: [68, 95], 6967 * shadowAnchor: [22, 94] 6968 * }); 6969 * 6970 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 6971 * ``` 6972 * 6973 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 6974 * 6975 */ 6976 6977var Icon = Class.extend({ 6978 6979 /* @section 6980 * @aka Icon options 6981 * 6982 * @option iconUrl: String = null 6983 * **(required)** The URL to the icon image (absolute or relative to your script path). 6984 * 6985 * @option iconRetinaUrl: String = null 6986 * The URL to a retina sized version of the icon image (absolute or relative to your 6987 * script path). Used for Retina screen devices. 6988 * 6989 * @option iconSize: Point = null 6990 * Size of the icon image in pixels. 6991 * 6992 * @option iconAnchor: Point = null 6993 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 6994 * will be aligned so that this point is at the marker's geographical location. Centered 6995 * by default if size is specified, also can be set in CSS with negative margins. 6996 * 6997 * @option popupAnchor: Point = [0, 0] 6998 * The coordinates of the point from which popups will "open", relative to the icon anchor. 6999 * 7000 * @option tooltipAnchor: Point = [0, 0] 7001 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7002 * 7003 * @option shadowUrl: String = null 7004 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7005 * 7006 * @option shadowRetinaUrl: String = null 7007 * 7008 * @option shadowSize: Point = null 7009 * Size of the shadow image in pixels. 7010 * 7011 * @option shadowAnchor: Point = null 7012 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7013 * as iconAnchor if not specified). 7014 * 7015 * @option className: String = '' 7016 * A custom class name to assign to both icon and shadow images. Empty by default. 7017 */ 7018 7019 options: { 7020 popupAnchor: [0, 0], 7021 tooltipAnchor: [0, 0] 7022 }, 7023 7024 initialize: function (options) { 7025 setOptions(this, options); 7026 }, 7027 7028 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7029 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7030 // styled according to the options. 7031 createIcon: function (oldIcon) { 7032 return this._createIcon('icon', oldIcon); 7033 }, 7034 7035 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7036 // As `createIcon`, but for the shadow beneath it. 7037 createShadow: function (oldIcon) { 7038 return this._createIcon('shadow', oldIcon); 7039 }, 7040 7041 _createIcon: function (name, oldIcon) { 7042 var src = this._getIconUrl(name); 7043 7044 if (!src) { 7045 if (name === 'icon') { 7046 throw new Error('iconUrl not set in Icon options (see the docs).'); 7047 } 7048 return null; 7049 } 7050 7051 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7052 this._setIconStyles(img, name); 7053 7054 return img; 7055 }, 7056 7057 _setIconStyles: function (img, name) { 7058 var options = this.options; 7059 var sizeOption = options[name + 'Size']; 7060 7061 if (typeof sizeOption === 'number') { 7062 sizeOption = [sizeOption, sizeOption]; 7063 } 7064 7065 var size = toPoint(sizeOption), 7066 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7067 size && size.divideBy(2, true)); 7068 7069 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7070 7071 if (anchor) { 7072 img.style.marginLeft = (-anchor.x) + 'px'; 7073 img.style.marginTop = (-anchor.y) + 'px'; 7074 } 7075 7076 if (size) { 7077 img.style.width = size.x + 'px'; 7078 img.style.height = size.y + 'px'; 7079 } 7080 }, 7081 7082 _createImg: function (src, el) { 7083 el = el || document.createElement('img'); 7084 el.src = src; 7085 return el; 7086 }, 7087 7088 _getIconUrl: function (name) { 7089 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7090 } 7091}); 7092 7093 7094// @factory L.icon(options: Icon options) 7095// Creates an icon instance with the given options. 7096function icon(options) { 7097 return new Icon(options); 7098} 7099 7100/* 7101 * @miniclass Icon.Default (Icon) 7102 * @aka L.Icon.Default 7103 * @section 7104 * 7105 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7106 * no icon is specified. Points to the blue marker image distributed with Leaflet 7107 * releases. 7108 * 7109 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7110 * (which is a set of `Icon options`). 7111 * 7112 * If you want to _completely_ replace the default icon, override the 7113 * `L.Marker.prototype.options.icon` with your own icon instead. 7114 */ 7115 7116var IconDefault = Icon.extend({ 7117 7118 options: { 7119 iconUrl: 'marker-icon-old.png', 7120 iconRetinaUrl: 'marker-icon-2x.png', 7121 shadowUrl: 'marker-shadow.png', 7122 iconSize: [25, 41], 7123 iconAnchor: [12, 41], 7124 popupAnchor: [1, -34], 7125 tooltipAnchor: [16, -28], 7126 shadowSize: [41, 41] 7127 }, 7128 7129 _getIconUrl: function (name) { 7130 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only 7131 IconDefault.imagePath = this._detectIconPath(); 7132 } 7133 7134 // @option imagePath: String 7135 // `Icon.Default` will try to auto-detect the location of the 7136 // blue icon images. If you are placing these images in a non-standard 7137 // way, set this option to point to the right path. 7138 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7139 }, 7140 7141 _detectIconPath: function () { 7142 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7143 var path = getStyle(el, 'background-image') || 7144 getStyle(el, 'backgroundImage'); // IE8 7145 7146 document.body.removeChild(el); 7147 7148 if (path === null || path.indexOf('url') !== 0) { 7149 path = ''; 7150 } else { 7151 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, ''); 7152 } 7153 7154 return path; 7155 } 7156}); 7157 7158/* 7159 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7160 */ 7161 7162 7163/* @namespace Marker 7164 * @section Interaction handlers 7165 * 7166 * 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: 7167 * 7168 * ```js 7169 * marker.dragging.disable(); 7170 * ``` 7171 * 7172 * @property dragging: Handler 7173 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7174 */ 7175 7176var MarkerDrag = Handler.extend({ 7177 initialize: function (marker) { 7178 this._marker = marker; 7179 }, 7180 7181 addHooks: function () { 7182 var icon = this._marker._icon; 7183 7184 if (!this._draggable) { 7185 this._draggable = new Draggable(icon, icon, true); 7186 } 7187 7188 this._draggable.on({ 7189 dragstart: this._onDragStart, 7190 predrag: this._onPreDrag, 7191 drag: this._onDrag, 7192 dragend: this._onDragEnd 7193 }, this).enable(); 7194 7195 addClass(icon, 'leaflet-marker-draggable'); 7196 }, 7197 7198 removeHooks: function () { 7199 this._draggable.off({ 7200 dragstart: this._onDragStart, 7201 predrag: this._onPreDrag, 7202 drag: this._onDrag, 7203 dragend: this._onDragEnd 7204 }, this).disable(); 7205 7206 if (this._marker._icon) { 7207 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7208 } 7209 }, 7210 7211 moved: function () { 7212 return this._draggable && this._draggable._moved; 7213 }, 7214 7215 _adjustPan: function (e) { 7216 var marker = this._marker, 7217 map = marker._map, 7218 speed = this._marker.options.autoPanSpeed, 7219 padding = this._marker.options.autoPanPadding, 7220 iconPos = getPosition(marker._icon), 7221 bounds = map.getPixelBounds(), 7222 origin = map.getPixelOrigin(); 7223 7224 var panBounds = toBounds( 7225 bounds.min._subtract(origin).add(padding), 7226 bounds.max._subtract(origin).subtract(padding) 7227 ); 7228 7229 if (!panBounds.contains(iconPos)) { 7230 // Compute incremental movement 7231 var movement = toPoint( 7232 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7233 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7234 7235 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7236 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7237 ).multiplyBy(speed); 7238 7239 map.panBy(movement, {animate: false}); 7240 7241 this._draggable._newPos._add(movement); 7242 this._draggable._startPos._add(movement); 7243 7244 setPosition(marker._icon, this._draggable._newPos); 7245 this._onDrag(e); 7246 7247 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7248 } 7249 }, 7250 7251 _onDragStart: function () { 7252 // @section Dragging events 7253 // @event dragstart: Event 7254 // Fired when the user starts dragging the marker. 7255 7256 // @event movestart: Event 7257 // Fired when the marker starts moving (because of dragging). 7258 7259 this._oldLatLng = this._marker.getLatLng(); 7260 this._marker 7261 .closePopup() 7262 .fire('movestart') 7263 .fire('dragstart'); 7264 }, 7265 7266 _onPreDrag: function (e) { 7267 if (this._marker.options.autoPan) { 7268 cancelAnimFrame(this._panRequest); 7269 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7270 } 7271 }, 7272 7273 _onDrag: function (e) { 7274 var marker = this._marker, 7275 shadow = marker._shadow, 7276 iconPos = getPosition(marker._icon), 7277 latlng = marker._map.layerPointToLatLng(iconPos); 7278 7279 // update shadow position 7280 if (shadow) { 7281 setPosition(shadow, iconPos); 7282 } 7283 7284 marker._latlng = latlng; 7285 e.latlng = latlng; 7286 e.oldLatLng = this._oldLatLng; 7287 7288 // @event drag: Event 7289 // Fired repeatedly while the user drags the marker. 7290 marker 7291 .fire('move', e) 7292 .fire('drag', e); 7293 }, 7294 7295 _onDragEnd: function (e) { 7296 // @event dragend: DragEndEvent 7297 // Fired when the user stops dragging the marker. 7298 7299 cancelAnimFrame(this._panRequest); 7300 7301 // @event moveend: Event 7302 // Fired when the marker stops moving (because of dragging). 7303 delete this._oldLatLng; 7304 this._marker 7305 .fire('moveend') 7306 .fire('dragend', e); 7307 } 7308}); 7309 7310/* 7311 * @class Marker 7312 * @inherits Interactive layer 7313 * @aka L.Marker 7314 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7315 * 7316 * @example 7317 * 7318 * ```js 7319 * L.marker([50.5, 30.5]).addTo(map); 7320 * ``` 7321 */ 7322 7323var Marker = Layer.extend({ 7324 7325 // @section 7326 // @aka Marker options 7327 options: { 7328 // @option icon: Icon = * 7329 // Icon instance to use for rendering the marker. 7330 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7331 // If not specified, a common instance of `L.Icon.Default` is used. 7332 icon: new IconDefault(), 7333 7334 // Option inherited from "Interactive layer" abstract class 7335 interactive: true, 7336 7337 // @option keyboard: Boolean = true 7338 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7339 keyboard: true, 7340 7341 // @option title: String = '' 7342 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7343 title: '', 7344 7345 // @option alt: String = '' 7346 // Text for the `alt` attribute of the icon image (useful for accessibility). 7347 alt: '', 7348 7349 // @option zIndexOffset: Number = 0 7350 // 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). 7351 zIndexOffset: 0, 7352 7353 // @option opacity: Number = 1.0 7354 // The opacity of the marker. 7355 opacity: 1, 7356 7357 // @option riseOnHover: Boolean = false 7358 // If `true`, the marker will get on top of others when you hover the mouse over it. 7359 riseOnHover: false, 7360 7361 // @option riseOffset: Number = 250 7362 // The z-index offset used for the `riseOnHover` feature. 7363 riseOffset: 250, 7364 7365 // @option pane: String = 'markerPane' 7366 // `Map pane` where the markers icon will be added. 7367 pane: 'markerPane', 7368 7369 // @option bubblingMouseEvents: Boolean = false 7370 // When `true`, a mouse event on this marker will trigger the same event on the map 7371 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7372 bubblingMouseEvents: false, 7373 7374 // @section Draggable marker options 7375 // @option draggable: Boolean = false 7376 // Whether the marker is draggable with mouse/touch or not. 7377 draggable: false, 7378 7379 // @option autoPan: Boolean = false 7380 // Whether to pan the map when dragging this marker near its edge or not. 7381 autoPan: false, 7382 7383 // @option autoPanPadding: Point = Point(50, 50) 7384 // Distance (in pixels to the left/right and to the top/bottom) of the 7385 // map edge to start panning the map. 7386 autoPanPadding: [50, 50], 7387 7388 // @option autoPanSpeed: Number = 10 7389 // Number of pixels the map should pan by. 7390 autoPanSpeed: 10 7391 }, 7392 7393 /* @section 7394 * 7395 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 7396 */ 7397 7398 initialize: function (latlng, options) { 7399 setOptions(this, options); 7400 this._latlng = toLatLng(latlng); 7401 }, 7402 7403 onAdd: function (map) { 7404 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 7405 7406 if (this._zoomAnimated) { 7407 map.on('zoomanim', this._animateZoom, this); 7408 } 7409 7410 this._initIcon(); 7411 this.update(); 7412 }, 7413 7414 onRemove: function (map) { 7415 if (this.dragging && this.dragging.enabled()) { 7416 this.options.draggable = true; 7417 this.dragging.removeHooks(); 7418 } 7419 delete this.dragging; 7420 7421 if (this._zoomAnimated) { 7422 map.off('zoomanim', this._animateZoom, this); 7423 } 7424 7425 this._removeIcon(); 7426 this._removeShadow(); 7427 }, 7428 7429 getEvents: function () { 7430 return { 7431 zoom: this.update, 7432 viewreset: this.update 7433 }; 7434 }, 7435 7436 // @method getLatLng: LatLng 7437 // Returns the current geographical position of the marker. 7438 getLatLng: function () { 7439 return this._latlng; 7440 }, 7441 7442 // @method setLatLng(latlng: LatLng): this 7443 // Changes the marker position to the given point. 7444 setLatLng: function (latlng) { 7445 var oldLatLng = this._latlng; 7446 this._latlng = toLatLng(latlng); 7447 this.update(); 7448 7449 // @event move: Event 7450 // 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`. 7451 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7452 }, 7453 7454 // @method setZIndexOffset(offset: Number): this 7455 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 7456 setZIndexOffset: function (offset) { 7457 this.options.zIndexOffset = offset; 7458 return this.update(); 7459 }, 7460 7461 // @method setIcon(icon: Icon): this 7462 // Changes the marker icon. 7463 setIcon: function (icon) { 7464 7465 this.options.icon = icon; 7466 7467 if (this._map) { 7468 this._initIcon(); 7469 this.update(); 7470 } 7471 7472 if (this._popup) { 7473 this.bindPopup(this._popup, this._popup.options); 7474 } 7475 7476 return this; 7477 }, 7478 7479 getElement: function () { 7480 return this._icon; 7481 }, 7482 7483 update: function () { 7484 7485 if (this._icon && this._map) { 7486 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 7487 this._setPos(pos); 7488 } 7489 7490 return this; 7491 }, 7492 7493 _initIcon: function () { 7494 var options = this.options, 7495 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7496 7497 var icon = options.icon.createIcon(this._icon), 7498 addIcon = false; 7499 7500 // if we're not reusing the icon, remove the old one and init new one 7501 if (icon !== this._icon) { 7502 if (this._icon) { 7503 this._removeIcon(); 7504 } 7505 addIcon = true; 7506 7507 if (options.title) { 7508 icon.title = options.title; 7509 } 7510 7511 if (icon.tagName === 'IMG') { 7512 icon.alt = options.alt || ''; 7513 } 7514 } 7515 7516 addClass(icon, classToAdd); 7517 7518 if (options.keyboard) { 7519 icon.tabIndex = '0'; 7520 } 7521 7522 this._icon = icon; 7523 7524 if (options.riseOnHover) { 7525 this.on({ 7526 mouseover: this._bringToFront, 7527 mouseout: this._resetZIndex 7528 }); 7529 } 7530 7531 var newShadow = options.icon.createShadow(this._shadow), 7532 addShadow = false; 7533 7534 if (newShadow !== this._shadow) { 7535 this._removeShadow(); 7536 addShadow = true; 7537 } 7538 7539 if (newShadow) { 7540 addClass(newShadow, classToAdd); 7541 newShadow.alt = ''; 7542 } 7543 this._shadow = newShadow; 7544 7545 7546 if (options.opacity < 1) { 7547 this._updateOpacity(); 7548 } 7549 7550 7551 if (addIcon) { 7552 this.getPane().appendChild(this._icon); 7553 } 7554 this._initInteraction(); 7555 if (newShadow && addShadow) { 7556 this.getPane('shadowPane').appendChild(this._shadow); 7557 } 7558 }, 7559 7560 _removeIcon: function () { 7561 if (this.options.riseOnHover) { 7562 this.off({ 7563 mouseover: this._bringToFront, 7564 mouseout: this._resetZIndex 7565 }); 7566 } 7567 7568 remove(this._icon); 7569 this.removeInteractiveTarget(this._icon); 7570 7571 this._icon = null; 7572 }, 7573 7574 _removeShadow: function () { 7575 if (this._shadow) { 7576 remove(this._shadow); 7577 } 7578 this._shadow = null; 7579 }, 7580 7581 _setPos: function (pos) { 7582 setPosition(this._icon, pos); 7583 7584 if (this._shadow) { 7585 setPosition(this._shadow, pos); 7586 } 7587 7588 this._zIndex = pos.y + this.options.zIndexOffset; 7589 7590 this._resetZIndex(); 7591 }, 7592 7593 _updateZIndex: function (offset) { 7594 this._icon.style.zIndex = this._zIndex + offset; 7595 }, 7596 7597 _animateZoom: function (opt) { 7598 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 7599 7600 this._setPos(pos); 7601 }, 7602 7603 _initInteraction: function () { 7604 7605 if (!this.options.interactive) { return; } 7606 7607 addClass(this._icon, 'leaflet-interactive'); 7608 7609 this.addInteractiveTarget(this._icon); 7610 7611 if (MarkerDrag) { 7612 var draggable = this.options.draggable; 7613 if (this.dragging) { 7614 draggable = this.dragging.enabled(); 7615 this.dragging.disable(); 7616 } 7617 7618 this.dragging = new MarkerDrag(this); 7619 7620 if (draggable) { 7621 this.dragging.enable(); 7622 } 7623 } 7624 }, 7625 7626 // @method setOpacity(opacity: Number): this 7627 // Changes the opacity of the marker. 7628 setOpacity: function (opacity) { 7629 this.options.opacity = opacity; 7630 if (this._map) { 7631 this._updateOpacity(); 7632 } 7633 7634 return this; 7635 }, 7636 7637 _updateOpacity: function () { 7638 var opacity = this.options.opacity; 7639 7640 setOpacity(this._icon, opacity); 7641 7642 if (this._shadow) { 7643 setOpacity(this._shadow, opacity); 7644 } 7645 }, 7646 7647 _bringToFront: function () { 7648 this._updateZIndex(this.options.riseOffset); 7649 }, 7650 7651 _resetZIndex: function () { 7652 this._updateZIndex(0); 7653 }, 7654 7655 _getPopupAnchor: function () { 7656 return this.options.icon.options.popupAnchor; 7657 }, 7658 7659 _getTooltipAnchor: function () { 7660 return this.options.icon.options.tooltipAnchor; 7661 } 7662}); 7663 7664 7665// factory L.marker(latlng: LatLng, options? : Marker options) 7666 7667// @factory L.marker(latlng: LatLng, options? : Marker options) 7668// Instantiates a Marker object given a geographical point and optionally an options object. 7669function marker(latlng, options) { 7670 return new Marker(latlng, options); 7671} 7672 7673/* 7674 * @class Path 7675 * @aka L.Path 7676 * @inherits Interactive layer 7677 * 7678 * An abstract class that contains options and constants shared between vector 7679 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 7680 */ 7681 7682var Path = Layer.extend({ 7683 7684 // @section 7685 // @aka Path options 7686 options: { 7687 // @option stroke: Boolean = true 7688 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 7689 stroke: true, 7690 7691 // @option color: String = '#3388ff' 7692 // Stroke color 7693 color: '#3388ff', 7694 7695 // @option weight: Number = 3 7696 // Stroke width in pixels 7697 weight: 3, 7698 7699 // @option opacity: Number = 1.0 7700 // Stroke opacity 7701 opacity: 1, 7702 7703 // @option lineCap: String= 'round' 7704 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 7705 lineCap: 'round', 7706 7707 // @option lineJoin: String = 'round' 7708 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 7709 lineJoin: 'round', 7710 7711 // @option dashArray: String = null 7712 // 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). 7713 dashArray: null, 7714 7715 // @option dashOffset: String = null 7716 // 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). 7717 dashOffset: null, 7718 7719 // @option fill: Boolean = depends 7720 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 7721 fill: false, 7722 7723 // @option fillColor: String = * 7724 // Fill color. Defaults to the value of the [`color`](#path-color) option 7725 fillColor: null, 7726 7727 // @option fillOpacity: Number = 0.2 7728 // Fill opacity. 7729 fillOpacity: 0.2, 7730 7731 // @option fillRule: String = 'evenodd' 7732 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 7733 fillRule: 'evenodd', 7734 7735 // className: '', 7736 7737 // Option inherited from "Interactive layer" abstract class 7738 interactive: true, 7739 7740 // @option bubblingMouseEvents: Boolean = true 7741 // When `true`, a mouse event on this path will trigger the same event on the map 7742 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7743 bubblingMouseEvents: true 7744 }, 7745 7746 beforeAdd: function (map) { 7747 // Renderer is set here because we need to call renderer.getEvents 7748 // before this.getEvents. 7749 this._renderer = map.getRenderer(this); 7750 }, 7751 7752 onAdd: function () { 7753 this._renderer._initPath(this); 7754 this._reset(); 7755 this._renderer._addPath(this); 7756 }, 7757 7758 onRemove: function () { 7759 this._renderer._removePath(this); 7760 }, 7761 7762 // @method redraw(): this 7763 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 7764 redraw: function () { 7765 if (this._map) { 7766 this._renderer._updatePath(this); 7767 } 7768 return this; 7769 }, 7770 7771 // @method setStyle(style: Path options): this 7772 // Changes the appearance of a Path based on the options in the `Path options` object. 7773 setStyle: function (style) { 7774 setOptions(this, style); 7775 if (this._renderer) { 7776 this._renderer._updateStyle(this); 7777 } 7778 return this; 7779 }, 7780 7781 // @method bringToFront(): this 7782 // Brings the layer to the top of all path layers. 7783 bringToFront: function () { 7784 if (this._renderer) { 7785 this._renderer._bringToFront(this); 7786 } 7787 return this; 7788 }, 7789 7790 // @method bringToBack(): this 7791 // Brings the layer to the bottom of all path layers. 7792 bringToBack: function () { 7793 if (this._renderer) { 7794 this._renderer._bringToBack(this); 7795 } 7796 return this; 7797 }, 7798 7799 getElement: function () { 7800 return this._path; 7801 }, 7802 7803 _reset: function () { 7804 // defined in child classes 7805 this._project(); 7806 this._update(); 7807 }, 7808 7809 _clickTolerance: function () { 7810 // used when doing hit detection for Canvas layers 7811 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance; 7812 } 7813}); 7814 7815/* 7816 * @class CircleMarker 7817 * @aka L.CircleMarker 7818 * @inherits Path 7819 * 7820 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 7821 */ 7822 7823var CircleMarker = Path.extend({ 7824 7825 // @section 7826 // @aka CircleMarker options 7827 options: { 7828 fill: true, 7829 7830 // @option radius: Number = 10 7831 // Radius of the circle marker, in pixels 7832 radius: 10 7833 }, 7834 7835 initialize: function (latlng, options) { 7836 setOptions(this, options); 7837 this._latlng = toLatLng(latlng); 7838 this._radius = this.options.radius; 7839 }, 7840 7841 // @method setLatLng(latLng: LatLng): this 7842 // Sets the position of a circle marker to a new location. 7843 setLatLng: function (latlng) { 7844 this._latlng = toLatLng(latlng); 7845 this.redraw(); 7846 return this.fire('move', {latlng: this._latlng}); 7847 }, 7848 7849 // @method getLatLng(): LatLng 7850 // Returns the current geographical position of the circle marker 7851 getLatLng: function () { 7852 return this._latlng; 7853 }, 7854 7855 // @method setRadius(radius: Number): this 7856 // Sets the radius of a circle marker. Units are in pixels. 7857 setRadius: function (radius) { 7858 this.options.radius = this._radius = radius; 7859 return this.redraw(); 7860 }, 7861 7862 // @method getRadius(): Number 7863 // Returns the current radius of the circle 7864 getRadius: function () { 7865 return this._radius; 7866 }, 7867 7868 setStyle : function (options) { 7869 var radius = options && options.radius || this._radius; 7870 Path.prototype.setStyle.call(this, options); 7871 this.setRadius(radius); 7872 return this; 7873 }, 7874 7875 _project: function () { 7876 this._point = this._map.latLngToLayerPoint(this._latlng); 7877 this._updateBounds(); 7878 }, 7879 7880 _updateBounds: function () { 7881 var r = this._radius, 7882 r2 = this._radiusY || r, 7883 w = this._clickTolerance(), 7884 p = [r + w, r2 + w]; 7885 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 7886 }, 7887 7888 _update: function () { 7889 if (this._map) { 7890 this._updatePath(); 7891 } 7892 }, 7893 7894 _updatePath: function () { 7895 this._renderer._updateCircle(this); 7896 }, 7897 7898 _empty: function () { 7899 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 7900 }, 7901 7902 // Needed by the `Canvas` renderer for interactivity 7903 _containsPoint: function (p) { 7904 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 7905 } 7906}); 7907 7908 7909// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 7910// Instantiates a circle marker object given a geographical point, and an optional options object. 7911function circleMarker(latlng, options) { 7912 return new CircleMarker(latlng, options); 7913} 7914 7915/* 7916 * @class Circle 7917 * @aka L.Circle 7918 * @inherits CircleMarker 7919 * 7920 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 7921 * 7922 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 7923 * 7924 * @example 7925 * 7926 * ```js 7927 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 7928 * ``` 7929 */ 7930 7931var Circle = CircleMarker.extend({ 7932 7933 initialize: function (latlng, options, legacyOptions) { 7934 if (typeof options === 'number') { 7935 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 7936 options = extend({}, legacyOptions, {radius: options}); 7937 } 7938 setOptions(this, options); 7939 this._latlng = toLatLng(latlng); 7940 7941 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 7942 7943 // @section 7944 // @aka Circle options 7945 // @option radius: Number; Radius of the circle, in meters. 7946 this._mRadius = this.options.radius; 7947 }, 7948 7949 // @method setRadius(radius: Number): this 7950 // Sets the radius of a circle. Units are in meters. 7951 setRadius: function (radius) { 7952 this._mRadius = radius; 7953 return this.redraw(); 7954 }, 7955 7956 // @method getRadius(): Number 7957 // Returns the current radius of a circle. Units are in meters. 7958 getRadius: function () { 7959 return this._mRadius; 7960 }, 7961 7962 // @method getBounds(): LatLngBounds 7963 // Returns the `LatLngBounds` of the path. 7964 getBounds: function () { 7965 var half = [this._radius, this._radiusY || this._radius]; 7966 7967 return new LatLngBounds( 7968 this._map.layerPointToLatLng(this._point.subtract(half)), 7969 this._map.layerPointToLatLng(this._point.add(half))); 7970 }, 7971 7972 setStyle: Path.prototype.setStyle, 7973 7974 _project: function () { 7975 7976 var lng = this._latlng.lng, 7977 lat = this._latlng.lat, 7978 map = this._map, 7979 crs = map.options.crs; 7980 7981 if (crs.distance === Earth.distance) { 7982 var d = Math.PI / 180, 7983 latR = (this._mRadius / Earth.R) / d, 7984 top = map.project([lat + latR, lng]), 7985 bottom = map.project([lat - latR, lng]), 7986 p = top.add(bottom).divideBy(2), 7987 lat2 = map.unproject(p).lat, 7988 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 7989 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 7990 7991 if (isNaN(lngR) || lngR === 0) { 7992 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 7993 } 7994 7995 this._point = p.subtract(map.getPixelOrigin()); 7996 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 7997 this._radiusY = p.y - top.y; 7998 7999 } else { 8000 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8001 8002 this._point = map.latLngToLayerPoint(this._latlng); 8003 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8004 } 8005 8006 this._updateBounds(); 8007 } 8008}); 8009 8010// @factory L.circle(latlng: LatLng, options?: Circle options) 8011// Instantiates a circle object given a geographical point, and an options object 8012// which contains the circle radius. 8013// @alternative 8014// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8015// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8016// Do not use in new applications or plugins. 8017function circle(latlng, options, legacyOptions) { 8018 return new Circle(latlng, options, legacyOptions); 8019} 8020 8021/* 8022 * @class Polyline 8023 * @aka L.Polyline 8024 * @inherits Path 8025 * 8026 * A class for drawing polyline overlays on a map. Extends `Path`. 8027 * 8028 * @example 8029 * 8030 * ```js 8031 * // create a red polyline from an array of LatLng points 8032 * var latlngs = [ 8033 * [45.51, -122.68], 8034 * [37.77, -122.43], 8035 * [34.04, -118.2] 8036 * ]; 8037 * 8038 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8039 * 8040 * // zoom the map to the polyline 8041 * map.fitBounds(polyline.getBounds()); 8042 * ``` 8043 * 8044 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8045 * 8046 * ```js 8047 * // create a red polyline from an array of arrays of LatLng points 8048 * var latlngs = [ 8049 * [[45.51, -122.68], 8050 * [37.77, -122.43], 8051 * [34.04, -118.2]], 8052 * [[40.78, -73.91], 8053 * [41.83, -87.62], 8054 * [32.76, -96.72]] 8055 * ]; 8056 * ``` 8057 */ 8058 8059 8060var Polyline = Path.extend({ 8061 8062 // @section 8063 // @aka Polyline options 8064 options: { 8065 // @option smoothFactor: Number = 1.0 8066 // How much to simplify the polyline on each zoom level. More means 8067 // better performance and smoother look, and less means more accurate representation. 8068 smoothFactor: 1.0, 8069 8070 // @option noClip: Boolean = false 8071 // Disable polyline clipping. 8072 noClip: false 8073 }, 8074 8075 initialize: function (latlngs, options) { 8076 setOptions(this, options); 8077 this._setLatLngs(latlngs); 8078 }, 8079 8080 // @method getLatLngs(): LatLng[] 8081 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8082 getLatLngs: function () { 8083 return this._latlngs; 8084 }, 8085 8086 // @method setLatLngs(latlngs: LatLng[]): this 8087 // Replaces all the points in the polyline with the given array of geographical points. 8088 setLatLngs: function (latlngs) { 8089 this._setLatLngs(latlngs); 8090 return this.redraw(); 8091 }, 8092 8093 // @method isEmpty(): Boolean 8094 // Returns `true` if the Polyline has no LatLngs. 8095 isEmpty: function () { 8096 return !this._latlngs.length; 8097 }, 8098 8099 // @method closestLayerPoint(p: Point): Point 8100 // Returns the point closest to `p` on the Polyline. 8101 closestLayerPoint: function (p) { 8102 var minDistance = Infinity, 8103 minPoint = null, 8104 closest = _sqClosestPointOnSegment, 8105 p1, p2; 8106 8107 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8108 var points = this._parts[j]; 8109 8110 for (var i = 1, len = points.length; i < len; i++) { 8111 p1 = points[i - 1]; 8112 p2 = points[i]; 8113 8114 var sqDist = closest(p, p1, p2, true); 8115 8116 if (sqDist < minDistance) { 8117 minDistance = sqDist; 8118 minPoint = closest(p, p1, p2); 8119 } 8120 } 8121 } 8122 if (minPoint) { 8123 minPoint.distance = Math.sqrt(minDistance); 8124 } 8125 return minPoint; 8126 }, 8127 8128 // @method getCenter(): LatLng 8129 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline. 8130 getCenter: function () { 8131 // throws error when not yet added to map as this center calculation requires projected coordinates 8132 if (!this._map) { 8133 throw new Error('Must add layer to map before using getCenter()'); 8134 } 8135 8136 var i, halfDist, segDist, dist, p1, p2, ratio, 8137 points = this._rings[0], 8138 len = points.length; 8139 8140 if (!len) { return null; } 8141 8142 // polyline centroid algorithm; only uses the first ring if there are multiple 8143 8144 for (i = 0, halfDist = 0; i < len - 1; i++) { 8145 halfDist += points[i].distanceTo(points[i + 1]) / 2; 8146 } 8147 8148 // The line is so small in the current view that all points are on the same pixel. 8149 if (halfDist === 0) { 8150 return this._map.layerPointToLatLng(points[0]); 8151 } 8152 8153 for (i = 0, dist = 0; i < len - 1; i++) { 8154 p1 = points[i]; 8155 p2 = points[i + 1]; 8156 segDist = p1.distanceTo(p2); 8157 dist += segDist; 8158 8159 if (dist > halfDist) { 8160 ratio = (dist - halfDist) / segDist; 8161 return this._map.layerPointToLatLng([ 8162 p2.x - ratio * (p2.x - p1.x), 8163 p2.y - ratio * (p2.y - p1.y) 8164 ]); 8165 } 8166 } 8167 }, 8168 8169 // @method getBounds(): LatLngBounds 8170 // Returns the `LatLngBounds` of the path. 8171 getBounds: function () { 8172 return this._bounds; 8173 }, 8174 8175 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this 8176 // Adds a given point to the polyline. By default, adds to the first ring of 8177 // the polyline in case of a multi-polyline, but can be overridden by passing 8178 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8179 addLatLng: function (latlng, latlngs) { 8180 latlngs = latlngs || this._defaultShape(); 8181 latlng = toLatLng(latlng); 8182 latlngs.push(latlng); 8183 this._bounds.extend(latlng); 8184 return this.redraw(); 8185 }, 8186 8187 _setLatLngs: function (latlngs) { 8188 this._bounds = new LatLngBounds(); 8189 this._latlngs = this._convertLatLngs(latlngs); 8190 }, 8191 8192 _defaultShape: function () { 8193 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8194 }, 8195 8196 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8197 _convertLatLngs: function (latlngs) { 8198 var result = [], 8199 flat = isFlat(latlngs); 8200 8201 for (var i = 0, len = latlngs.length; i < len; i++) { 8202 if (flat) { 8203 result[i] = toLatLng(latlngs[i]); 8204 this._bounds.extend(result[i]); 8205 } else { 8206 result[i] = this._convertLatLngs(latlngs[i]); 8207 } 8208 } 8209 8210 return result; 8211 }, 8212 8213 _project: function () { 8214 var pxBounds = new Bounds(); 8215 this._rings = []; 8216 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8217 8218 var w = this._clickTolerance(), 8219 p = new Point(w, w); 8220 8221 if (this._bounds.isValid() && pxBounds.isValid()) { 8222 pxBounds.min._subtract(p); 8223 pxBounds.max._add(p); 8224 this._pxBounds = pxBounds; 8225 } 8226 }, 8227 8228 // recursively turns latlngs into a set of rings with projected coordinates 8229 _projectLatlngs: function (latlngs, result, projectedBounds) { 8230 var flat = latlngs[0] instanceof LatLng, 8231 len = latlngs.length, 8232 i, ring; 8233 8234 if (flat) { 8235 ring = []; 8236 for (i = 0; i < len; i++) { 8237 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8238 projectedBounds.extend(ring[i]); 8239 } 8240 result.push(ring); 8241 } else { 8242 for (i = 0; i < len; i++) { 8243 this._projectLatlngs(latlngs[i], result, projectedBounds); 8244 } 8245 } 8246 }, 8247 8248 // clip polyline by renderer bounds so that we have less to render for performance 8249 _clipPoints: function () { 8250 var bounds = this._renderer._bounds; 8251 8252 this._parts = []; 8253 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8254 return; 8255 } 8256 8257 if (this.options.noClip) { 8258 this._parts = this._rings; 8259 return; 8260 } 8261 8262 var parts = this._parts, 8263 i, j, k, len, len2, segment, points; 8264 8265 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8266 points = this._rings[i]; 8267 8268 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8269 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8270 8271 if (!segment) { continue; } 8272 8273 parts[k] = parts[k] || []; 8274 parts[k].push(segment[0]); 8275 8276 // if segment goes out of screen, or it's the last one, it's the end of the line part 8277 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8278 parts[k].push(segment[1]); 8279 k++; 8280 } 8281 } 8282 } 8283 }, 8284 8285 // simplify each clipped part of the polyline for performance 8286 _simplifyPoints: function () { 8287 var parts = this._parts, 8288 tolerance = this.options.smoothFactor; 8289 8290 for (var i = 0, len = parts.length; i < len; i++) { 8291 parts[i] = simplify(parts[i], tolerance); 8292 } 8293 }, 8294 8295 _update: function () { 8296 if (!this._map) { return; } 8297 8298 this._clipPoints(); 8299 this._simplifyPoints(); 8300 this._updatePath(); 8301 }, 8302 8303 _updatePath: function () { 8304 this._renderer._updatePoly(this); 8305 }, 8306 8307 // Needed by the `Canvas` renderer for interactivity 8308 _containsPoint: function (p, closed) { 8309 var i, j, k, len, len2, part, 8310 w = this._clickTolerance(); 8311 8312 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8313 8314 // hit detection for polylines 8315 for (i = 0, len = this._parts.length; i < len; i++) { 8316 part = this._parts[i]; 8317 8318 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8319 if (!closed && (j === 0)) { continue; } 8320 8321 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8322 return true; 8323 } 8324 } 8325 } 8326 return false; 8327 } 8328}); 8329 8330// @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8331// Instantiates a polyline object given an array of geographical points and 8332// optionally an options object. You can create a `Polyline` object with 8333// multiple separate lines (`MultiPolyline`) by passing an array of arrays 8334// of geographic points. 8335function polyline(latlngs, options) { 8336 return new Polyline(latlngs, options); 8337} 8338 8339// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8340Polyline._flat = _flat; 8341 8342/* 8343 * @class Polygon 8344 * @aka L.Polygon 8345 * @inherits Polyline 8346 * 8347 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8348 * 8349 * 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. 8350 * 8351 * 8352 * @example 8353 * 8354 * ```js 8355 * // create a red polygon from an array of LatLng points 8356 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 8357 * 8358 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8359 * 8360 * // zoom the map to the polygon 8361 * map.fitBounds(polygon.getBounds()); 8362 * ``` 8363 * 8364 * 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: 8365 * 8366 * ```js 8367 * var latlngs = [ 8368 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8369 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8370 * ]; 8371 * ``` 8372 * 8373 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8374 * 8375 * ```js 8376 * var latlngs = [ 8377 * [ // first polygon 8378 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8379 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8380 * ], 8381 * [ // second polygon 8382 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 8383 * ] 8384 * ]; 8385 * ``` 8386 */ 8387 8388var Polygon = Polyline.extend({ 8389 8390 options: { 8391 fill: true 8392 }, 8393 8394 isEmpty: function () { 8395 return !this._latlngs.length || !this._latlngs[0].length; 8396 }, 8397 8398 getCenter: function () { 8399 // throws error when not yet added to map as this center calculation requires projected coordinates 8400 if (!this._map) { 8401 throw new Error('Must add layer to map before using getCenter()'); 8402 } 8403 8404 var i, j, p1, p2, f, area, x, y, center, 8405 points = this._rings[0], 8406 len = points.length; 8407 8408 if (!len) { return null; } 8409 8410 // polygon centroid algorithm; only uses the first ring if there are multiple 8411 8412 area = x = y = 0; 8413 8414 for (i = 0, j = len - 1; i < len; j = i++) { 8415 p1 = points[i]; 8416 p2 = points[j]; 8417 8418 f = p1.y * p2.x - p2.y * p1.x; 8419 x += (p1.x + p2.x) * f; 8420 y += (p1.y + p2.y) * f; 8421 area += f * 3; 8422 } 8423 8424 if (area === 0) { 8425 // Polygon is so small that all points are on same pixel. 8426 center = points[0]; 8427 } else { 8428 center = [x / area, y / area]; 8429 } 8430 return this._map.layerPointToLatLng(center); 8431 }, 8432 8433 _convertLatLngs: function (latlngs) { 8434 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 8435 len = result.length; 8436 8437 // remove last point if it equals first one 8438 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 8439 result.pop(); 8440 } 8441 return result; 8442 }, 8443 8444 _setLatLngs: function (latlngs) { 8445 Polyline.prototype._setLatLngs.call(this, latlngs); 8446 if (isFlat(this._latlngs)) { 8447 this._latlngs = [this._latlngs]; 8448 } 8449 }, 8450 8451 _defaultShape: function () { 8452 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8453 }, 8454 8455 _clipPoints: function () { 8456 // polygons need a different clipping algorithm so we redefine that 8457 8458 var bounds = this._renderer._bounds, 8459 w = this.options.weight, 8460 p = new Point(w, w); 8461 8462 // increase clip padding by stroke width to avoid stroke on clip edges 8463 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8464 8465 this._parts = []; 8466 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8467 return; 8468 } 8469 8470 if (this.options.noClip) { 8471 this._parts = this._rings; 8472 return; 8473 } 8474 8475 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8476 clipped = clipPolygon(this._rings[i], bounds, true); 8477 if (clipped.length) { 8478 this._parts.push(clipped); 8479 } 8480 } 8481 }, 8482 8483 _updatePath: function () { 8484 this._renderer._updatePoly(this, true); 8485 }, 8486 8487 // Needed by the `Canvas` renderer for interactivity 8488 _containsPoint: function (p) { 8489 var inside = false, 8490 part, p1, p2, i, j, k, len, len2; 8491 8492 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8493 8494 // ray casting algorithm for detecting if point is in polygon 8495 for (i = 0, len = this._parts.length; i < len; i++) { 8496 part = this._parts[i]; 8497 8498 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8499 p1 = part[j]; 8500 p2 = part[k]; 8501 8502 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)) { 8503 inside = !inside; 8504 } 8505 } 8506 } 8507 8508 // also check if it's on polygon stroke 8509 return inside || Polyline.prototype._containsPoint.call(this, p, true); 8510 } 8511 8512}); 8513 8514 8515// @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8516function polygon(latlngs, options) { 8517 return new Polygon(latlngs, options); 8518} 8519 8520/* 8521 * @class GeoJSON 8522 * @aka L.GeoJSON 8523 * @inherits FeatureGroup 8524 * 8525 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 8526 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 8527 * 8528 * @example 8529 * 8530 * ```js 8531 * L.geoJSON(data, { 8532 * style: function (feature) { 8533 * return {color: feature.properties.color}; 8534 * } 8535 * }).bindPopup(function (layer) { 8536 * return layer.feature.properties.description; 8537 * }).addTo(map); 8538 * ``` 8539 */ 8540 8541var GeoJSON = FeatureGroup.extend({ 8542 8543 /* @section 8544 * @aka GeoJSON options 8545 * 8546 * @option pointToLayer: Function = * 8547 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 8548 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 8549 * The default is to spawn a default `Marker`: 8550 * ```js 8551 * function(geoJsonPoint, latlng) { 8552 * return L.marker(latlng); 8553 * } 8554 * ``` 8555 * 8556 * @option style: Function = * 8557 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8558 * called internally when data is added. 8559 * The default value is to not override any defaults: 8560 * ```js 8561 * function (geoJsonFeature) { 8562 * return {} 8563 * } 8564 * ``` 8565 * 8566 * @option onEachFeature: Function = * 8567 * A `Function` that will be called once for each created `Feature`, after it has 8568 * been created and styled. Useful for attaching events and popups to features. 8569 * The default is to do nothing with the newly created layers: 8570 * ```js 8571 * function (feature, layer) {} 8572 * ``` 8573 * 8574 * @option filter: Function = * 8575 * A `Function` that will be used to decide whether to include a feature or not. 8576 * The default is to include all features: 8577 * ```js 8578 * function (geoJsonFeature) { 8579 * return true; 8580 * } 8581 * ``` 8582 * Note: dynamically changing the `filter` option will have effect only on newly 8583 * added data. It will _not_ re-evaluate already included features. 8584 * 8585 * @option coordsToLatLng: Function = * 8586 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 8587 * The default is the `coordsToLatLng` static method. 8588 */ 8589 8590 initialize: function (geojson, options) { 8591 setOptions(this, options); 8592 8593 this._layers = {}; 8594 8595 if (geojson) { 8596 this.addData(geojson); 8597 } 8598 }, 8599 8600 // @method addData( <GeoJSON> data ): this 8601 // Adds a GeoJSON object to the layer. 8602 addData: function (geojson) { 8603 var features = isArray(geojson) ? geojson : geojson.features, 8604 i, len, feature; 8605 8606 if (features) { 8607 for (i = 0, len = features.length; i < len; i++) { 8608 // only add this if geometry or geometries are set and not null 8609 feature = features[i]; 8610 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 8611 this.addData(feature); 8612 } 8613 } 8614 return this; 8615 } 8616 8617 var options = this.options; 8618 8619 if (options.filter && !options.filter(geojson)) { return this; } 8620 8621 var layer = geometryToLayer(geojson, options); 8622 if (!layer) { 8623 return this; 8624 } 8625 layer.feature = asFeature(geojson); 8626 8627 layer.defaultOptions = layer.options; 8628 this.resetStyle(layer); 8629 8630 if (options.onEachFeature) { 8631 options.onEachFeature(geojson, layer); 8632 } 8633 8634 return this.addLayer(layer); 8635 }, 8636 8637 // @method resetStyle( <Path> layer ): this 8638 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 8639 resetStyle: function (layer) { 8640 // reset any custom styles 8641 layer.options = extend({}, layer.defaultOptions); 8642 this._setLayerStyle(layer, this.options.style); 8643 return this; 8644 }, 8645 8646 // @method setStyle( <Function> style ): this 8647 // Changes styles of GeoJSON vector layers with the given style function. 8648 setStyle: function (style) { 8649 return this.eachLayer(function (layer) { 8650 this._setLayerStyle(layer, style); 8651 }, this); 8652 }, 8653 8654 _setLayerStyle: function (layer, style) { 8655 if (typeof style === 'function') { 8656 style = style(layer.feature); 8657 } 8658 if (layer.setStyle) { 8659 layer.setStyle(style); 8660 } 8661 } 8662}); 8663 8664// @section 8665// There are several static functions which can be called without instantiating L.GeoJSON: 8666 8667// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 8668// Creates a `Layer` from a given GeoJSON feature. Can use a custom 8669// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 8670// functions if provided as options. 8671function geometryToLayer(geojson, options) { 8672 8673 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 8674 coords = geometry ? geometry.coordinates : null, 8675 layers = [], 8676 pointToLayer = options && options.pointToLayer, 8677 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 8678 latlng, latlngs, i, len; 8679 8680 if (!coords && !geometry) { 8681 return null; 8682 } 8683 8684 switch (geometry.type) { 8685 case 'Point': 8686 latlng = _coordsToLatLng(coords); 8687 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng); 8688 8689 case 'MultiPoint': 8690 for (i = 0, len = coords.length; i < len; i++) { 8691 latlng = _coordsToLatLng(coords[i]); 8692 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng)); 8693 } 8694 return new FeatureGroup(layers); 8695 8696 case 'LineString': 8697 case 'MultiLineString': 8698 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 8699 return new Polyline(latlngs, options); 8700 8701 case 'Polygon': 8702 case 'MultiPolygon': 8703 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 8704 return new Polygon(latlngs, options); 8705 8706 case 'GeometryCollection': 8707 for (i = 0, len = geometry.geometries.length; i < len; i++) { 8708 var layer = geometryToLayer({ 8709 geometry: geometry.geometries[i], 8710 type: 'Feature', 8711 properties: geojson.properties 8712 }, options); 8713 8714 if (layer) { 8715 layers.push(layer); 8716 } 8717 } 8718 return new FeatureGroup(layers); 8719 8720 default: 8721 throw new Error('Invalid GeoJSON object.'); 8722 } 8723} 8724 8725// @function coordsToLatLng(coords: Array): LatLng 8726// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 8727// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 8728function coordsToLatLng(coords) {
vendor: 12,744 bytes, lines 8729-9191
8729 return new LatLng(coords[1], coords[0], coords[2]); 8730} 8731 8732// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 8733// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 8734// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 8735// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 8736function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 8737 var latlngs = []; 8738 8739 for (var i = 0, len = coords.length, latlng; i < len; i++) { 8740 latlng = levelsDeep ? 8741 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 8742 (_coordsToLatLng || coordsToLatLng)(coords[i]); 8743 8744 latlngs.push(latlng); 8745 } 8746 8747 return latlngs; 8748} 8749 8750// @function latLngToCoords(latlng: LatLng, precision?: Number): Array 8751// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 8752function latLngToCoords(latlng, precision) { 8753 precision = typeof precision === 'number' ? precision : 6; 8754 return latlng.alt !== undefined ? 8755 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 8756 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 8757} 8758 8759// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array 8760// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 8761// `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. 8762function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 8763 var coords = []; 8764 8765 for (var i = 0, len = latlngs.length; i < len; i++) { 8766 coords.push(levelsDeep ? 8767 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : 8768 latLngToCoords(latlngs[i], precision)); 8769 } 8770 8771 if (!levelsDeep && closed) { 8772 coords.push(coords[0]); 8773 } 8774 8775 return coords; 8776} 8777 8778function getFeature(layer, newGeometry) { 8779 return layer.feature ? 8780 extend({}, layer.feature, {geometry: newGeometry}) : 8781 asFeature(newGeometry); 8782} 8783 8784// @function asFeature(geojson: Object): Object 8785// Normalize GeoJSON geometries/features into GeoJSON features. 8786function asFeature(geojson) { 8787 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 8788 return geojson; 8789 } 8790 8791 return { 8792 type: 'Feature', 8793 properties: {}, 8794 geometry: geojson 8795 }; 8796} 8797 8798var PointToGeoJSON = { 8799 toGeoJSON: function (precision) { 8800 return getFeature(this, { 8801 type: 'Point', 8802 coordinates: latLngToCoords(this.getLatLng(), precision) 8803 }); 8804 } 8805}; 8806 8807// @namespace Marker 8808// @method toGeoJSON(): Object 8809// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 8810Marker.include(PointToGeoJSON); 8811 8812// @namespace CircleMarker 8813// @method toGeoJSON(): Object 8814// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 8815Circle.include(PointToGeoJSON); 8816CircleMarker.include(PointToGeoJSON); 8817 8818 8819// @namespace Polyline 8820// @method toGeoJSON(): Object 8821// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 8822Polyline.include({ 8823 toGeoJSON: function (precision) { 8824 var multi = !isFlat(this._latlngs); 8825 8826 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 8827 8828 return getFeature(this, { 8829 type: (multi ? 'Multi' : '') + 'LineString', 8830 coordinates: coords 8831 }); 8832 } 8833}); 8834 8835// @namespace Polygon 8836// @method toGeoJSON(): Object 8837// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 8838Polygon.include({ 8839 toGeoJSON: function (precision) { 8840 var holes = !isFlat(this._latlngs), 8841 multi = holes && !isFlat(this._latlngs[0]); 8842 8843 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 8844 8845 if (!holes) { 8846 coords = [coords]; 8847 } 8848 8849 return getFeature(this, { 8850 type: (multi ? 'Multi' : '') + 'Polygon', 8851 coordinates: coords 8852 }); 8853 } 8854}); 8855 8856 8857// @namespace LayerGroup 8858LayerGroup.include({ 8859 toMultiPoint: function (precision) { 8860 var coords = []; 8861 8862 this.eachLayer(function (layer) { 8863 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 8864 }); 8865 8866 return getFeature(this, { 8867 type: 'MultiPoint', 8868 coordinates: coords 8869 }); 8870 }, 8871 8872 // @method toGeoJSON(): Object 8873 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 8874 toGeoJSON: function (precision) { 8875 8876 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 8877 8878 if (type === 'MultiPoint') { 8879 return this.toMultiPoint(precision); 8880 } 8881 8882 var isGeometryCollection = type === 'GeometryCollection', 8883 jsons = []; 8884 8885 this.eachLayer(function (layer) { 8886 if (layer.toGeoJSON) { 8887 var json = layer.toGeoJSON(precision); 8888 if (isGeometryCollection) { 8889 jsons.push(json.geometry); 8890 } else { 8891 var feature = asFeature(json); 8892 // Squash nested feature collections 8893 if (feature.type === 'FeatureCollection') { 8894 jsons.push.apply(jsons, feature.features); 8895 } else { 8896 jsons.push(feature); 8897 } 8898 } 8899 } 8900 }); 8901 8902 if (isGeometryCollection) { 8903 return getFeature(this, { 8904 geometries: jsons, 8905 type: 'GeometryCollection' 8906 }); 8907 } 8908 8909 return { 8910 type: 'FeatureCollection', 8911 features: jsons 8912 }; 8913 } 8914}); 8915 8916// @namespace GeoJSON 8917// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 8918// Creates a GeoJSON layer. Optionally accepts an object in 8919// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 8920// (you can alternatively add it later with `addData` method) and an `options` object. 8921function geoJSON(geojson, options) { 8922 return new GeoJSON(geojson, options); 8923} 8924 8925// Backward compatibility. 8926var geoJson = geoJSON; 8927 8928/* 8929 * @class ImageOverlay 8930 * @aka L.ImageOverlay 8931 * @inherits Interactive layer 8932 * 8933 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 8934 * 8935 * @example 8936 * 8937 * ```js 8938 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 8939 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 8940 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 8941 * ``` 8942 */ 8943 8944var ImageOverlay = Layer.extend({ 8945 8946 // @section 8947 // @aka ImageOverlay options 8948 options: { 8949 // @option opacity: Number = 1.0 8950 // The opacity of the image overlay. 8951 opacity: 1, 8952 8953 // @option alt: String = '' 8954 // Text for the `alt` attribute of the image (useful for accessibility). 8955 alt: '', 8956 8957 // @option interactive: Boolean = false 8958 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 8959 interactive: false, 8960 8961 // @option crossOrigin: Boolean|String = false 8962 // Whether the crossOrigin attribute will be added to the image. 8963 // 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. 8964 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 8965 crossOrigin: false, 8966 8967 // @option errorOverlayUrl: String = '' 8968 // URL to the overlay image to show in place of the overlay that failed to load. 8969 errorOverlayUrl: '', 8970 8971 // @option zIndex: Number = 1 8972 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 8973 zIndex: 1, 8974 8975 // @option className: String = '' 8976 // A custom class name to assign to the image. Empty by default. 8977 className: '' 8978 }, 8979 8980 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 8981 this._url = url; 8982 this._bounds = toLatLngBounds(bounds); 8983 8984 setOptions(this, options); 8985 }, 8986 8987 onAdd: function () { 8988 if (!this._image) { 8989 this._initImage(); 8990 8991 if (this.options.opacity < 1) { 8992 this._updateOpacity(); 8993 } 8994 } 8995 8996 if (this.options.interactive) { 8997 addClass(this._image, 'leaflet-interactive'); 8998 this.addInteractiveTarget(this._image); 8999 } 9000 9001 this.getPane().appendChild(this._image); 9002 this._reset(); 9003 }, 9004 9005 onRemove: function () { 9006 remove(this._image); 9007 if (this.options.interactive) { 9008 this.removeInteractiveTarget(this._image); 9009 } 9010 }, 9011 9012 // @method setOpacity(opacity: Number): this 9013 // Sets the opacity of the overlay. 9014 setOpacity: function (opacity) { 9015 this.options.opacity = opacity; 9016 9017 if (this._image) { 9018 this._updateOpacity(); 9019 } 9020 return this; 9021 }, 9022 9023 setStyle: function (styleOpts) { 9024 if (styleOpts.opacity) { 9025 this.setOpacity(styleOpts.opacity); 9026 } 9027 return this; 9028 }, 9029 9030 // @method bringToFront(): this 9031 // Brings the layer to the top of all overlays. 9032 bringToFront: function () { 9033 if (this._map) { 9034 toFront(this._image); 9035 } 9036 return this; 9037 }, 9038 9039 // @method bringToBack(): this 9040 // Brings the layer to the bottom of all overlays. 9041 bringToBack: function () { 9042 if (this._map) { 9043 toBack(this._image); 9044 } 9045 return this; 9046 }, 9047 9048 // @method setUrl(url: String): this 9049 // Changes the URL of the image. 9050 setUrl: function (url) { 9051 this._url = url; 9052 9053 if (this._image) { 9054 this._image.src = url; 9055 } 9056 return this; 9057 }, 9058 9059 // @method setBounds(bounds: LatLngBounds): this 9060 // Update the bounds that this ImageOverlay covers 9061 setBounds: function (bounds) { 9062 this._bounds = toLatLngBounds(bounds); 9063 9064 if (this._map) { 9065 this._reset(); 9066 } 9067 return this; 9068 }, 9069 9070 getEvents: function () { 9071 var events = { 9072 zoom: this._reset, 9073 viewreset: this._reset 9074 }; 9075 9076 if (this._zoomAnimated) { 9077 events.zoomanim = this._animateZoom; 9078 } 9079 9080 return events; 9081 }, 9082 9083 // @method setZIndex(value: Number): this 9084 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9085 setZIndex: function (value) { 9086 this.options.zIndex = value; 9087 this._updateZIndex(); 9088 return this; 9089 }, 9090 9091 // @method getBounds(): LatLngBounds 9092 // Get the bounds that this ImageOverlay covers 9093 getBounds: function () { 9094 return this._bounds; 9095 }, 9096 9097 // @method getElement(): HTMLElement 9098 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9099 // used by this overlay. 9100 getElement: function () { 9101 return this._image; 9102 }, 9103 9104 _initImage: function () { 9105 var wasElementSupplied = this._url.tagName === 'IMG'; 9106 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9107 9108 addClass(img, 'leaflet-image-layer'); 9109 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9110 if (this.options.className) { addClass(img, this.options.className); } 9111 9112 img.onselectstart = falseFn; 9113 img.onmousemove = falseFn; 9114 9115 // @event load: Event 9116 // Fired when the ImageOverlay layer has loaded its image 9117 img.onload = bind(this.fire, this, 'load'); 9118 img.onerror = bind(this._overlayOnError, this, 'error'); 9119 9120 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9121 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9122 } 9123 9124 if (this.options.zIndex) { 9125 this._updateZIndex(); 9126 } 9127 9128 if (wasElementSupplied) { 9129 this._url = img.src; 9130 return; 9131 } 9132 9133 img.src = this._url; 9134 img.alt = this.options.alt; 9135 }, 9136 9137 _animateZoom: function (e) { 9138 var scale = this._map.getZoomScale(e.zoom), 9139 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9140 9141 setTransform(this._image, offset, scale); 9142 }, 9143 9144 _reset: function () { 9145 var image = this._image, 9146 bounds = new Bounds( 9147 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9148 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9149 size = bounds.getSize(); 9150 9151 setPosition(image, bounds.min); 9152 9153 image.style.width = size.x + 'px'; 9154 image.style.height = size.y + 'px'; 9155 }, 9156 9157 _updateOpacity: function () { 9158 setOpacity(this._image, this.options.opacity); 9159 }, 9160 9161 _updateZIndex: function () { 9162 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9163 this._image.style.zIndex = this.options.zIndex; 9164 } 9165 }, 9166 9167 _overlayOnError: function () { 9168 // @event error: Event 9169 // Fired when the ImageOverlay layer fails to load its image 9170 this.fire('error'); 9171 9172 var errorUrl = this.options.errorOverlayUrl; 9173 if (errorUrl && this._url !== errorUrl) { 9174 this._url = errorUrl; 9175 this._image.src = errorUrl; 9176 } 9177 } 9178}); 9179 9180// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9181// Instantiates an image overlay object given the URL of the image and the 9182// geographical bounds it is tied to. 9183var imageOverlay = function (url, bounds, options) { 9184 return new ImageOverlay(url, bounds, options); 9185}; 9186 9187/* 9188 * @class VideoOverlay 9189 * @aka L.VideoOverlay 9190 * @inherits ImageOverlay 9191 *
9192 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9193 * 9194 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9195 * HTML5 element. 9196 * 9197 * @example 9198 * 9199 * ```js 9200 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9201 * videoBounds = [[ 32, -130], [ 13, -100]]; 9202 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9203 * ``` 9204 */ 9205 9206var VideoOverlay = ImageOverlay.extend({ 9207 9208 // @section 9209 // @aka VideoOverlay options 9210 options: { 9211 // @option autoplay: Boolean = true 9212 // Whether the video starts playing automatically when loaded. 9213 autoplay: true, 9214 9215 // @option loop: Boolean = true 9216 // Whether the video will loop back to the beginning when played. 9217 loop: true 9218 }, 9219 9220 _initImage: function () { 9221 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9222 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9223 9224 addClass(vid, 'leaflet-image-layer'); 9225 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9226 9227 vid.onselectstart = falseFn; 9228 vid.onmousemove = falseFn; 9229 9230 // @event load: Event 9231 // Fired when the video has finished loading the first frame 9232 vid.onloadeddata = bind(this.fire, this, 'load'); 9233 9234 if (wasElementSupplied) { 9235 var sourceElements = vid.getElementsByTagName('source'); 9236 var sources = []; 9237 for (var j = 0; j < sourceElements.length; j++) { 9238 sources.push(sourceElements[j].src); 9239 } 9240 9241 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9242 return; 9243 } 9244 9245 if (!isArray(this._url)) { this._url = [this._url]; } 9246 9247 vid.autoplay = !!this.options.autoplay; 9248 vid.loop = !!this.options.loop; 9249 for (var i = 0; i < this._url.length; i++) { 9250 var source = create$1('source'); 9251 source.src = this._url[i]; 9252 vid.appendChild(source); 9253 } 9254 } 9255 9256 // @method getElement(): HTMLVideoElement 9257 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9258 // used by this overlay. 9259}); 9260 9261 9262// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9263// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9264// geographical bounds it is tied to. 9265 9266function videoOverlay(video, bounds, options) { 9267 return new VideoOverlay(video, bounds, options); 9268} 9269 9270/* 9271 * @class DivOverlay 9272 * @inherits Layer 9273 * @aka L.DivOverlay 9274 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins. 9275 */ 9276 9277// @namespace DivOverlay 9278var DivOverlay = Layer.extend({ 9279 9280 // @section 9281 // @aka DivOverlay options 9282 options: { 9283 // @option offset: Point = Point(0, 7) 9284 // The offset of the popup position. Useful to control the anchor 9285 // of the popup when opening it on some overlays. 9286 offset: [0, 7], 9287 9288 // @option className: String = '' 9289 // A custom CSS class name to assign to the popup. 9290 className: '', 9291 9292 // @option pane: String = 'popupPane' 9293 // `Map pane` where the popup will be added. 9294 pane: 'popupPane' 9295 }, 9296 9297 initialize: function (options, source) { 9298 setOptions(this, options); 9299 9300 this._source = source; 9301 }, 9302 9303 onAdd: function (map) { 9304 this._zoomAnimated = map._zoomAnimated; 9305 9306 if (!this._container) { 9307 this._initLayout(); 9308 } 9309 9310 if (map._fadeAnimated) { 9311 setOpacity(this._container, 0); 9312 } 9313 9314 clearTimeout(this._removeTimeout); 9315 this.getPane().appendChild(this._container); 9316 this.update(); 9317 9318 if (map._fadeAnimated) { 9319 setOpacity(this._container, 1); 9320 } 9321 9322 this.bringToFront(); 9323 }, 9324 9325 onRemove: function (map) { 9326 if (map._fadeAnimated) { 9327 setOpacity(this._container, 0); 9328 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200); 9329 } else { 9330 remove(this._container); 9331 } 9332 }, 9333 9334 // @namespace Popup 9335 // @method getLatLng: LatLng 9336 // Returns the geographical point of popup. 9337 getLatLng: function () { 9338 return this._latlng; 9339 }, 9340 9341 // @method setLatLng(latlng: LatLng): this 9342 // Sets the geographical point where the popup will open. 9343 setLatLng: function (latlng) { 9344 this._latlng = toLatLng(latlng); 9345 if (this._map) { 9346 this._updatePosition(); 9347 this._adjustPan(); 9348 } 9349 return this; 9350 }, 9351 9352 // @method getContent: String|HTMLElement 9353 // Returns the content of the popup. 9354 getContent: function () { 9355 return this._content; 9356 }, 9357 9358 // @method setContent(htmlContent: String|HTMLElement|Function): this
vendor: 3,443 bytes, lines 9359-9488
9359 // 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. 9360 setContent: function (content) { 9361 this._content = content; 9362 this.update(); 9363 return this; 9364 }, 9365 9366 // @method getElement: String|HTMLElement 9367 // Alias for [getContent()](#popup-getcontent) 9368 getElement: function () { 9369 return this._container; 9370 }, 9371 9372 // @method update: null 9373 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded. 9374 update: function () { 9375 if (!this._map) { return; } 9376 9377 this._container.style.visibility = 'hidden'; 9378 9379 this._updateContent(); 9380 this._updateLayout(); 9381 this._updatePosition(); 9382 9383 this._container.style.visibility = ''; 9384 9385 this._adjustPan(); 9386 }, 9387 9388 getEvents: function () { 9389 var events = { 9390 zoom: this._updatePosition, 9391 viewreset: this._updatePosition 9392 }; 9393 9394 if (this._zoomAnimated) { 9395 events.zoomanim = this._animateZoom; 9396 } 9397 return events; 9398 }, 9399 9400 // @method isOpen: Boolean 9401 // Returns `true` when the popup is visible on the map. 9402 isOpen: function () { 9403 return !!this._map && this._map.hasLayer(this); 9404 }, 9405 9406 // @method bringToFront: this 9407 // Brings this popup in front of other popups (in the same map pane). 9408 bringToFront: function () { 9409 if (this._map) { 9410 toFront(this._container); 9411 } 9412 return this; 9413 }, 9414 9415 // @method bringToBack: this 9416 // Brings this popup to the back of other popups (in the same map pane). 9417 bringToBack: function () { 9418 if (this._map) { 9419 toBack(this._container); 9420 } 9421 return this; 9422 }, 9423 9424 _updateContent: function () { 9425 if (!this._content) { return; } 9426 9427 var node = this._contentNode; 9428 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 9429 9430 if (typeof content === 'string') { 9431 node.innerHTML = content; 9432 } else { 9433 while (node.hasChildNodes()) { 9434 node.removeChild(node.firstChild); 9435 } 9436 node.appendChild(content); 9437 } 9438 this.fire('contentupdate'); 9439 }, 9440 9441 _updatePosition: function () { 9442 if (!this._map) { return; } 9443 9444 var pos = this._map.latLngToLayerPoint(this._latlng), 9445 offset = toPoint(this.options.offset), 9446 anchor = this._getAnchor(); 9447 9448 if (this._zoomAnimated) { 9449 setPosition(this._container, pos.add(anchor)); 9450 } else { 9451 offset = offset.add(pos).add(anchor); 9452 } 9453 9454 var bottom = this._containerBottom = -offset.y, 9455 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 9456 9457 // bottom position the popup in case the height of the popup changes (images loading etc) 9458 this._container.style.bottom = bottom + 'px'; 9459 this._container.style.left = left + 'px'; 9460 }, 9461 9462 _getAnchor: function () { 9463 return [0, 0]; 9464 } 9465 9466}); 9467 9468/* 9469 * @class Popup 9470 * @inherits DivOverlay 9471 * @aka L.Popup 9472 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 9473 * open popups while making sure that only one popup is open at one time 9474 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 9475 * 9476 * @example 9477 * 9478 * If you want to just bind a popup to marker click and then open it, it's really easy: 9479 * 9480 * ```js 9481 * marker.bindPopup(popupContent).openPopup(); 9482 * ``` 9483 * Path overlays like polylines also have a `bindPopup` method. 9484 * Here's a more complicated way to open a popup on a map: 9485 * 9486 * ```js 9487 * var popup = L.popup() 9488 * .setLatLng(latlng)
9489 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 9490 * .openOn(map); 9491 * ``` 9492 */ 9493 9494 9495// @namespace Popup 9496var Popup = DivOverlay.extend({ 9497 9498 // @section 9499 // @aka Popup options 9500 options: { 9501 // @option maxWidth: Number = 300 9502 // Max width of the popup, in pixels. 9503 maxWidth: 300, 9504 9505 // @option minWidth: Number = 50 9506 // Min width of the popup, in pixels. 9507 minWidth: 50, 9508 9509 // @option maxHeight: Number = null 9510 // If set, creates a scrollable container of the given height 9511 // inside a popup if its content exceeds it. 9512 maxHeight: null, 9513 9514 // @option autoPan: Boolean = true 9515 // Set it to `false` if you don't want the map to do panning animation 9516 // to fit the opened popup. 9517 autoPan: true, 9518 9519 // @option autoPanPaddingTopLeft: Point = null 9520 // The margin between the popup and the top left corner of the map 9521 // view after autopanning was performed. 9522 autoPanPaddingTopLeft: null, 9523 9524 // @option autoPanPaddingBottomRight: Point = null 9525 // The margin between the popup and the bottom right corner of the map 9526 // view after autopanning was performed. 9527 autoPanPaddingBottomRight: null, 9528 9529 // @option autoPanPadding: Point = Point(5, 5) 9530 // Equivalent of setting both top left and bottom right autopan padding to the same value. 9531 autoPanPadding: [5, 5], 9532 9533 // @option keepInView: Boolean = false 9534 // Set it to `true` if you want to prevent users from panning the popup 9535 // off of the screen while it is open. 9536 keepInView: false, 9537 9538 // @option closeButton: Boolean = true 9539 // Controls the presence of a close button in the popup. 9540 closeButton: true, 9541 9542 // @option autoClose: Boolean = true 9543 // Set it to `false` if you want to override the default behavior of 9544 // the popup closing when another popup is opened. 9545 autoClose: true, 9546 9547 // @option closeOnEscapeKey: Boolean = true 9548 // Set it to `false` if you want to override the default behavior of 9549 // the ESC key for closing of the popup. 9550 closeOnEscapeKey: true, 9551 9552 // @option closeOnClick: Boolean = * 9553 // Set it if you want to override the default behavior of the popup closing when user clicks 9554 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 9555 9556 // @option className: String = '' 9557 // A custom CSS class name to assign to the popup. 9558 className: '' 9559 }, 9560 9561 // @namespace Popup 9562 // @method openOn(map: Map): this 9563 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`. 9564 openOn: function (map) { 9565 map.openPopup(this); 9566 return this; 9567 }, 9568 9569 onAdd: function (map) { 9570 DivOverlay.prototype.onAdd.call(this, map); 9571 9572 // @namespace Map 9573 // @section Popup events 9574 // @event popupopen: PopupEvent 9575 // Fired when a popup is opened in the map 9576 map.fire('popupopen', {popup: this}); 9577 9578 if (this._source) { 9579 // @namespace Layer 9580 // @section Popup events 9581 // @event popupopen: PopupEvent 9582 // Fired when a popup bound to this layer is opened 9583 this._source.fire('popupopen', {popup: this}, true); 9584 // For non-path layers, we toggle the popup when clicking 9585 // again the layer, so prevent the map to reopen it. 9586 if (!(this._source instanceof Path)) { 9587 this._source.on('preclick', stopPropagation); 9588 } 9589 } 9590 }, 9591 9592 onRemove: function (map) { 9593 DivOverlay.prototype.onRemove.call(this, map); 9594 9595 // @namespace Map 9596 // @section Popup events 9597 // @event popupclose: PopupEvent 9598 // Fired when a popup in the map is closed 9599 map.fire('popupclose', {popup: this}); 9600 9601 if (this._source) { 9602 // @namespace Layer 9603 // @section Popup events 9604 // @event popupclose: PopupEvent 9605 // Fired when a popup bound to this layer is closed 9606 this._source.fire('popupclose', {popup: this}, true); 9607 if (!(this._source instanceof Path)) { 9608 this._source.off('preclick', stopPropagation); 9609 } 9610 } 9611 }, 9612 9613 getEvents: function () { 9614 var events = DivOverlay.prototype.getEvents.call(this); 9615 9616 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 9617 events.preclick = this._close; 9618 } 9619 9620 if (this.options.keepInView) { 9621 events.moveend = this._adjustPan; 9622 } 9623 9624 return events; 9625 }, 9626 9627 _close: function () { 9628 if (this._map) { 9629 this._map.closePopup(this); 9630 } 9631 }, 9632 9633 _initLayout: function () { 9634 var prefix = 'leaflet-popup', 9635 container = this._container = create$1('div', 9636 prefix + ' ' + (this.options.className || '') + 9637 ' leaflet-zoom-animated'); 9638 9639 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container); 9640 this._contentNode = create$1('div', prefix + '-content', wrapper); 9641 9642 disableClickPropagation(wrapper); 9643 disableScrollPropagation(this._contentNode); 9644 on(wrapper, 'contextmenu', stopPropagation); 9645 9646 this._tipContainer = create$1('div', prefix + '-tip-container', container); 9647 this._tip = create$1('div', prefix + '-tip', this._tipContainer); 9648 9649 if (this.options.closeButton) { 9650 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container); 9651 closeButton.href = '#close'; 9652 closeButton.innerHTML = '×'; 9653 9654 on(closeButton, 'click', this._onCloseButtonClick, this); 9655 } 9656 }, 9657 9658 _updateLayout: function () { 9659 var container = this._contentNode, 9660 style = container.style; 9661 9662 style.width = ''; 9663 style.whiteSpace = 'nowrap'; 9664 9665 var width = container.offsetWidth; 9666 width = Math.min(width, this.options.maxWidth); 9667 width = Math.max(width, this.options.minWidth); 9668 9669 style.width = (width + 1) + 'px'; 9670 style.whiteSpace = ''; 9671 9672 style.height = ''; 9673 9674 var height = container.offsetHeight, 9675 maxHeight = this.options.maxHeight, 9676 scrolledClass = 'leaflet-popup-scrolled'; 9677 9678 if (maxHeight && height > maxHeight) { 9679 style.height = maxHeight + 'px'; 9680 addClass(container, scrolledClass); 9681 } else { 9682 removeClass(container, scrolledClass); 9683 } 9684 9685 this._containerWidth = this._container.offsetWidth; 9686 }, 9687 9688 _animateZoom: function (e) { 9689 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 9690 anchor = this._getAnchor(); 9691 setPosition(this._container, pos.add(anchor)); 9692 }, 9693 9694 _adjustPan: function () { 9695 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } 9696 9697 var map = this._map, 9698 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0, 9699 containerHeight = this._container.offsetHeight + marginBottom, 9700 containerWidth = this._containerWidth, 9701 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 9702 9703 layerPos._add(getPosition(this._container)); 9704 9705 var containerPos = map.layerPointToContainerPoint(layerPos), 9706 padding = toPoint(this.options.autoPanPadding), 9707 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), 9708 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), 9709 size = map.getSize(), 9710 dx = 0, 9711 dy = 0; 9712 9713 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 9714 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 9715 } 9716 if (containerPos.x - dx - paddingTL.x < 0) { // left 9717 dx = containerPos.x - paddingTL.x; 9718 } 9719 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 9720 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 9721 } 9722 if (containerPos.y - dy - paddingTL.y < 0) { // top 9723 dy = containerPos.y - paddingTL.y; 9724 } 9725 9726 // @namespace Map 9727 // @section Popup events 9728 // @event autopanstart: Event 9729 // Fired when the map starts autopanning when opening a popup. 9730 if (dx || dy) { 9731 map 9732 .fire('autopanstart') 9733 .panBy([dx, dy]); 9734 } 9735 }, 9736 9737 _onCloseButtonClick: function (e) { 9738 this._close(); 9739 stop(e); 9740 }, 9741 9742 _getAnchor: function () { 9743 // Where should we anchor the popup on the source layer? 9744 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 9745 } 9746 9747}); 9748 9749// @namespace Popup 9750// @factory L.popup(options?: Popup options, source?: Layer) 9751// 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. 9752var popup = function (options, source) { 9753 return new Popup(options, source); 9754}; 9755 9756 9757/* @namespace Map 9758 * @section Interaction Options 9759 * @option closePopupOnClick: Boolean = true 9760 * Set it to `false` if you don't want popups to close when user clicks the map. 9761 */ 9762Map.mergeOptions({ 9763 closePopupOnClick: true 9764}); 9765 9766 9767// @namespace Map 9768// @section Methods for Layers and Controls 9769Map.include({ 9770 // @method openPopup(popup: Popup): this 9771 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 9772 // @alternative 9773 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 9774 // Creates a popup with the specified content and options and opens it in the given point on a map. 9775 openPopup: function (popup, latlng, options) { 9776 if (!(popup instanceof Popup)) { 9777 popup = new Popup(options).setContent(popup); 9778 } 9779 9780 if (latlng) { 9781 popup.setLatLng(latlng); 9782 } 9783 9784 if (this.hasLayer(popup)) { 9785 return this; 9786 } 9787 9788 if (this._popup && this._popup.options.autoClose) { 9789 this.closePopup(); 9790 } 9791 9792 this._popup = popup; 9793 return this.addLayer(popup); 9794 }, 9795 9796 // @method closePopup(popup?: Popup): this 9797 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 9798 closePopup: function (popup) { 9799 if (!popup || popup === this._popup) { 9800 popup = this._popup; 9801 this._popup = null; 9802 } 9803 if (popup) { 9804 this.removeLayer(popup); 9805 } 9806 return this; 9807 } 9808}); 9809 9810/* 9811 * @namespace Layer 9812 * @section Popup methods example 9813 * 9814 * All layers share a set of methods convenient for binding popups to it. 9815 * 9816 * ```js 9817 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 9818 * layer.openPopup(); 9819 * layer.closePopup(); 9820 * ``` 9821 * 9822 * 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. 9823 */ 9824 9825// @section Popup methods 9826Layer.include({ 9827 9828 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 9829 // Binds a popup to the layer with the passed `content` and sets up the 9830 // necessary event listeners. If a `Function` is passed it will receive 9831 // the layer as the first argument and should return a `String` or `HTMLElement`. 9832 bindPopup: function (content, options) { 9833 9834 if (content instanceof Popup) { 9835 setOptions(content, options); 9836 this._popup = content; 9837 content._source = this; 9838 } else { 9839 if (!this._popup || options) { 9840 this._popup = new Popup(options, this); 9841 } 9842 this._popup.setContent(content); 9843 } 9844 9845 if (!this._popupHandlersAdded) { 9846 this.on({ 9847 click: this._openPopup, 9848 keypress: this._onKeyPress, 9849 remove: this.closePopup,
9850 move: this._movePopup 9851 }); 9852 this._popupHandlersAdded = true; 9853 } 9854 9855 return this; 9856 }, 9857 9858 // @method unbindPopup(): this 9859 // Removes the popup previously bound with `bindPopup`. 9860 unbindPopup: function () { 9861 if (this._popup) { 9862 this.off({ 9863 click: this._openPopup, 9864 keypress: this._onKeyPress, 9865 remove: this.closePopup, 9866 move: this._movePopup 9867 }); 9868 this._popupHandlersAdded = false; 9869 this._popup = null; 9870 } 9871 return this; 9872 }, 9873 9874 // @method openPopup(latlng?: LatLng): this 9875 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 9876 openPopup: function (layer, latlng) { 9877 if (!(layer instanceof Layer)) { 9878 latlng = layer; 9879 layer = this; 9880 } 9881 9882 if (layer instanceof FeatureGroup) { 9883 for (var id in this._layers) { 9884 layer = this._layers[id]; 9885 break; 9886 } 9887 } 9888 9889 if (!latlng) { 9890 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng(); 9891 } 9892 9893 if (this._popup && this._map) { 9894 // set popup source to this layer 9895 this._popup._source = layer; 9896 9897 // update the popup (content, layout, ect...) 9898 this._popup.update(); 9899 9900 // open the popup on the map 9901 this._map.openPopup(this._popup, latlng); 9902 } 9903 9904 return this; 9905 }, 9906 9907 // @method closePopup(): this 9908 // Closes the popup bound to this layer if it is open. 9909 closePopup: function () { 9910 if (this._popup) { 9911 this._popup._close(); 9912 } 9913 return this; 9914 }, 9915 9916 // @method togglePopup(): this 9917 // Opens or closes the popup bound to this layer depending on its current state. 9918 togglePopup: function (target) { 9919 if (this._popup) { 9920 if (this._popup._map) { 9921 this.closePopup(); 9922 } else { 9923 this.openPopup(target); 9924 } 9925 } 9926 return this; 9927 }, 9928 9929 // @method isPopupOpen(): boolean 9930 // Returns `true` if the popup bound to this layer is currently open. 9931 isPopupOpen: function () { 9932 return (this._popup ? this._popup.isOpen() : false); 9933 }, 9934 9935 // @method setPopupContent(content: String|HTMLElement|Popup): this 9936 // Sets the content of the popup bound to this layer. 9937 setPopupContent: function (content) { 9938 if (this._popup) { 9939 this._popup.setContent(content); 9940 } 9941 return this; 9942 }, 9943 9944 // @method getPopup(): Popup 9945 // Returns the popup bound to this layer. 9946 getPopup: function () { 9947 return this._popup; 9948 }, 9949 9950 _openPopup: function (e) { 9951 var layer = e.layer || e.target; 9952 9953 if (!this._popup) { 9954 return; 9955 } 9956 9957 if (!this._map) { 9958 return; 9959 } 9960 9961 // prevent map click 9962 stop(e); 9963 9964 // if this inherits from Path its a vector and we can just 9965 // open the popup at the new location 9966 if (layer instanceof Path) { 9967 this.openPopup(e.layer || e.target, e.latlng); 9968 return; 9969 } 9970 9971 // otherwise treat it like a marker and figure out 9972 // if we should toggle it open/closed 9973 if (this._map.hasLayer(this._popup) && this._popup._source === layer) { 9974 this.closePopup(); 9975 } else { 9976 this.openPopup(layer, e.latlng); 9977 } 9978 }, 9979 9980 _movePopup: function (e) { 9981 this._popup.setLatLng(e.latlng); 9982 }, 9983 9984 _onKeyPress: function (e) { 9985 if (e.originalEvent.keyCode === 13) { 9986 this._openPopup(e); 9987 } 9988 } 9989}); 9990 9991/* 9992 * @class Tooltip 9993 * @inherits DivOverlay 9994 * @aka L.Tooltip 9995 * Used to display small texts on top of map layers. 9996 * 9997 * @example 9998 * 9999 * ```js 10000 * marker.bindTooltip("my tooltip text").openTooltip(); 10001 * ``` 10002 * Note about tooltip offset. Leaflet takes two options in consideration 10003 * for computing tooltip offsetting: 10004 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 10005 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 10006 * move it to the bottom. Negatives will move to the left and top. 10007 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 10008 * should adapt this value if you use a custom icon. 10009 */ 10010 10011 10012// @namespace Tooltip 10013var Tooltip = DivOverlay.extend({ 10014 10015 // @section 10016 // @aka Tooltip options 10017 options: { 10018 // @option pane: String = 'tooltipPane' 10019 // `Map pane` where the tooltip will be added. 10020 pane: 'tooltipPane', 10021 10022 // @option offset: Point = Point(0, 0) 10023 // Optional offset of the tooltip position. 10024 offset: [0, 0], 10025 10026 // @option direction: String = 'auto' 10027 // Direction where to open the tooltip. Possible values are: `right`, `left`, 10028 // `top`, `bottom`, `center`, `auto`. 10029 // `auto` will dynamically switch between `right` and `left` according to the tooltip 10030 // position on the map. 10031 direction: 'auto', 10032 10033 // @option permanent: Boolean = false 10034 // Whether to open the tooltip permanently or only on mouseover. 10035 permanent: false, 10036 10037 // @option sticky: Boolean = false 10038 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10039 sticky: false, 10040 10041 // @option interactive: Boolean = false 10042 // If true, the tooltip will listen to the feature events. 10043 interactive: false, 10044 10045 // @option opacity: Number = 0.9 10046 // Tooltip container opacity. 10047 opacity: 0.9 10048 }, 10049 10050 onAdd: function (map) { 10051 DivOverlay.prototype.onAdd.call(this, map); 10052 this.setOpacity(this.options.opacity); 10053 10054 // @namespace Map 10055 // @section Tooltip events 10056 // @event tooltipopen: TooltipEvent 10057 // Fired when a tooltip is opened in the map. 10058 map.fire('tooltipopen', {tooltip: this}); 10059 10060 if (this._source) { 10061 // @namespace Layer 10062 // @section Tooltip events 10063 // @event tooltipopen: TooltipEvent 10064 // Fired when a tooltip bound to this layer is opened. 10065 this._source.fire('tooltipopen', {tooltip: this}, true); 10066 } 10067 }, 10068 10069 onRemove: function (map) { 10070 DivOverlay.prototype.onRemove.call(this, map); 10071 10072 // @namespace Map 10073 // @section Tooltip events 10074 // @event tooltipclose: TooltipEvent 10075 // Fired when a tooltip in the map is closed. 10076 map.fire('tooltipclose', {tooltip: this}); 10077 10078 if (this._source) { 10079 // @namespace Layer 10080 // @section Tooltip events 10081 // @event tooltipclose: TooltipEvent 10082 // Fired when a tooltip bound to this layer is closed. 10083 this._source.fire('tooltipclose', {tooltip: this}, true); 10084 } 10085 }, 10086 10087 getEvents: function () { 10088 var events = DivOverlay.prototype.getEvents.call(this); 10089 10090 if (touch && !this.options.permanent) { 10091 events.preclick = this._close; 10092 } 10093 10094 return events; 10095 }, 10096 10097 _close: function () { 10098 if (this._map) { 10099 this._map.closeTooltip(this); 10100 } 10101 }, 10102 10103 _initLayout: function () { 10104 var prefix = 'leaflet-tooltip', 10105 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 10106 10107 this._contentNode = this._container = create$1('div', className); 10108 }, 10109 10110 _updateLayout: function () {}, 10111 10112 _adjustPan: function () {}, 10113 10114 _setPosition: function (pos) { 10115 var map = this._map, 10116 container = this._container, 10117 centerPoint = map.latLngToContainerPoint(map.getCenter()), 10118 tooltipPoint = map.layerPointToContainerPoint(pos), 10119 direction = this.options.direction, 10120 tooltipWidth = container.offsetWidth, 10121 tooltipHeight = container.offsetHeight, 10122 offset = toPoint(this.options.offset), 10123 anchor = this._getAnchor(); 10124 10125 if (direction === 'top') { 10126 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true)); 10127 } else if (direction === 'bottom') { 10128 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true)); 10129 } else if (direction === 'center') { 10130 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true)); 10131 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) { 10132 direction = 'right'; 10133 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true)); 10134 } else { 10135 direction = 'left'; 10136 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true)); 10137 } 10138 10139 removeClass(container, 'leaflet-tooltip-right'); 10140 removeClass(container, 'leaflet-tooltip-left'); 10141 removeClass(container, 'leaflet-tooltip-top'); 10142 removeClass(container, 'leaflet-tooltip-bottom'); 10143 addClass(container, 'leaflet-tooltip-' + direction); 10144 setPosition(container, pos); 10145 }, 10146 10147 _updatePosition: function () { 10148 var pos = this._map.latLngToLayerPoint(this._latlng); 10149 this._setPosition(pos); 10150 }, 10151 10152 setOpacity: function (opacity) { 10153 this.options.opacity = opacity; 10154 10155 if (this._container) { 10156 setOpacity(this._container, opacity); 10157 } 10158 }, 10159 10160 _animateZoom: function (e) { 10161 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 10162 this._setPosition(pos); 10163 }, 10164 10165 _getAnchor: function () { 10166 // Where should we anchor the tooltip on the source layer? 10167 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 10168 } 10169 10170}); 10171 10172// @namespace Tooltip 10173// @factory L.tooltip(options?: Tooltip options, source?: Layer) 10174// 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. 10175var tooltip = function (options, source) { 10176 return new Tooltip(options, source); 10177}; 10178 10179// @namespace Map 10180// @section Methods for Layers and Controls 10181Map.include({ 10182 10183 // @method openTooltip(tooltip: Tooltip): this 10184 // Opens the specified tooltip. 10185 // @alternative 10186 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 10187 // Creates a tooltip with the specified content and options and open it. 10188 openTooltip: function (tooltip, latlng, options) { 10189 if (!(tooltip instanceof Tooltip)) { 10190 tooltip = new Tooltip(options).setContent(tooltip); 10191 } 10192 10193 if (latlng) { 10194 tooltip.setLatLng(latlng); 10195 } 10196 10197 if (this.hasLayer(tooltip)) { 10198 return this; 10199 } 10200 10201 return this.addLayer(tooltip); 10202 }, 10203 10204 // @method closeTooltip(tooltip?: Tooltip): this 10205 // Closes the tooltip given as parameter. 10206 closeTooltip: function (tooltip) { 10207 if (tooltip) { 10208 this.removeLayer(tooltip); 10209 } 10210 return this; 10211 } 10212 10213}); 10214 10215/* 10216 * @namespace Layer 10217 * @section Tooltip methods example 10218 * 10219 * All layers share a set of methods convenient for binding tooltips to it. 10220 * 10221 * ```js 10222 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 10223 * layer.openTooltip(); 10224 * layer.closeTooltip(); 10225 * ``` 10226 */ 10227 10228// @section Tooltip methods 10229Layer.include({ 10230 10231 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 10232 // Binds a tooltip to the layer with the passed `content` and sets up the 10233 // necessary event listeners. If a `Function` is passed it will receive 10234 // the layer as the first argument and should return a `String` or `HTMLElement`. 10235 bindTooltip: function (content, options) { 10236 10237 if (content instanceof Tooltip) { 10238 setOptions(content, options); 10239 this._tooltip = content; 10240 content._source = this; 10241 } else { 10242 if (!this._tooltip || options) { 10243 this._tooltip = new Tooltip(options, this); 10244 } 10245 this._tooltip.setContent(content); 10246 10247 } 10248 10249 this._initTooltipInteractions(); 10250 10251 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 10252 this.openTooltip(); 10253 } 10254 10255 return this; 10256 }, 10257 10258 // @method unbindTooltip(): this 10259 // Removes the tooltip previously bound with `bindTooltip`. 10260 unbindTooltip: function () { 10261 if (this._tooltip) { 10262 this._initTooltipInteractions(true); 10263 this.closeTooltip(); 10264 this._tooltip = null; 10265 } 10266 return this; 10267 }, 10268 10269 _initTooltipInteractions: function (remove$$1) { 10270 if (!remove$$1 && this._tooltipHandlersAdded) { return; } 10271 var onOff = remove$$1 ? 'off' : 'on', 10272 events = { 10273 remove: this.closeTooltip, 10274 move: this._moveTooltip 10275 }; 10276 if (!this._tooltip.options.permanent) { 10277 events.mouseover = this._openTooltip; 10278 events.mouseout = this.closeTooltip; 10279 if (this._tooltip.options.sticky) { 10280 events.mousemove = this._moveTooltip; 10281 } 10282 if (touch) { 10283 events.click = this._openTooltip; 10284 } 10285 } else { 10286 events.add = this._openTooltip; 10287 } 10288 this[onOff](events); 10289 this._tooltipHandlersAdded = !remove$$1; 10290 }, 10291 10292 // @method openTooltip(latlng?: LatLng): this 10293 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 10294 openTooltip: function (layer, latlng) { 10295 if (!(layer instanceof Layer)) { 10296 latlng = layer; 10297 layer = this; 10298 } 10299 10300 if (layer instanceof FeatureGroup) { 10301 for (var id in this._layers) { 10302 layer = this._layers[id]; 10303 break; 10304 } 10305 } 10306 10307 if (!latlng) { 10308 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng(); 10309 } 10310 10311 if (this._tooltip && this._map) { 10312 10313 // set tooltip source to this layer 10314 this._tooltip._source = layer; 10315 10316 // update the tooltip (content, layout, ect...) 10317 this._tooltip.update(); 10318 10319 // open the tooltip on the map 10320 this._map.openTooltip(this._tooltip, latlng); 10321 10322 // Tooltip container may not be defined if not permanent and never 10323 // opened. 10324 if (this._tooltip.options.interactive && this._tooltip._container) { 10325 addClass(this._tooltip._container, 'leaflet-clickable'); 10326 this.addInteractiveTarget(this._tooltip._container); 10327 } 10328 } 10329 10330 return this; 10331 }, 10332 10333 // @method closeTooltip(): this 10334 // Closes the tooltip bound to this layer if it is open. 10335 closeTooltip: function () { 10336 if (this._tooltip) { 10337 this._tooltip._close(); 10338 if (this._tooltip.options.interactive && this._tooltip._container) { 10339 removeClass(this._tooltip._container, 'leaflet-clickable'); 10340 this.removeInteractiveTarget(this._tooltip._container); 10341 } 10342 } 10343 return this; 10344 }, 10345 10346 // @method toggleTooltip(): this 10347 // Opens or closes the tooltip bound to this layer depending on its current state. 10348 toggleTooltip: function (target) { 10349 if (this._tooltip) { 10350 if (this._tooltip._map) { 10351 this.closeTooltip(); 10352 } else { 10353 this.openTooltip(target); 10354 } 10355 } 10356 return this; 10357 }, 10358 10359 // @method isTooltipOpen(): boolean 10360 // Returns `true` if the tooltip bound to this layer is currently open. 10361 isTooltipOpen: function () { 10362 return this._tooltip.isOpen(); 10363 }, 10364 10365 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10366 // Sets the content of the tooltip bound to this layer. 10367 setTooltipContent: function (content) { 10368 if (this._tooltip) { 10369 this._tooltip.setContent(content); 10370 } 10371 return this; 10372 }, 10373 10374 // @method getTooltip(): Tooltip 10375 // Returns the tooltip bound to this layer. 10376 getTooltip: function () { 10377 return this._tooltip; 10378 }, 10379 10380 _openTooltip: function (e) { 10381 var layer = e.layer || e.target; 10382 10383 if (!this._tooltip || !this._map) { 10384 return; 10385 } 10386 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined); 10387 }, 10388 10389 _moveTooltip: function (e) { 10390 var latlng = e.latlng, containerPoint, layerPoint; 10391 if (this._tooltip.options.sticky && e.originalEvent) { 10392 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 10393 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 10394 latlng = this._map.layerPointToLatLng(layerPoint); 10395 } 10396 this._tooltip.setLatLng(latlng); 10397 } 10398}); 10399 10400/* 10401 * @class DivIcon 10402 * @aka L.DivIcon 10403 * @inherits Icon 10404 * 10405 * Represents a lightweight icon for markers that uses a simple `<div>` 10406 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 10407 * 10408 * @example 10409 * ```js 10410 * var myIcon = L.divIcon({className: 'my-div-icon'}); 10411 * // you can set .my-div-icon styles in CSS 10412 * 10413 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 10414 * ``` 10415 * 10416 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 10417 */ 10418 10419var DivIcon = Icon.extend({ 10420 options: { 10421 // @section 10422 // @aka DivIcon options 10423 iconSize: [12, 12], // also can be set through CSS 10424 10425 // iconAnchor: (Point), 10426 // popupAnchor: (Point), 10427 10428 // @option html: String = '' 10429 // Custom HTML code to put inside the div element, empty by default. 10430 html: false, 10431 10432 // @option bgPos: Point = [0, 0] 10433 // Optional relative position of the background, in pixels 10434 bgPos: null, 10435 10436 className: 'leaflet-div-icon' 10437 }, 10438 10439 createIcon: function (oldIcon) { 10440 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 10441 options = this.options; 10442 10443 div.innerHTML = options.html !== false ? options.html : ''; 10444 10445 if (options.bgPos) { 10446 var bgPos = toPoint(options.bgPos); 10447 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 10448 } 10449 this._setIconStyles(div, 'icon'); 10450 10451 return div; 10452 }, 10453 10454 createShadow: function () { 10455 return null; 10456 } 10457}); 10458 10459// @factory L.divIcon(options: DivIcon options) 10460// Creates a `DivIcon` instance with the given options. 10461function divIcon(options) { 10462 return new DivIcon(options); 10463} 10464 10465Icon.Default = IconDefault; 10466 10467/* 10468 * @class GridLayer 10469 * @inherits Layer 10470 * @aka L.GridLayer 10471 * 10472 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 10473 * 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. 10474 * 10475 * 10476 * @section Synchronous usage 10477 * @example 10478 * 10479 * 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. 10480 * 10481 * ```js 10482 * var CanvasLayer = L.GridLayer.extend({ 10483 * createTile: function(coords){ 10484 * // create a <canvas> element for drawing 10485 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10486 * 10487 * // setup tile width and height according to the options 10488 * var size = this.getTileSize(); 10489 * tile.width = size.x; 10490 * tile.height = size.y; 10491 * 10492 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 10493 * var ctx = tile.getContext('2d'); 10494 * 10495 * // return the tile so it can be rendered on screen 10496 * return tile; 10497 * } 10498 * }); 10499 * ``` 10500 * 10501 * @section Asynchronous usage 10502 * @example 10503 * 10504 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback. 10505 * 10506 * ```js 10507 * var CanvasLayer = L.GridLayer.extend({ 10508 * createTile: function(coords, done){ 10509 * var error; 10510 * 10511 * // create a <canvas> element for drawing 10512 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10513 * 10514 * // setup tile width and height according to the options 10515 * var size = this.getTileSize(); 10516 * tile.width = size.x; 10517 * tile.height = size.y; 10518 * 10519 * // draw something asynchronously and pass the tile to the done() callback 10520 * setTimeout(function() { 10521 * done(error, tile); 10522 * }, 1000); 10523 * 10524 * return tile; 10525 * } 10526 * }); 10527 * ``` 10528 * 10529 * @section 10530 */ 10531 10532 10533var GridLayer = Layer.extend({ 10534 10535 // @section 10536 // @aka GridLayer options 10537 options: {
vendor: 44,535 bytes, lines 10538-12167
10538 // @option tileSize: Number|Point = 256 10539 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 10540 tileSize: 256, 10541 10542 // @option opacity: Number = 1.0 10543 // Opacity of the tiles. Can be used in the `createTile()` function. 10544 opacity: 1, 10545 10546 // @option updateWhenIdle: Boolean = (depends) 10547 // Load new tiles only when panning ends. 10548 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 10549 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 10550 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 10551 updateWhenIdle: mobile, 10552 10553 // @option updateWhenZooming: Boolean = true 10554 // 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. 10555 updateWhenZooming: true, 10556 10557 // @option updateInterval: Number = 200 10558 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 10559 updateInterval: 200, 10560 10561 // @option zIndex: Number = 1 10562 // The explicit zIndex of the tile layer. 10563 zIndex: 1, 10564 10565 // @option bounds: LatLngBounds = undefined 10566 // If set, tiles will only be loaded inside the set `LatLngBounds`. 10567 bounds: null, 10568 10569 // @option minZoom: Number = 0 10570 // The minimum zoom level down to which this layer will be displayed (inclusive). 10571 minZoom: 0, 10572 10573 // @option maxZoom: Number = undefined 10574 // The maximum zoom level up to which this layer will be displayed (inclusive). 10575 maxZoom: undefined, 10576 10577 // @option maxNativeZoom: Number = undefined 10578 // Maximum zoom number the tile source has available. If it is specified, 10579 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 10580 // from `maxNativeZoom` level and auto-scaled. 10581 maxNativeZoom: undefined, 10582 10583 // @option minNativeZoom: Number = undefined 10584 // Minimum zoom number the tile source has available. If it is specified, 10585 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 10586 // from `minNativeZoom` level and auto-scaled. 10587 minNativeZoom: undefined, 10588 10589 // @option noWrap: Boolean = false 10590 // Whether the layer is wrapped around the antimeridian. If `true`, the 10591 // GridLayer will only be displayed once at low zoom levels. Has no 10592 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 10593 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 10594 // tiles outside the CRS limits. 10595 noWrap: false, 10596 10597 // @option pane: String = 'tilePane' 10598 // `Map pane` where the grid layer will be added. 10599 pane: 'tilePane', 10600 10601 // @option className: String = '' 10602 // A custom class name to assign to the tile layer. Empty by default. 10603 className: '', 10604 10605 // @option keepBuffer: Number = 2 10606 // When panning the map, keep this many rows and columns of tiles before unloading them. 10607 keepBuffer: 2 10608 }, 10609 10610 initialize: function (options) { 10611 setOptions(this, options); 10612 }, 10613 10614 onAdd: function () { 10615 this._initContainer(); 10616 10617 this._levels = {}; 10618 this._tiles = {}; 10619 10620 this._resetView(); 10621 this._update(); 10622 }, 10623 10624 beforeAdd: function (map) { 10625 map._addZoomLimit(this); 10626 }, 10627 10628 onRemove: function (map) { 10629 this._removeAllTiles(); 10630 remove(this._container); 10631 map._removeZoomLimit(this); 10632 this._container = null; 10633 this._tileZoom = undefined; 10634 }, 10635 10636 // @method bringToFront: this 10637 // Brings the tile layer to the top of all tile layers. 10638 bringToFront: function () { 10639 if (this._map) { 10640 toFront(this._container); 10641 this._setAutoZIndex(Math.max); 10642 } 10643 return this; 10644 }, 10645 10646 // @method bringToBack: this 10647 // Brings the tile layer to the bottom of all tile layers. 10648 bringToBack: function () { 10649 if (this._map) { 10650 toBack(this._container); 10651 this._setAutoZIndex(Math.min); 10652 } 10653 return this; 10654 }, 10655 10656 // @method getContainer: HTMLElement 10657 // Returns the HTML element that contains the tiles for this layer. 10658 getContainer: function () { 10659 return this._container; 10660 }, 10661 10662 // @method setOpacity(opacity: Number): this 10663 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 10664 setOpacity: function (opacity) { 10665 this.options.opacity = opacity; 10666 this._updateOpacity(); 10667 return this; 10668 }, 10669 10670 // @method setZIndex(zIndex: Number): this 10671 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 10672 setZIndex: function (zIndex) { 10673 this.options.zIndex = zIndex; 10674 this._updateZIndex(); 10675 10676 return this; 10677 }, 10678 10679 // @method isLoading: Boolean 10680 // Returns `true` if any tile in the grid layer has not finished loading. 10681 isLoading: function () { 10682 return this._loading; 10683 }, 10684 10685 // @method redraw: this 10686 // Causes the layer to clear all the tiles and request them again. 10687 redraw: function () { 10688 if (this._map) { 10689 this._removeAllTiles(); 10690 this._update(); 10691 } 10692 return this; 10693 }, 10694 10695 getEvents: function () { 10696 var events = { 10697 viewprereset: this._invalidateAll, 10698 viewreset: this._resetView, 10699 zoom: this._resetView, 10700 moveend: this._onMoveEnd 10701 }; 10702 10703 if (!this.options.updateWhenIdle) { 10704 // update tiles on move, but not more often than once per given interval 10705 if (!this._onMove) { 10706 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 10707 } 10708 10709 events.move = this._onMove; 10710 } 10711 10712 if (this._zoomAnimated) { 10713 events.zoomanim = this._animateZoom; 10714 } 10715 10716 return events; 10717 }, 10718 10719 // @section Extension methods 10720 // Layers extending `GridLayer` shall reimplement the following method. 10721 // @method createTile(coords: Object, done?: Function): HTMLElement 10722 // Called only internally, must be overridden by classes extending `GridLayer`. 10723 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 10724 // is specified, it must be called when the tile has finished loading and drawing. 10725 createTile: function () { 10726 return document.createElement('div'); 10727 }, 10728 10729 // @section 10730 // @method getTileSize: Point 10731 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 10732 getTileSize: function () { 10733 var s = this.options.tileSize; 10734 return s instanceof Point ? s : new Point(s, s); 10735 }, 10736 10737 _updateZIndex: function () { 10738 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 10739 this._container.style.zIndex = this.options.zIndex; 10740 } 10741 }, 10742 10743 _setAutoZIndex: function (compare) { 10744 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 10745 10746 var layers = this.getPane().children, 10747 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 10748 10749 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 10750 10751 zIndex = layers[i].style.zIndex; 10752 10753 if (layers[i] !== this._container && zIndex) { 10754 edgeZIndex = compare(edgeZIndex, +zIndex); 10755 } 10756 } 10757 10758 if (isFinite(edgeZIndex)) { 10759 this.options.zIndex = edgeZIndex + compare(-1, 1); 10760 this._updateZIndex(); 10761 } 10762 }, 10763 10764 _updateOpacity: function () { 10765 if (!this._map) { return; } 10766 10767 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 10768 if (ielt9) { return; } 10769 10770 setOpacity(this._container, this.options.opacity); 10771 10772 var now = +new Date(), 10773 nextFrame = false, 10774 willPrune = false; 10775 10776 for (var key in this._tiles) { 10777 var tile = this._tiles[key]; 10778 if (!tile.current || !tile.loaded) { continue; } 10779 10780 var fade = Math.min(1, (now - tile.loaded) / 200); 10781 10782 setOpacity(tile.el, fade); 10783 if (fade < 1) { 10784 nextFrame = true; 10785 } else { 10786 if (tile.active) { 10787 willPrune = true; 10788 } else { 10789 this._onOpaqueTile(tile); 10790 } 10791 tile.active = true; 10792 } 10793 } 10794 10795 if (willPrune && !this._noPrune) { this._pruneTiles(); } 10796 10797 if (nextFrame) { 10798 cancelAnimFrame(this._fadeFrame); 10799 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 10800 } 10801 }, 10802 10803 _onOpaqueTile: falseFn, 10804 10805 _initContainer: function () { 10806 if (this._container) { return; } 10807 10808 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || '')); 10809 this._updateZIndex(); 10810 10811 if (this.options.opacity < 1) { 10812 this._updateOpacity(); 10813 } 10814 10815 this.getPane().appendChild(this._container); 10816 }, 10817 10818 _updateLevels: function () { 10819 10820 var zoom = this._tileZoom, 10821 maxZoom = this.options.maxZoom; 10822 10823 if (zoom === undefined) { return undefined; } 10824 10825 for (var z in this._levels) { 10826 if (this._levels[z].el.children.length || z === zoom) { 10827 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 10828 this._onUpdateLevel(z); 10829 } else { 10830 remove(this._levels[z].el); 10831 this._removeTilesAtZoom(z); 10832 this._onRemoveLevel(z); 10833 delete this._levels[z]; 10834 } 10835 } 10836 10837 var level = this._levels[zoom], 10838 map = this._map; 10839 10840 if (!level) { 10841 level = this._levels[zoom] = {}; 10842 10843 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 10844 level.el.style.zIndex = maxZoom; 10845 10846 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 10847 level.zoom = zoom; 10848 10849 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 10850 10851 // force the browser to consider the newly added element for transition 10852 falseFn(level.el.offsetWidth); 10853 10854 this._onCreateLevel(level); 10855 } 10856 10857 this._level = level; 10858 10859 return level; 10860 }, 10861 10862 _onUpdateLevel: falseFn, 10863 10864 _onRemoveLevel: falseFn, 10865 10866 _onCreateLevel: falseFn, 10867 10868 _pruneTiles: function () { 10869 if (!this._map) { 10870 return; 10871 } 10872 10873 var key, tile; 10874 10875 var zoom = this._map.getZoom(); 10876 if (zoom > this.options.maxZoom || 10877 zoom < this.options.minZoom) { 10878 this._removeAllTiles(); 10879 return; 10880 } 10881 10882 for (key in this._tiles) { 10883 tile = this._tiles[key]; 10884 tile.retain = tile.current; 10885 } 10886 10887 for (key in this._tiles) { 10888 tile = this._tiles[key]; 10889 if (tile.current && !tile.active) { 10890 var coords = tile.coords; 10891 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 10892 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 10893 } 10894 } 10895 } 10896 10897 for (key in this._tiles) { 10898 if (!this._tiles[key].retain) { 10899 this._removeTile(key); 10900 } 10901 } 10902 }, 10903 10904 _removeTilesAtZoom: function (zoom) { 10905 for (var key in this._tiles) { 10906 if (this._tiles[key].coords.z !== zoom) { 10907 continue; 10908 } 10909 this._removeTile(key); 10910 } 10911 }, 10912 10913 _removeAllTiles: function () { 10914 for (var key in this._tiles) { 10915 this._removeTile(key); 10916 } 10917 }, 10918 10919 _invalidateAll: function () { 10920 for (var z in this._levels) { 10921 remove(this._levels[z].el); 10922 this._onRemoveLevel(z); 10923 delete this._levels[z]; 10924 } 10925 this._removeAllTiles(); 10926 10927 this._tileZoom = undefined; 10928 }, 10929 10930 _retainParent: function (x, y, z, minZoom) { 10931 var x2 = Math.floor(x / 2), 10932 y2 = Math.floor(y / 2), 10933 z2 = z - 1, 10934 coords2 = new Point(+x2, +y2); 10935 coords2.z = +z2; 10936 10937 var key = this._tileCoordsToKey(coords2), 10938 tile = this._tiles[key]; 10939 10940 if (tile && tile.active) { 10941 tile.retain = true; 10942 return true; 10943 10944 } else if (tile && tile.loaded) { 10945 tile.retain = true; 10946 } 10947 10948 if (z2 > minZoom) { 10949 return this._retainParent(x2, y2, z2, minZoom); 10950 } 10951 10952 return false; 10953 }, 10954 10955 _retainChildren: function (x, y, z, maxZoom) { 10956 10957 for (var i = 2 * x; i < 2 * x + 2; i++) { 10958 for (var j = 2 * y; j < 2 * y + 2; j++) { 10959 10960 var coords = new Point(i, j); 10961 coords.z = z + 1; 10962 10963 var key = this._tileCoordsToKey(coords), 10964 tile = this._tiles[key]; 10965 10966 if (tile && tile.active) { 10967 tile.retain = true; 10968 continue; 10969 10970 } else if (tile && tile.loaded) { 10971 tile.retain = true; 10972 } 10973 10974 if (z + 1 < maxZoom) { 10975 this._retainChildren(i, j, z + 1, maxZoom); 10976 } 10977 } 10978 } 10979 }, 10980 10981 _resetView: function (e) { 10982 var animating = e && (e.pinch || e.flyTo); 10983 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 10984 }, 10985 10986 _animateZoom: function (e) { 10987 this._setView(e.center, e.zoom, true, e.noUpdate); 10988 }, 10989 10990 _clampZoom: function (zoom) { 10991 var options = this.options; 10992 10993 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) { 10994 return options.minNativeZoom; 10995 } 10996 10997 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) { 10998 return options.maxNativeZoom; 10999 } 11000 11001 return zoom; 11002 }, 11003 11004 _setView: function (center, zoom, noPrune, noUpdate) { 11005 var tileZoom = this._clampZoom(Math.round(zoom)); 11006 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 11007 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 11008 tileZoom = undefined; 11009 } 11010 11011 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 11012 11013 if (!noUpdate || tileZoomChanged) { 11014 11015 this._tileZoom = tileZoom; 11016 11017 if (this._abortLoading) { 11018 this._abortLoading(); 11019 } 11020 11021 this._updateLevels(); 11022 this._resetGrid(); 11023 11024 if (tileZoom !== undefined) { 11025 this._update(center); 11026 } 11027 11028 if (!noPrune) { 11029 this._pruneTiles(); 11030 } 11031 11032 // Flag to prevent _updateOpacity from pruning tiles during 11033 // a zoom anim or a pinch gesture 11034 this._noPrune = !!noPrune; 11035 } 11036 11037 this._setZoomTransforms(center, zoom); 11038 }, 11039 11040 _setZoomTransforms: function (center, zoom) { 11041 for (var i in this._levels) { 11042 this._setZoomTransform(this._levels[i], center, zoom); 11043 } 11044 }, 11045 11046 _setZoomTransform: function (level, center, zoom) { 11047 var scale = this._map.getZoomScale(zoom, level.zoom), 11048 translate = level.origin.multiplyBy(scale) 11049 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 11050 11051 if (any3d) { 11052 setTransform(level.el, translate, scale); 11053 } else { 11054 setPosition(level.el, translate); 11055 } 11056 }, 11057 11058 _resetGrid: function () { 11059 var map = this._map, 11060 crs = map.options.crs, 11061 tileSize = this._tileSize = this.getTileSize(), 11062 tileZoom = this._tileZoom; 11063 11064 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 11065 if (bounds) { 11066 this._globalTileRange = this._pxBoundsToTileRange(bounds); 11067 } 11068 11069 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 11070 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 11071 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 11072 ]; 11073 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 11074 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 11075 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 11076 ]; 11077 }, 11078 11079 _onMoveEnd: function () { 11080 if (!this._map || this._map._animatingZoom) { return; } 11081 11082 this._update(); 11083 }, 11084 11085 _getTiledPixelBounds: function (center) { 11086 var map = this._map, 11087 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 11088 scale = map.getZoomScale(mapZoom, this._tileZoom), 11089 pixelCenter = map.project(center, this._tileZoom).floor(), 11090 halfSize = map.getSize().divideBy(scale * 2); 11091 11092 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 11093 }, 11094 11095 // Private method to load tiles in the grid's active zoom level according to map bounds 11096 _update: function (center) { 11097 var map = this._map; 11098 if (!map) { return; } 11099 var zoom = this._clampZoom(map.getZoom()); 11100 11101 if (center === undefined) { center = map.getCenter(); } 11102 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 11103 11104 var pixelBounds = this._getTiledPixelBounds(center), 11105 tileRange = this._pxBoundsToTileRange(pixelBounds), 11106 tileCenter = tileRange.getCenter(), 11107 queue = [], 11108 margin = this.options.keepBuffer, 11109 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 11110 tileRange.getTopRight().add([margin, -margin])); 11111 11112 // Sanity check: panic if the tile range contains Infinity somewhere. 11113 if (!(isFinite(tileRange.min.x) && 11114 isFinite(tileRange.min.y) && 11115 isFinite(tileRange.max.x) && 11116 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 11117 11118 for (var key in this._tiles) { 11119 var c = this._tiles[key].coords; 11120 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 11121 this._tiles[key].current = false; 11122 } 11123 } 11124 11125 // _update just loads more tiles. If the tile zoom level differs too much 11126 // from the map's, let _setView reset levels and prune old tiles. 11127 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 11128 11129 // create a queue of coordinates to load tiles from 11130 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 11131 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 11132 var coords = new Point(i, j); 11133 coords.z = this._tileZoom; 11134 11135 if (!this._isValidTile(coords)) { continue; } 11136 11137 var tile = this._tiles[this._tileCoordsToKey(coords)]; 11138 if (tile) { 11139 tile.current = true; 11140 } else { 11141 queue.push(coords); 11142 } 11143 } 11144 } 11145 11146 // sort tile queue to load tiles in order of their distance to center 11147 queue.sort(function (a, b) { 11148 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 11149 }); 11150 11151 if (queue.length !== 0) { 11152 // if it's the first batch of tiles to load 11153 if (!this._loading) { 11154 this._loading = true; 11155 // @event loading: Event 11156 // Fired when the grid layer starts loading tiles. 11157 this.fire('loading'); 11158 } 11159 11160 // create DOM fragment to append tiles in one batch 11161 var fragment = document.createDocumentFragment(); 11162 11163 for (i = 0; i < queue.length; i++) { 11164 this._addTile(queue[i], fragment); 11165 } 11166 11167 this._level.el.appendChild(fragment); 11168 } 11169 }, 11170 11171 _isValidTile: function (coords) { 11172 var crs = this._map.options.crs; 11173 11174 if (!crs.infinite) { 11175 // don't load tile if it's out of bounds and not wrapped 11176 var bounds = this._globalTileRange; 11177 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 11178 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 11179 } 11180 11181 if (!this.options.bounds) { return true; } 11182 11183 // don't load tile if it doesn't intersect the bounds in options 11184 var tileBounds = this._tileCoordsToBounds(coords); 11185 return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 11186 }, 11187 11188 _keyToBounds: function (key) { 11189 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 11190 }, 11191 11192 _tileCoordsToNwSe: function (coords) { 11193 var map = this._map, 11194 tileSize = this.getTileSize(), 11195 nwPoint = coords.scaleBy(tileSize), 11196 sePoint = nwPoint.add(tileSize), 11197 nw = map.unproject(nwPoint, coords.z), 11198 se = map.unproject(sePoint, coords.z); 11199 return [nw, se]; 11200 }, 11201 11202 // converts tile coordinates to its geographical bounds 11203 _tileCoordsToBounds: function (coords) { 11204 var bp = this._tileCoordsToNwSe(coords), 11205 bounds = new LatLngBounds(bp[0], bp[1]); 11206 11207 if (!this.options.noWrap) { 11208 bounds = this._map.wrapLatLngBounds(bounds); 11209 } 11210 return bounds; 11211 }, 11212 // converts tile coordinates to key for the tile cache 11213 _tileCoordsToKey: function (coords) { 11214 return coords.x + ':' + coords.y + ':' + coords.z; 11215 }, 11216 11217 // converts tile cache key to coordinates 11218 _keyToTileCoords: function (key) { 11219 var k = key.split(':'), 11220 coords = new Point(+k[0], +k[1]); 11221 coords.z = +k[2]; 11222 return coords; 11223 }, 11224 11225 _removeTile: function (key) { 11226 var tile = this._tiles[key]; 11227 if (!tile) { return; } 11228 11229 remove(tile.el); 11230 11231 delete this._tiles[key]; 11232 11233 // @event tileunload: TileEvent 11234 // Fired when a tile is removed (e.g. when a tile goes off the screen). 11235 this.fire('tileunload', { 11236 tile: tile.el, 11237 coords: this._keyToTileCoords(key) 11238 }); 11239 }, 11240 11241 _initTile: function (tile) { 11242 addClass(tile, 'leaflet-tile'); 11243 11244 var tileSize = this.getTileSize(); 11245 tile.style.width = tileSize.x + 'px'; 11246 tile.style.height = tileSize.y + 'px'; 11247 11248 tile.onselectstart = falseFn; 11249 tile.onmousemove = falseFn; 11250 11251 // update opacity on tiles in IE7-8 because of filter inheritance problems 11252 if (ielt9 && this.options.opacity < 1) { 11253 setOpacity(tile, this.options.opacity); 11254 } 11255 11256 // without this hack, tiles disappear after zoom on Chrome for Android 11257 // https://github.com/Leaflet/Leaflet/issues/2078 11258 if (android && !android23) { 11259 tile.style.WebkitBackfaceVisibility = 'hidden'; 11260 } 11261 }, 11262 11263 _addTile: function (coords, container) { 11264 var tilePos = this._getTilePos(coords), 11265 key = this._tileCoordsToKey(coords); 11266 11267 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 11268 11269 this._initTile(tile); 11270 11271 // if createTile is defined with a second argument ("done" callback), 11272 // we know that tile is async and will be ready later; otherwise 11273 if (this.createTile.length < 2) { 11274 // mark tile as ready, but delay one frame for opacity animation to happen 11275 requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 11276 } 11277 11278 setPosition(tile, tilePos); 11279 11280 // save tile in cache 11281 this._tiles[key] = { 11282 el: tile, 11283 coords: coords, 11284 current: true 11285 }; 11286 11287 container.appendChild(tile); 11288 // @event tileloadstart: TileEvent 11289 // Fired when a tile is requested and starts loading. 11290 this.fire('tileloadstart', { 11291 tile: tile, 11292 coords: coords 11293 }); 11294 }, 11295 11296 _tileReady: function (coords, err, tile) { 11297 if (err) { 11298 // @event tileerror: TileErrorEvent 11299 // Fired when there is an error loading a tile. 11300 this.fire('tileerror', { 11301 error: err, 11302 tile: tile, 11303 coords: coords 11304 }); 11305 } 11306 11307 var key = this._tileCoordsToKey(coords); 11308 11309 tile = this._tiles[key]; 11310 if (!tile) { return; } 11311 11312 tile.loaded = +new Date(); 11313 if (this._map._fadeAnimated) { 11314 setOpacity(tile.el, 0); 11315 cancelAnimFrame(this._fadeFrame); 11316 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11317 } else { 11318 tile.active = true; 11319 this._pruneTiles(); 11320 } 11321 11322 if (!err) { 11323 addClass(tile.el, 'leaflet-tile-loaded'); 11324 11325 // @event tileload: TileEvent 11326 // Fired when a tile loads. 11327 this.fire('tileload', { 11328 tile: tile.el, 11329 coords: coords 11330 }); 11331 } 11332 11333 if (this._noTilesToLoad()) { 11334 this._loading = false; 11335 // @event load: Event 11336 // Fired when the grid layer loaded all visible tiles. 11337 this.fire('load'); 11338 11339 if (ielt9 || !this._map._fadeAnimated) { 11340 requestAnimFrame(this._pruneTiles, this); 11341 } else { 11342 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 11343 // to trigger a pruning. 11344 setTimeout(bind(this._pruneTiles, this), 250); 11345 } 11346 } 11347 }, 11348 11349 _getTilePos: function (coords) { 11350 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 11351 }, 11352 11353 _wrapCoords: function (coords) { 11354 var newCoords = new Point( 11355 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 11356 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y); 11357 newCoords.z = coords.z; 11358 return newCoords; 11359 }, 11360 11361 _pxBoundsToTileRange: function (bounds) { 11362 var tileSize = this.getTileSize(); 11363 return new Bounds( 11364 bounds.min.unscaleBy(tileSize).floor(), 11365 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 11366 }, 11367 11368 _noTilesToLoad: function () { 11369 for (var key in this._tiles) { 11370 if (!this._tiles[key].loaded) { return false; } 11371 } 11372 return true; 11373 } 11374}); 11375 11376// @factory L.gridLayer(options?: GridLayer options) 11377// Creates a new instance of GridLayer with the supplied options. 11378function gridLayer(options) { 11379 return new GridLayer(options); 11380} 11381 11382/* 11383 * @class TileLayer 11384 * @inherits GridLayer 11385 * @aka L.TileLayer 11386 * Used to load and display tile layers on the map. Extends `GridLayer`. 11387 * 11388 * @example 11389 * 11390 * ```js 11391 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map); 11392 * ``` 11393 * 11394 * @section URL template 11395 * @example 11396 * 11397 * A string of the following form: 11398 * 11399 * ``` 11400 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 11401 * ``` 11402 * 11403 * `{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. 11404 * 11405 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 11406 * 11407 * ``` 11408 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 11409 * ``` 11410 */ 11411 11412 11413var TileLayer = GridLayer.extend({ 11414 11415 // @section 11416 // @aka TileLayer options 11417 options: { 11418 // @option minZoom: Number = 0 11419 // The minimum zoom level down to which this layer will be displayed (inclusive). 11420 minZoom: 0, 11421 11422 // @option maxZoom: Number = 18 11423 // The maximum zoom level up to which this layer will be displayed (inclusive). 11424 maxZoom: 18, 11425 11426 // @option subdomains: String|String[] = 'abc' 11427 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings. 11428 subdomains: 'abc', 11429 11430 // @option errorTileUrl: String = '' 11431 // URL to the tile image to show in place of the tile that failed to load. 11432 errorTileUrl: '', 11433 11434 // @option zoomOffset: Number = 0 11435 // The zoom number used in tile URLs will be offset with this value. 11436 zoomOffset: 0, 11437 11438 // @option tms: Boolean = false 11439 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 11440 tms: false, 11441 11442 // @option zoomReverse: Boolean = false 11443 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 11444 zoomReverse: false, 11445 11446 // @option detectRetina: Boolean = false 11447 // 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. 11448 detectRetina: false, 11449 11450 // @option crossOrigin: Boolean|String = false 11451 // Whether the crossOrigin attribute will be added to the tiles. 11452 // 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. 11453 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 11454 crossOrigin: false 11455 }, 11456 11457 initialize: function (url, options) { 11458 11459 this._url = url; 11460 11461 options = setOptions(this, options); 11462 11463 // detecting retina displays, adjusting tileSize and zoom levels 11464 if (options.detectRetina && retina && options.maxZoom > 0) { 11465 11466 options.tileSize = Math.floor(options.tileSize / 2); 11467 11468 if (!options.zoomReverse) { 11469 options.zoomOffset++; 11470 options.maxZoom--; 11471 } else { 11472 options.zoomOffset--; 11473 options.minZoom++; 11474 } 11475 11476 options.minZoom = Math.max(0, options.minZoom); 11477 } 11478 11479 if (typeof options.subdomains === 'string') { 11480 options.subdomains = options.subdomains.split(''); 11481 } 11482 11483 // for https://github.com/Leaflet/Leaflet/issues/137 11484 if (!android) { 11485 this.on('tileunload', this._onTileRemove); 11486 } 11487 }, 11488 11489 // @method setUrl(url: String, noRedraw?: Boolean): this 11490 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 11491 setUrl: function (url, noRedraw) { 11492 this._url = url; 11493 11494 if (!noRedraw) { 11495 this.redraw(); 11496 } 11497 return this; 11498 }, 11499 11500 // @method createTile(coords: Object, done?: Function): HTMLElement 11501 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 11502 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 11503 // callback is called when the tile has been loaded. 11504 createTile: function (coords, done) { 11505 var tile = document.createElement('img'); 11506 11507 on(tile, 'load', bind(this._tileOnLoad, this, done, tile)); 11508 on(tile, 'error', bind(this._tileOnError, this, done, tile)); 11509 11510 if (this.options.crossOrigin || this.options.crossOrigin === '') { 11511 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 11512 } 11513 11514 /* 11515 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons 11516 http://www.w3.org/TR/WCAG20-TECHS/H67 11517 */ 11518 tile.alt = ''; 11519 11520 /* 11521 Set role="presentation" to force screen readers to ignore this 11522 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation 11523 */ 11524 tile.setAttribute('role', 'presentation'); 11525 11526 tile.src = this.getTileUrl(coords); 11527 11528 return tile; 11529 }, 11530 11531 // @section Extension methods 11532 // @uninheritable 11533 // Layers extending `TileLayer` might reimplement the following method. 11534 // @method getTileUrl(coords: Object): String 11535 // Called only internally, returns the URL for a tile given its coordinates. 11536 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 11537 getTileUrl: function (coords) { 11538 var data = { 11539 r: retina ? '@2x' : '', 11540 s: this._getSubdomain(coords), 11541 x: coords.x, 11542 y: coords.y, 11543 z: this._getZoomForUrl() 11544 }; 11545 if (this._map && !this._map.options.crs.infinite) { 11546 var invertedY = this._globalTileRange.max.y - coords.y; 11547 if (this.options.tms) { 11548 data['y'] = invertedY; 11549 } 11550 data['-y'] = invertedY; 11551 } 11552 11553 return template(this._url, extend(data, this.options)); 11554 }, 11555 11556 _tileOnLoad: function (done, tile) { 11557 // For https://github.com/Leaflet/Leaflet/issues/3332 11558 if (ielt9) { 11559 setTimeout(bind(done, this, null, tile), 0); 11560 } else { 11561 done(null, tile); 11562 } 11563 }, 11564 11565 _tileOnError: function (done, tile, e) { 11566 var errorUrl = this.options.errorTileUrl; 11567 if (errorUrl && tile.getAttribute('src') !== errorUrl) { 11568 tile.src = errorUrl; 11569 } 11570 done(e, tile); 11571 }, 11572 11573 _onTileRemove: function (e) { 11574 e.tile.onload = null; 11575 }, 11576 11577 _getZoomForUrl: function () { 11578 var zoom = this._tileZoom, 11579 maxZoom = this.options.maxZoom, 11580 zoomReverse = this.options.zoomReverse, 11581 zoomOffset = this.options.zoomOffset; 11582 11583 if (zoomReverse) { 11584 zoom = maxZoom - zoom; 11585 } 11586 11587 return zoom + zoomOffset; 11588 }, 11589 11590 _getSubdomain: function (tilePoint) { 11591 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 11592 return this.options.subdomains[index]; 11593 }, 11594 11595 // stops loading all tiles in the background layer 11596 _abortLoading: function () { 11597 var i, tile; 11598 for (i in this._tiles) { 11599 if (this._tiles[i].coords.z !== this._tileZoom) { 11600 tile = this._tiles[i].el; 11601 11602 tile.onload = falseFn; 11603 tile.onerror = falseFn; 11604 11605 if (!tile.complete) { 11606 tile.src = emptyImageUrl; 11607 remove(tile); 11608 delete this._tiles[i]; 11609 } 11610 } 11611 } 11612 }, 11613 11614 _removeTile: function (key) { 11615 var tile = this._tiles[key]; 11616 if (!tile) { return; } 11617 11618 // Cancels any pending http requests associated with the tile 11619 // unless we're on Android's stock browser, 11620 // see https://github.com/Leaflet/Leaflet/issues/137 11621 if (!androidStock) { 11622 tile.el.setAttribute('src', emptyImageUrl); 11623 } 11624 11625 return GridLayer.prototype._removeTile.call(this, key); 11626 }, 11627 11628 _tileReady: function (coords, err, tile) { 11629 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) { 11630 return; 11631 } 11632 11633 return GridLayer.prototype._tileReady.call(this, coords, err, tile); 11634 } 11635}); 11636 11637 11638// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 11639// Instantiates a tile layer object given a `URL template` and optionally an options object. 11640 11641function tileLayer(url, options) { 11642 return new TileLayer(url, options); 11643} 11644 11645/* 11646 * @class TileLayer.WMS 11647 * @inherits TileLayer 11648 * @aka L.TileLayer.WMS 11649 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 11650 * 11651 * @example 11652 * 11653 * ```js 11654 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 11655 * layers: 'nexrad-n0r-900913', 11656 * format: 'image/png', 11657 * transparent: true, 11658 * attribution: "Weather data © 2012 IEM Nexrad" 11659 * }); 11660 * ``` 11661 */ 11662 11663var TileLayerWMS = TileLayer.extend({ 11664 11665 // @section 11666 // @aka TileLayer.WMS options 11667 // If any custom options not documented here are used, they will be sent to the 11668 // WMS server as extra parameters in each request URL. This can be useful for 11669 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 11670 defaultWmsParams: { 11671 service: 'WMS', 11672 request: 'GetMap', 11673 11674 // @option layers: String = '' 11675 // **(required)** Comma-separated list of WMS layers to show. 11676 layers: '', 11677 11678 // @option styles: String = '' 11679 // Comma-separated list of WMS styles. 11680 styles: '', 11681 11682 // @option format: String = 'image/jpeg' 11683 // WMS image format (use `'image/png'` for layers with transparency). 11684 format: 'image/jpeg', 11685 11686 // @option transparent: Boolean = false 11687 // If `true`, the WMS service will return images with transparency. 11688 transparent: false, 11689 11690 // @option version: String = '1.1.1' 11691 // Version of the WMS service to use 11692 version: '1.1.1' 11693 }, 11694 11695 options: { 11696 // @option crs: CRS = null 11697 // Coordinate Reference System to use for the WMS requests, defaults to 11698 // map CRS. Don't change this if you're not sure what it means. 11699 crs: null, 11700 11701 // @option uppercase: Boolean = false 11702 // If `true`, WMS request parameter keys will be uppercase. 11703 uppercase: false 11704 }, 11705 11706 initialize: function (url, options) { 11707 11708 this._url = url; 11709 11710 var wmsParams = extend({}, this.defaultWmsParams); 11711 11712 // all keys that are not TileLayer options go to WMS params 11713 for (var i in options) { 11714 if (!(i in this.options)) { 11715 wmsParams[i] = options[i]; 11716 } 11717 } 11718 11719 options = setOptions(this, options); 11720 11721 var realRetina = options.detectRetina && retina ? 2 : 1; 11722 var tileSize = this.getTileSize(); 11723 wmsParams.width = tileSize.x * realRetina; 11724 wmsParams.height = tileSize.y * realRetina; 11725 11726 this.wmsParams = wmsParams; 11727 }, 11728 11729 onAdd: function (map) { 11730 11731 this._crs = this.options.crs || map.options.crs; 11732 this._wmsVersion = parseFloat(this.wmsParams.version); 11733 11734 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 11735 this.wmsParams[projectionKey] = this._crs.code; 11736 11737 TileLayer.prototype.onAdd.call(this, map); 11738 }, 11739 11740 getTileUrl: function (coords) { 11741 11742 var tileBounds = this._tileCoordsToNwSe(coords), 11743 crs = this._crs, 11744 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), 11745 min = bounds.min, 11746 max = bounds.max, 11747 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? 11748 [min.y, min.x, max.y, max.x] : 11749 [min.x, min.y, max.x, max.y]).join(','), 11750 url = TileLayer.prototype.getTileUrl.call(this, coords); 11751 return url + 11752 getParamString(this.wmsParams, url, this.options.uppercase) + 11753 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 11754 }, 11755 11756 // @method setParams(params: Object, noRedraw?: Boolean): this 11757 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 11758 setParams: function (params, noRedraw) { 11759 11760 extend(this.wmsParams, params); 11761 11762 if (!noRedraw) { 11763 this.redraw(); 11764 } 11765 11766 return this; 11767 } 11768}); 11769 11770 11771// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 11772// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 11773function tileLayerWMS(url, options) { 11774 return new TileLayerWMS(url, options); 11775} 11776 11777TileLayer.WMS = TileLayerWMS; 11778tileLayer.wms = tileLayerWMS; 11779 11780/* 11781 * @class Renderer 11782 * @inherits Layer 11783 * @aka L.Renderer 11784 * 11785 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 11786 * DOM container of the renderer, its bounds, and its zoom animation. 11787 * 11788 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 11789 * itself can be added or removed to the map. All paths use a renderer, which can 11790 * be implicit (the map will decide the type of renderer and use it automatically) 11791 * or explicit (using the [`renderer`](#path-renderer) option of the path). 11792 * 11793 * Do not use this class directly, use `SVG` and `Canvas` instead. 11794 * 11795 * @event update: Event 11796 * Fired when the renderer updates its bounds, center and zoom, for example when 11797 * its map has moved 11798 */ 11799 11800var Renderer = Layer.extend({ 11801 11802 // @section 11803 // @aka Renderer options 11804 options: { 11805 // @option padding: Number = 0.1 11806 // How much to extend the clip area around the map view (relative to its size) 11807 // e.g. 0.1 would be 10% of map view in each direction 11808 padding: 0.1, 11809 11810 // @option tolerance: Number = 0 11811 // How much to extend click tolerance round a path/object on the map 11812 tolerance : 0 11813 }, 11814 11815 initialize: function (options) { 11816 setOptions(this, options); 11817 stamp(this); 11818 this._layers = this._layers || {}; 11819 }, 11820 11821 onAdd: function () { 11822 if (!this._container) { 11823 this._initContainer(); // defined by renderer implementations 11824 11825 if (this._zoomAnimated) { 11826 addClass(this._container, 'leaflet-zoom-animated'); 11827 } 11828 } 11829 11830 this.getPane().appendChild(this._container); 11831 this._update(); 11832 this.on('update', this._updatePaths, this); 11833 }, 11834 11835 onRemove: function () { 11836 this.off('update', this._updatePaths, this); 11837 this._destroyContainer(); 11838 }, 11839 11840 getEvents: function () { 11841 var events = { 11842 viewreset: this._reset, 11843 zoom: this._onZoom, 11844 moveend: this._update, 11845 zoomend: this._onZoomEnd 11846 }; 11847 if (this._zoomAnimated) { 11848 events.zoomanim = this._onAnimZoom; 11849 } 11850 return events; 11851 }, 11852 11853 _onAnimZoom: function (ev) { 11854 this._updateTransform(ev.center, ev.zoom); 11855 }, 11856 11857 _onZoom: function () { 11858 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 11859 }, 11860 11861 _updateTransform: function (center, zoom) { 11862 var scale = this._map.getZoomScale(zoom, this._zoom), 11863 position = getPosition(this._container), 11864 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 11865 currentCenterPoint = this._map.project(this._center, zoom), 11866 destCenterPoint = this._map.project(center, zoom), 11867 centerOffset = destCenterPoint.subtract(currentCenterPoint), 11868 11869 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset); 11870 11871 if (any3d) { 11872 setTransform(this._container, topLeftOffset, scale); 11873 } else { 11874 setPosition(this._container, topLeftOffset); 11875 } 11876 }, 11877 11878 _reset: function () { 11879 this._update(); 11880 this._updateTransform(this._center, this._zoom); 11881 11882 for (var id in this._layers) { 11883 this._layers[id]._reset(); 11884 } 11885 }, 11886 11887 _onZoomEnd: function () { 11888 for (var id in this._layers) { 11889 this._layers[id]._project(); 11890 } 11891 }, 11892 11893 _updatePaths: function () { 11894 for (var id in this._layers) { 11895 this._layers[id]._update(); 11896 } 11897 }, 11898 11899 _update: function () { 11900 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 11901 // Subclasses are responsible of firing the 'update' event. 11902 var p = this.options.padding, 11903 size = this._map.getSize(), 11904 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 11905 11906 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 11907 11908 this._center = this._map.getCenter(); 11909 this._zoom = this._map.getZoom(); 11910 } 11911}); 11912 11913/* 11914 * @class Canvas 11915 * @inherits Renderer 11916 * @aka L.Canvas 11917 * 11918 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 11919 * Inherits `Renderer`. 11920 * 11921 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not 11922 * available in all web browsers, notably IE8, and overlapping geometries might 11923 * not display properly in some edge cases. 11924 * 11925 * @example 11926 * 11927 * Use Canvas by default for all paths in the map: 11928 * 11929 * ```js 11930 * var map = L.map('map', { 11931 * renderer: L.canvas() 11932 * }); 11933 * ``` 11934 * 11935 * Use a Canvas renderer with extra padding for specific vector geometries: 11936 * 11937 * ```js 11938 * var map = L.map('map'); 11939 * var myRenderer = L.canvas({ padding: 0.5 }); 11940 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 11941 * var circle = L.circle( center, { renderer: myRenderer } ); 11942 * ``` 11943 */ 11944 11945var Canvas = Renderer.extend({ 11946 getEvents: function () { 11947 var events = Renderer.prototype.getEvents.call(this); 11948 events.viewprereset = this._onViewPreReset; 11949 return events; 11950 }, 11951 11952 _onViewPreReset: function () { 11953 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 11954 this._postponeUpdatePaths = true; 11955 }, 11956 11957 onAdd: function () { 11958 Renderer.prototype.onAdd.call(this); 11959 11960 // Redraw vectors since canvas is cleared upon removal, 11961 // in case of removing the renderer itself from the map. 11962 this._draw(); 11963 }, 11964 11965 _initContainer: function () { 11966 var container = this._container = document.createElement('canvas'); 11967 11968 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this); 11969 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); 11970 on(container, 'mouseout', this._handleMouseOut, this); 11971 11972 this._ctx = container.getContext('2d'); 11973 }, 11974 11975 _destroyContainer: function () { 11976 cancelAnimFrame(this._redrawRequest); 11977 delete this._ctx; 11978 remove(this._container); 11979 off(this._container); 11980 delete this._container; 11981 }, 11982 11983 _updatePaths: function () { 11984 if (this._postponeUpdatePaths) { return; } 11985 11986 var layer; 11987 this._redrawBounds = null; 11988 for (var id in this._layers) { 11989 layer = this._layers[id]; 11990 layer._update(); 11991 } 11992 this._redraw(); 11993 }, 11994 11995 _update: function () { 11996 if (this._map._animatingZoom && this._bounds) { return; } 11997 11998 this._drawnLayers = {}; 11999 12000 Renderer.prototype._update.call(this); 12001 12002 var b = this._bounds, 12003 container = this._container, 12004 size = b.getSize(), 12005 m = retina ? 2 : 1; 12006 12007 setPosition(container, b.min); 12008 12009 // set canvas size (also clearing it); use double size on retina 12010 container.width = m * size.x; 12011 container.height = m * size.y; 12012 container.style.width = size.x + 'px'; 12013 container.style.height = size.y + 'px'; 12014 12015 if (retina) { 12016 this._ctx.scale(2, 2); 12017 } 12018 12019 // translate so we use the same path coordinates after canvas element moves 12020 this._ctx.translate(-b.min.x, -b.min.y); 12021 12022 // Tell paths to redraw themselves 12023 this.fire('update'); 12024 }, 12025 12026 _reset: function () { 12027 Renderer.prototype._reset.call(this); 12028 12029 if (this._postponeUpdatePaths) { 12030 this._postponeUpdatePaths = false; 12031 this._updatePaths(); 12032 } 12033 }, 12034 12035 _initPath: function (layer) { 12036 this._updateDashArray(layer); 12037 this._layers[stamp(layer)] = layer; 12038 12039 var order = layer._order = { 12040 layer: layer, 12041 prev: this._drawLast, 12042 next: null 12043 }; 12044 if (this._drawLast) { this._drawLast.next = order; } 12045 this._drawLast = order; 12046 this._drawFirst = this._drawFirst || this._drawLast; 12047 }, 12048 12049 _addPath: function (layer) { 12050 this._requestRedraw(layer); 12051 }, 12052 12053 _removePath: function (layer) { 12054 var order = layer._order; 12055 var next = order.next; 12056 var prev = order.prev; 12057 12058 if (next) { 12059 next.prev = prev; 12060 } else { 12061 this._drawLast = prev; 12062 } 12063 if (prev) { 12064 prev.next = next; 12065 } else { 12066 this._drawFirst = next; 12067 } 12068 12069 delete this._drawnLayers[layer._leaflet_id]; 12070 12071 delete layer._order; 12072 12073 delete this._layers[stamp(layer)]; 12074 12075 this._requestRedraw(layer); 12076 }, 12077 12078 _updatePath: function (layer) { 12079 // Redraw the union of the layer's old pixel 12080 // bounds and the new pixel bounds. 12081 this._extendRedrawBounds(layer); 12082 layer._project(); 12083 layer._update(); 12084 // The redraw will extend the redraw bounds 12085 // with the new pixel bounds. 12086 this._requestRedraw(layer); 12087 }, 12088 12089 _updateStyle: function (layer) { 12090 this._updateDashArray(layer); 12091 this._requestRedraw(layer); 12092 }, 12093 12094 _updateDashArray: function (layer) { 12095 if (typeof layer.options.dashArray === 'string') { 12096 var parts = layer.options.dashArray.split(/[, ]+/), 12097 dashArray = [], 12098 i; 12099 for (i = 0; i < parts.length; i++) { 12100 dashArray.push(Number(parts[i])); 12101 } 12102 layer.options._dashArray = dashArray; 12103 } else { 12104 layer.options._dashArray = layer.options.dashArray; 12105 } 12106 }, 12107 12108 _requestRedraw: function (layer) { 12109 if (!this._map) { return; } 12110 12111 this._extendRedrawBounds(layer); 12112 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 12113 }, 12114 12115 _extendRedrawBounds: function (layer) { 12116 if (layer._pxBounds) { 12117 var padding = (layer.options.weight || 0) + 1; 12118 this._redrawBounds = this._redrawBounds || new Bounds(); 12119 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 12120 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 12121 } 12122 }, 12123 12124 _redraw: function () { 12125 this._redrawRequest = null; 12126 12127 if (this._redrawBounds) { 12128 this._redrawBounds.min._floor(); 12129 this._redrawBounds.max._ceil(); 12130 } 12131 12132 this._clear(); // clear layers in redraw bounds 12133 this._draw(); // draw layers 12134 12135 this._redrawBounds = null; 12136 }, 12137 12138 _clear: function () { 12139 var bounds = this._redrawBounds; 12140 if (bounds) { 12141 var size = bounds.getSize(); 12142 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 12143 } else { 12144 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 12145 } 12146 }, 12147 12148 _draw: function () { 12149 var layer, bounds = this._redrawBounds; 12150 this._ctx.save(); 12151 if (bounds) { 12152 var size = bounds.getSize(); 12153 this._ctx.beginPath(); 12154 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 12155 this._ctx.clip(); 12156 } 12157 12158 this._drawing = true; 12159 12160 for (var order = this._drawFirst; order; order = order.next) { 12161 layer = order.layer; 12162 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 12163 layer._updatePath(); 12164 } 12165 } 12166 12167 this._drawing = false;
12168 12169 this._ctx.restore(); // Restore state before clipping. 12170 }, 12171 12172 _updatePoly: function (layer, closed) { 12173 if (!this._drawing) { return; } 12174 12175 var i, j, len2, p, 12176 parts = layer._parts, 12177 len = parts.length, 12178 ctx = this._ctx; 12179 12180 if (!len) { return; } 12181 12182 this._drawnLayers[layer._leaflet_id] = layer; 12183 12184 ctx.beginPath(); 12185 12186 for (i = 0; i < len; i++) { 12187 for (j = 0, len2 = parts[i].length; j < len2; j++) { 12188 p = parts[i][j]; 12189 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 12190 } 12191 if (closed) { 12192 ctx.closePath(); 12193 } 12194 } 12195 12196 this._fillStroke(ctx, layer); 12197 12198 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 12199 }, 12200 12201 _updateCircle: function (layer) { 12202 12203 if (!this._drawing || layer._empty()) { return; } 12204 12205 var p = layer._point, 12206 ctx = this._ctx, 12207 r = Math.max(Math.round(layer._radius), 1), 12208 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 12209 12210 this._drawnLayers[layer._leaflet_id] = layer; 12211 12212 if (s !== 1) { 12213 ctx.save(); 12214 ctx.scale(1, s); 12215 } 12216 12217 ctx.beginPath(); 12218 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 12219 12220 if (s !== 1) { 12221 ctx.restore(); 12222 } 12223 12224 this._fillStroke(ctx, layer); 12225 }, 12226 12227 _fillStroke: function (ctx, layer) { 12228 var options = layer.options; 12229 12230 if (options.fill) { 12231 ctx.globalAlpha = options.fillOpacity; 12232 ctx.fillStyle = options.fillColor || options.color; 12233 ctx.fill(options.fillRule || 'evenodd'); 12234 } 12235 12236 if (options.stroke && options.weight !== 0) { 12237 if (ctx.setLineDash) { 12238 ctx.setLineDash(layer.options && layer.options._dashArray || []); 12239 } 12240 ctx.globalAlpha = options.opacity; 12241 ctx.lineWidth = options.weight; 12242 ctx.strokeStyle = options.color; 12243 ctx.lineCap = options.lineCap; 12244 ctx.lineJoin = options.lineJoin; 12245 ctx.stroke(); 12246 } 12247 }, 12248 12249 // Canvas obviously doesn't have mouse events for individual drawn objects, 12250 // so we emulate that by calculating what's under the mouse on mousemove/click manually 12251 12252 _onClick: function (e) { 12253 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 12254 12255 for (var order = this._drawFirst; order; order = order.next) { 12256 layer = order.layer; 12257 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) { 12258 clickedLayer = layer; 12259 } 12260 } 12261 if (clickedLayer) { 12262 fakeStop(e); 12263 this._fireEvent([clickedLayer], e); 12264 } 12265 }, 12266 12267 _onMouseMove: function (e) { 12268 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 12269 12270 var point = this._map.mouseEventToLayerPoint(e); 12271 this._handleMouseHover(e, point); 12272 }, 12273 12274 12275 _handleMouseOut: function (e) { 12276 var layer = this._hoveredLayer; 12277 if (layer) { 12278 // if we're leaving the layer, fire mouseout 12279 removeClass(this._container, 'leaflet-interactive'); 12280 this._fireEvent([layer], e, 'mouseout'); 12281 this._hoveredLayer = null; 12282 } 12283 }, 12284 12285 _handleMouseHover: function (e, point) { 12286 var layer, candidateHoveredLayer; 12287 12288 for (var order = this._drawFirst; order; order = order.next) { 12289 layer = order.layer; 12290 if (layer.options.interactive && layer._containsPoint(point)) { 12291 candidateHoveredLayer = layer; 12292 } 12293 } 12294 12295 if (candidateHoveredLayer !== this._hoveredLayer) { 12296 this._handleMouseOut(e); 12297 12298 if (candidateHoveredLayer) { 12299 addClass(this._container, 'leaflet-interactive'); // change cursor 12300 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 12301 this._hoveredLayer = candidateHoveredLayer; 12302 } 12303 } 12304 12305 if (this._hoveredLayer) { 12306 this._fireEvent([this._hoveredLayer], e); 12307 } 12308 }, 12309 12310 _fireEvent: function (layers, e, type) { 12311 this._map._fireDOMEvent(e, type || e.type, layers); 12312 }, 12313 12314 _bringToFront: function (layer) { 12315 var order = layer._order; 12316 var next = order.next; 12317 var prev = order.prev; 12318 12319 if (next) { 12320 next.prev = prev; 12321 } else { 12322 // Already last 12323 return; 12324 } 12325 if (prev) { 12326 prev.next = next; 12327 } else if (next) { 12328 // Update first entry unless this is the 12329 // single entry 12330 this._drawFirst = next; 12331 } 12332 12333 order.prev = this._drawLast; 12334 this._drawLast.next = order; 12335 12336 order.next = null; 12337 this._drawLast = order; 12338 12339 this._requestRedraw(layer); 12340 }, 12341 12342 _bringToBack: function (layer) { 12343 var order = layer._order; 12344 var next = order.next; 12345 var prev = order.prev; 12346 12347 if (prev) { 12348 prev.next = next; 12349 } else { 12350 // Already first 12351 return; 12352 } 12353 if (next) { 12354 next.prev = prev; 12355 } else if (prev) { 12356 // Update last entry unless this is the 12357 // single entry 12358 this._drawLast = prev; 12359 } 12360 12361 order.prev = null; 12362 12363 order.next = this._drawFirst; 12364 this._drawFirst.prev = order; 12365 this._drawFirst = order; 12366 12367 this._requestRedraw(layer); 12368 } 12369}); 12370 12371// @factory L.canvas(options?: Renderer options) 12372// Creates a Canvas renderer with the given options. 12373function canvas$1(options) { 12374 return canvas ? new Canvas(options) : null; 12375} 12376 12377/* 12378 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 12379 */ 12380 12381 12382var vmlCreate = (function () { 12383 try { 12384 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 12385 return function (name) { 12386 return document.createElement('<lvml:' + name + ' class="lvml">'); 12387 }; 12388 } catch (e) { 12389 return function (name) { 12390 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 12391 }; 12392 } 12393})(); 12394 12395 12396/* 12397 * @class SVG 12398 * 12399 * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case. 12400 *
vendor: 27,130 bytes, lines 12401-13431
12401 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 12402 * with old versions of Internet Explorer. 12403 */ 12404 12405// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences 12406var vmlMixin = { 12407 12408 _initContainer: function () { 12409 this._container = create$1('div', 'leaflet-vml-container'); 12410 }, 12411 12412 _update: function () { 12413 if (this._map._animatingZoom) { return; } 12414 Renderer.prototype._update.call(this); 12415 this.fire('update'); 12416 }, 12417 12418 _initPath: function (layer) { 12419 var container = layer._container = vmlCreate('shape'); 12420 12421 addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 12422 12423 container.coordsize = '1 1'; 12424 12425 layer._path = vmlCreate('path'); 12426 container.appendChild(layer._path); 12427 12428 this._updateStyle(layer); 12429 this._layers[stamp(layer)] = layer; 12430 }, 12431 12432 _addPath: function (layer) { 12433 var container = layer._container; 12434 this._container.appendChild(container); 12435 12436 if (layer.options.interactive) { 12437 layer.addInteractiveTarget(container); 12438 } 12439 }, 12440 12441 _removePath: function (layer) { 12442 var container = layer._container; 12443 remove(container); 12444 layer.removeInteractiveTarget(container); 12445 delete this._layers[stamp(layer)]; 12446 }, 12447 12448 _updateStyle: function (layer) { 12449 var stroke = layer._stroke, 12450 fill = layer._fill, 12451 options = layer.options, 12452 container = layer._container; 12453 12454 container.stroked = !!options.stroke; 12455 container.filled = !!options.fill; 12456 12457 if (options.stroke) { 12458 if (!stroke) { 12459 stroke = layer._stroke = vmlCreate('stroke'); 12460 } 12461 container.appendChild(stroke); 12462 stroke.weight = options.weight + 'px'; 12463 stroke.color = options.color; 12464 stroke.opacity = options.opacity; 12465 12466 if (options.dashArray) { 12467 stroke.dashStyle = isArray(options.dashArray) ? 12468 options.dashArray.join(' ') : 12469 options.dashArray.replace(/( *, *)/g, ' '); 12470 } else { 12471 stroke.dashStyle = ''; 12472 } 12473 stroke.endcap = options.lineCap.replace('butt', 'flat'); 12474 stroke.joinstyle = options.lineJoin; 12475 12476 } else if (stroke) { 12477 container.removeChild(stroke); 12478 layer._stroke = null; 12479 } 12480 12481 if (options.fill) { 12482 if (!fill) { 12483 fill = layer._fill = vmlCreate('fill'); 12484 } 12485 container.appendChild(fill); 12486 fill.color = options.fillColor || options.color; 12487 fill.opacity = options.fillOpacity; 12488 12489 } else if (fill) { 12490 container.removeChild(fill); 12491 layer._fill = null; 12492 } 12493 }, 12494 12495 _updateCircle: function (layer) { 12496 var p = layer._point.round(), 12497 r = Math.round(layer._radius), 12498 r2 = Math.round(layer._radiusY || r); 12499 12500 this._setPath(layer, layer._empty() ? 'M0 0' : 12501 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 12502 }, 12503 12504 _setPath: function (layer, path) { 12505 layer._path.v = path; 12506 }, 12507 12508 _bringToFront: function (layer) { 12509 toFront(layer._container); 12510 }, 12511 12512 _bringToBack: function (layer) { 12513 toBack(layer._container); 12514 } 12515}; 12516 12517var create$2 = vml ? vmlCreate : svgCreate; 12518 12519/* 12520 * @class SVG 12521 * @inherits Renderer 12522 * @aka L.SVG 12523 * 12524 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 12525 * Inherits `Renderer`. 12526 * 12527 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not 12528 * available in all web browsers, notably Android 2.x and 3.x. 12529 * 12530 * Although SVG is not available on IE7 and IE8, these browsers support 12531 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 12532 * (a now deprecated technology), and the SVG renderer will fall back to VML in 12533 * this case. 12534 * 12535 * @example 12536 * 12537 * Use SVG by default for all paths in the map: 12538 * 12539 * ```js 12540 * var map = L.map('map', { 12541 * renderer: L.svg() 12542 * }); 12543 * ``` 12544 * 12545 * Use a SVG renderer with extra padding for specific vector geometries: 12546 * 12547 * ```js 12548 * var map = L.map('map'); 12549 * var myRenderer = L.svg({ padding: 0.5 }); 12550 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12551 * var circle = L.circle( center, { renderer: myRenderer } ); 12552 * ``` 12553 */ 12554 12555var SVG = Renderer.extend({ 12556 12557 getEvents: function () { 12558 var events = Renderer.prototype.getEvents.call(this); 12559 events.zoomstart = this._onZoomStart; 12560 return events; 12561 }, 12562 12563 _initContainer: function () { 12564 this._container = create$2('svg'); 12565 12566 // makes it possible to click through svg root; we'll reset it back in individual paths 12567 this._container.setAttribute('pointer-events', 'none'); 12568 12569 this._rootGroup = create$2('g'); 12570 this._container.appendChild(this._rootGroup); 12571 }, 12572 12573 _destroyContainer: function () { 12574 remove(this._container); 12575 off(this._container); 12576 delete this._container; 12577 delete this._rootGroup; 12578 delete this._svgSize; 12579 }, 12580 12581 _onZoomStart: function () { 12582 // Drag-then-pinch interactions might mess up the center and zoom. 12583 // In this case, the easiest way to prevent this is re-do the renderer 12584 // bounds and padding when the zooming starts. 12585 this._update(); 12586 }, 12587 12588 _update: function () { 12589 if (this._map._animatingZoom && this._bounds) { return; } 12590 12591 Renderer.prototype._update.call(this); 12592 12593 var b = this._bounds, 12594 size = b.getSize(), 12595 container = this._container; 12596 12597 // set size of svg-container if changed 12598 if (!this._svgSize || !this._svgSize.equals(size)) { 12599 this._svgSize = size; 12600 container.setAttribute('width', size.x); 12601 container.setAttribute('height', size.y); 12602 } 12603 12604 // movement: update container viewBox so that we don't have to change coordinates of individual layers 12605 setPosition(container, b.min); 12606 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 12607 12608 this.fire('update'); 12609 }, 12610 12611 // methods below are called by vector layers implementations 12612 12613 _initPath: function (layer) { 12614 var path = layer._path = create$2('path'); 12615 12616 // @namespace Path 12617 // @option className: String = null 12618 // Custom class name set on an element. Only for SVG renderer. 12619 if (layer.options.className) { 12620 addClass(path, layer.options.className); 12621 } 12622 12623 if (layer.options.interactive) { 12624 addClass(path, 'leaflet-interactive'); 12625 } 12626 12627 this._updateStyle(layer); 12628 this._layers[stamp(layer)] = layer; 12629 }, 12630 12631 _addPath: function (layer) { 12632 if (!this._rootGroup) { this._initContainer(); } 12633 this._rootGroup.appendChild(layer._path); 12634 layer.addInteractiveTarget(layer._path); 12635 }, 12636 12637 _removePath: function (layer) { 12638 remove(layer._path); 12639 layer.removeInteractiveTarget(layer._path); 12640 delete this._layers[stamp(layer)]; 12641 }, 12642 12643 _updatePath: function (layer) { 12644 layer._project(); 12645 layer._update(); 12646 }, 12647 12648 _updateStyle: function (layer) { 12649 var path = layer._path, 12650 options = layer.options; 12651 12652 if (!path) { return; } 12653 12654 if (options.stroke) { 12655 path.setAttribute('stroke', options.color); 12656 path.setAttribute('stroke-opacity', options.opacity); 12657 path.setAttribute('stroke-width', options.weight); 12658 path.setAttribute('stroke-linecap', options.lineCap); 12659 path.setAttribute('stroke-linejoin', options.lineJoin); 12660 12661 if (options.dashArray) { 12662 path.setAttribute('stroke-dasharray', options.dashArray); 12663 } else { 12664 path.removeAttribute('stroke-dasharray'); 12665 } 12666 12667 if (options.dashOffset) { 12668 path.setAttribute('stroke-dashoffset', options.dashOffset); 12669 } else { 12670 path.removeAttribute('stroke-dashoffset'); 12671 } 12672 } else { 12673 path.setAttribute('stroke', 'none'); 12674 } 12675 12676 if (options.fill) { 12677 path.setAttribute('fill', options.fillColor || options.color); 12678 path.setAttribute('fill-opacity', options.fillOpacity); 12679 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 12680 } else { 12681 path.setAttribute('fill', 'none'); 12682 } 12683 }, 12684 12685 _updatePoly: function (layer, closed) { 12686 this._setPath(layer, pointsToPath(layer._parts, closed)); 12687 }, 12688 12689 _updateCircle: function (layer) { 12690 var p = layer._point, 12691 r = Math.max(Math.round(layer._radius), 1), 12692 r2 = Math.max(Math.round(layer._radiusY), 1) || r, 12693 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 12694 12695 // drawing a circle with two half-arcs 12696 var d = layer._empty() ? 'M0 0' : 12697 'M' + (p.x - r) + ',' + p.y + 12698 arc + (r * 2) + ',0 ' + 12699 arc + (-r * 2) + ',0 '; 12700 12701 this._setPath(layer, d); 12702 }, 12703 12704 _setPath: function (layer, path) { 12705 layer._path.setAttribute('d', path); 12706 }, 12707 12708 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 12709 _bringToFront: function (layer) { 12710 toFront(layer._path); 12711 }, 12712 12713 _bringToBack: function (layer) { 12714 toBack(layer._path); 12715 } 12716}); 12717 12718if (vml) { 12719 SVG.include(vmlMixin); 12720} 12721 12722// @namespace SVG 12723// @factory L.svg(options?: Renderer options) 12724// Creates a SVG renderer with the given options. 12725function svg$1(options) { 12726 return svg || vml ? new SVG(options) : null; 12727} 12728 12729Map.include({ 12730 // @namespace Map; @method getRenderer(layer: Path): Renderer 12731 // Returns the instance of `Renderer` that should be used to render the given 12732 // `Path`. It will ensure that the `renderer` options of the map and paths 12733 // are respected, and that the renderers do exist on the map. 12734 getRenderer: function (layer) { 12735 // @namespace Path; @option renderer: Renderer 12736 // Use this specific instance of `Renderer` for this path. Takes 12737 // precedence over the map's [default renderer](#map-renderer). 12738 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 12739 12740 if (!renderer) { 12741 renderer = this._renderer = this._createRenderer(); 12742 } 12743 12744 if (!this.hasLayer(renderer)) { 12745 this.addLayer(renderer); 12746 } 12747 return renderer; 12748 }, 12749 12750 _getPaneRenderer: function (name) { 12751 if (name === 'overlayPane' || name === undefined) { 12752 return false; 12753 } 12754 12755 var renderer = this._paneRenderers[name]; 12756 if (renderer === undefined) { 12757 renderer = this._createRenderer({pane: name}); 12758 this._paneRenderers[name] = renderer; 12759 } 12760 return renderer; 12761 }, 12762 12763 _createRenderer: function (options) { 12764 // @namespace Map; @option preferCanvas: Boolean = false 12765 // Whether `Path`s should be rendered on a `Canvas` renderer. 12766 // By default, all `Path`s are rendered in a `SVG` renderer. 12767 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options); 12768 } 12769}); 12770 12771/* 12772 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. 12773 */ 12774 12775/* 12776 * @class Rectangle 12777 * @aka L.Rectangle 12778 * @inherits Polygon 12779 * 12780 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 12781 * 12782 * @example 12783 * 12784 * ```js 12785 * // define rectangle geographical bounds 12786 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 12787 * 12788 * // create an orange rectangle 12789 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 12790 * 12791 * // zoom the map to the rectangle bounds 12792 * map.fitBounds(bounds); 12793 * ``` 12794 * 12795 */ 12796 12797 12798var Rectangle = Polygon.extend({ 12799 initialize: function (latLngBounds, options) { 12800 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 12801 }, 12802 12803 // @method setBounds(latLngBounds: LatLngBounds): this 12804 // Redraws the rectangle with the passed bounds. 12805 setBounds: function (latLngBounds) { 12806 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 12807 }, 12808 12809 _boundsToLatLngs: function (latLngBounds) { 12810 latLngBounds = toLatLngBounds(latLngBounds); 12811 return [ 12812 latLngBounds.getSouthWest(), 12813 latLngBounds.getNorthWest(), 12814 latLngBounds.getNorthEast(), 12815 latLngBounds.getSouthEast() 12816 ]; 12817 } 12818}); 12819 12820 12821// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 12822function rectangle(latLngBounds, options) { 12823 return new Rectangle(latLngBounds, options); 12824} 12825 12826SVG.create = create$2; 12827SVG.pointsToPath = pointsToPath; 12828 12829GeoJSON.geometryToLayer = geometryToLayer; 12830GeoJSON.coordsToLatLng = coordsToLatLng; 12831GeoJSON.coordsToLatLngs = coordsToLatLngs; 12832GeoJSON.latLngToCoords = latLngToCoords; 12833GeoJSON.latLngsToCoords = latLngsToCoords; 12834GeoJSON.getFeature = getFeature; 12835GeoJSON.asFeature = asFeature; 12836 12837/* 12838 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 12839 * (zoom to a selected bounding box), enabled by default. 12840 */ 12841 12842// @namespace Map 12843// @section Interaction Options 12844Map.mergeOptions({ 12845 // @option boxZoom: Boolean = true 12846 // Whether the map can be zoomed to a rectangular area specified by 12847 // dragging the mouse while pressing the shift key. 12848 boxZoom: true 12849}); 12850 12851var BoxZoom = Handler.extend({ 12852 initialize: function (map) { 12853 this._map = map; 12854 this._container = map._container; 12855 this._pane = map._panes.overlayPane; 12856 this._resetStateTimeout = 0; 12857 map.on('unload', this._destroy, this); 12858 }, 12859 12860 addHooks: function () { 12861 on(this._container, 'mousedown', this._onMouseDown, this); 12862 }, 12863 12864 removeHooks: function () { 12865 off(this._container, 'mousedown', this._onMouseDown, this); 12866 }, 12867 12868 moved: function () { 12869 return this._moved; 12870 }, 12871 12872 _destroy: function () { 12873 remove(this._pane); 12874 delete this._pane; 12875 }, 12876 12877 _resetState: function () { 12878 this._resetStateTimeout = 0; 12879 this._moved = false; 12880 }, 12881 12882 _clearDeferredResetState: function () { 12883 if (this._resetStateTimeout !== 0) { 12884 clearTimeout(this._resetStateTimeout); 12885 this._resetStateTimeout = 0; 12886 } 12887 }, 12888 12889 _onMouseDown: function (e) { 12890 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 12891 12892 // Clear the deferred resetState if it hasn't executed yet, otherwise it 12893 // will interrupt the interaction and orphan a box element in the container. 12894 this._clearDeferredResetState(); 12895 this._resetState(); 12896 12897 disableTextSelection(); 12898 disableImageDrag(); 12899 12900 this._startPoint = this._map.mouseEventToContainerPoint(e); 12901 12902 on(document, { 12903 contextmenu: stop, 12904 mousemove: this._onMouseMove, 12905 mouseup: this._onMouseUp, 12906 keydown: this._onKeyDown 12907 }, this); 12908 }, 12909 12910 _onMouseMove: function (e) { 12911 if (!this._moved) { 12912 this._moved = true; 12913 12914 this._box = create$1('div', 'leaflet-zoom-box', this._container); 12915 addClass(this._container, 'leaflet-crosshair'); 12916 12917 this._map.fire('boxzoomstart'); 12918 } 12919 12920 this._point = this._map.mouseEventToContainerPoint(e); 12921 12922 var bounds = new Bounds(this._point, this._startPoint), 12923 size = bounds.getSize(); 12924 12925 setPosition(this._box, bounds.min); 12926 12927 this._box.style.width = size.x + 'px'; 12928 this._box.style.height = size.y + 'px'; 12929 }, 12930 12931 _finish: function () { 12932 if (this._moved) { 12933 remove(this._box); 12934 removeClass(this._container, 'leaflet-crosshair'); 12935 } 12936 12937 enableTextSelection(); 12938 enableImageDrag(); 12939 12940 off(document, { 12941 contextmenu: stop, 12942 mousemove: this._onMouseMove, 12943 mouseup: this._onMouseUp, 12944 keydown: this._onKeyDown 12945 }, this); 12946 }, 12947 12948 _onMouseUp: function (e) { 12949 if ((e.which !== 1) && (e.button !== 1)) { return; } 12950 12951 this._finish(); 12952 12953 if (!this._moved) { return; } 12954 // Postpone to next JS tick so internal click event handling 12955 // still see it as "moved". 12956 this._clearDeferredResetState(); 12957 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 12958 12959 var bounds = new LatLngBounds( 12960 this._map.containerPointToLatLng(this._startPoint), 12961 this._map.containerPointToLatLng(this._point)); 12962 12963 this._map 12964 .fitBounds(bounds) 12965 .fire('boxzoomend', {boxZoomBounds: bounds}); 12966 }, 12967 12968 _onKeyDown: function (e) { 12969 if (e.keyCode === 27) { 12970 this._finish(); 12971 } 12972 } 12973}); 12974 12975// @section Handlers 12976// @property boxZoom: Handler 12977// Box (shift-drag with mouse) zoom handler. 12978Map.addInitHook('addHandler', 'boxZoom', BoxZoom); 12979 12980/* 12981 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 12982 */ 12983 12984// @namespace Map 12985// @section Interaction Options 12986 12987Map.mergeOptions({ 12988 // @option doubleClickZoom: Boolean|String = true 12989 // Whether the map can be zoomed in by double clicking on it and 12990 // zoomed out by double clicking while holding shift. If passed 12991 // `'center'`, double-click zoom will zoom to the center of the 12992 // view regardless of where the mouse was. 12993 doubleClickZoom: true 12994}); 12995 12996var DoubleClickZoom = Handler.extend({ 12997 addHooks: function () { 12998 this._map.on('dblclick', this._onDoubleClick, this); 12999 }, 13000 13001 removeHooks: function () { 13002 this._map.off('dblclick', this._onDoubleClick, this); 13003 }, 13004 13005 _onDoubleClick: function (e) { 13006 var map = this._map, 13007 oldZoom = map.getZoom(), 13008 delta = map.options.zoomDelta, 13009 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 13010 13011 if (map.options.doubleClickZoom === 'center') { 13012 map.setZoom(zoom); 13013 } else { 13014 map.setZoomAround(e.containerPoint, zoom); 13015 } 13016 } 13017}); 13018 13019// @section Handlers 13020// 13021// Map properties include interaction handlers that allow you to control 13022// interaction behavior in runtime, enabling or disabling certain features such 13023// as dragging or touch zoom (see `Handler` methods). For example: 13024// 13025// ```js 13026// map.doubleClickZoom.disable(); 13027// ``` 13028// 13029// @property doubleClickZoom: Handler 13030// Double click zoom handler. 13031Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); 13032 13033/* 13034 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 13035 */ 13036 13037// @namespace Map 13038// @section Interaction Options 13039Map.mergeOptions({ 13040 // @option dragging: Boolean = true 13041 // Whether the map be draggable with mouse/touch or not. 13042 dragging: true, 13043 13044 // @section Panning Inertia Options 13045 // @option inertia: Boolean = * 13046 // If enabled, panning of the map will have an inertia effect where 13047 // the map builds momentum while dragging and continues moving in 13048 // the same direction for some time. Feels especially nice on touch 13049 // devices. Enabled by default unless running on old Android devices. 13050 inertia: !android23, 13051 13052 // @option inertiaDeceleration: Number = 3000 13053 // The rate with which the inertial movement slows down, in pixels/second². 13054 inertiaDeceleration: 3400, // px/s^2 13055 13056 // @option inertiaMaxSpeed: Number = Infinity 13057 // Max speed of the inertial movement, in pixels/second. 13058 inertiaMaxSpeed: Infinity, // px/s 13059 13060 // @option easeLinearity: Number = 0.2 13061 easeLinearity: 0.2, 13062 13063 // TODO refactor, move to CRS 13064 // @option worldCopyJump: Boolean = false 13065 // With this option enabled, the map tracks when you pan to another "copy" 13066 // of the world and seamlessly jumps to the original one so that all overlays 13067 // like markers and vector layers are still visible. 13068 worldCopyJump: false, 13069 13070 // @option maxBoundsViscosity: Number = 0.0 13071 // If `maxBounds` is set, this option will control how solid the bounds 13072 // are when dragging the map around. The default value of `0.0` allows the 13073 // user to drag outside the bounds at normal speed, higher values will 13074 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 13075 // solid, preventing the user from dragging outside the bounds. 13076 maxBoundsViscosity: 0.0 13077}); 13078 13079var Drag = Handler.extend({ 13080 addHooks: function () { 13081 if (!this._draggable) { 13082 var map = this._map; 13083 13084 this._draggable = new Draggable(map._mapPane, map._container); 13085 13086 this._draggable.on({ 13087 dragstart: this._onDragStart, 13088 drag: this._onDrag, 13089 dragend: this._onDragEnd 13090 }, this); 13091 13092 this._draggable.on('predrag', this._onPreDragLimit, this); 13093 if (map.options.worldCopyJump) { 13094 this._draggable.on('predrag', this._onPreDragWrap, this); 13095 map.on('zoomend', this._onZoomEnd, this); 13096 13097 map.whenReady(this._onZoomEnd, this); 13098 } 13099 } 13100 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 13101 this._draggable.enable(); 13102 this._positions = []; 13103 this._times = []; 13104 }, 13105 13106 removeHooks: function () { 13107 removeClass(this._map._container, 'leaflet-grab'); 13108 removeClass(this._map._container, 'leaflet-touch-drag'); 13109 this._draggable.disable(); 13110 }, 13111 13112 moved: function () { 13113 return this._draggable && this._draggable._moved; 13114 }, 13115 13116 moving: function () { 13117 return this._draggable && this._draggable._moving; 13118 }, 13119 13120 _onDragStart: function () { 13121 var map = this._map; 13122 13123 map._stop(); 13124 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 13125 var bounds = toLatLngBounds(this._map.options.maxBounds); 13126 13127 this._offsetLimit = toBounds( 13128 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 13129 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 13130 .add(this._map.getSize())); 13131 13132 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 13133 } else { 13134 this._offsetLimit = null; 13135 } 13136 13137 map 13138 .fire('movestart') 13139 .fire('dragstart'); 13140 13141 if (map.options.inertia) { 13142 this._positions = []; 13143 this._times = []; 13144 } 13145 }, 13146 13147 _onDrag: function (e) { 13148 if (this._map.options.inertia) { 13149 var time = this._lastTime = +new Date(), 13150 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 13151 13152 this._positions.push(pos); 13153 this._times.push(time); 13154 13155 this._prunePositions(time); 13156 } 13157 13158 this._map 13159 .fire('move', e) 13160 .fire('drag', e); 13161 }, 13162 13163 _prunePositions: function (time) { 13164 while (this._positions.length > 1 && time - this._times[0] > 50) { 13165 this._positions.shift(); 13166 this._times.shift(); 13167 } 13168 }, 13169 13170 _onZoomEnd: function () { 13171 var pxCenter = this._map.getSize().divideBy(2), 13172 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 13173 13174 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 13175 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 13176 }, 13177 13178 _viscousLimit: function (value, threshold) { 13179 return value - (value - threshold) * this._viscosity; 13180 }, 13181 13182 _onPreDragLimit: function () { 13183 if (!this._viscosity || !this._offsetLimit) { return; } 13184 13185 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 13186 13187 var limit = this._offsetLimit; 13188 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 13189 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 13190 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 13191 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 13192 13193 this._draggable._newPos = this._draggable._startPos.add(offset); 13194 }, 13195 13196 _onPreDragWrap: function () { 13197 // TODO refactor to be able to adjust map pane position after zoom 13198 var worldWidth = this._worldWidth, 13199 halfWidth = Math.round(worldWidth / 2), 13200 dx = this._initialWorldOffset, 13201 x = this._draggable._newPos.x, 13202 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 13203 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 13204 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 13205 13206 this._draggable._absPos = this._draggable._newPos.clone(); 13207 this._draggable._newPos.x = newX; 13208 }, 13209 13210 _onDragEnd: function (e) { 13211 var map = this._map, 13212 options = map.options, 13213 13214 noInertia = !options.inertia || this._times.length < 2; 13215 13216 map.fire('dragend', e); 13217 13218 if (noInertia) { 13219 map.fire('moveend'); 13220 13221 } else { 13222 this._prunePositions(+new Date()); 13223 13224 var direction = this._lastPos.subtract(this._positions[0]), 13225 duration = (this._lastTime - this._times[0]) / 1000, 13226 ease = options.easeLinearity, 13227 13228 speedVector = direction.multiplyBy(ease / duration), 13229 speed = speedVector.distanceTo([0, 0]), 13230 13231 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 13232 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 13233 13234 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 13235 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 13236 13237 if (!offset.x && !offset.y) { 13238 map.fire('moveend'); 13239 13240 } else { 13241 offset = map._limitOffset(offset, map.options.maxBounds); 13242 13243 requestAnimFrame(function () { 13244 map.panBy(offset, { 13245 duration: decelerationDuration, 13246 easeLinearity: ease, 13247 noMoveStart: true, 13248 animate: true 13249 }); 13250 }); 13251 } 13252 } 13253 } 13254}); 13255 13256// @section Handlers 13257// @property dragging: Handler 13258// Map dragging handler (by both mouse and touch). 13259Map.addInitHook('addHandler', 'dragging', Drag); 13260 13261/* 13262 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 13263 */ 13264 13265// @namespace Map 13266// @section Keyboard Navigation Options 13267Map.mergeOptions({ 13268 // @option keyboard: Boolean = true 13269 // Makes the map focusable and allows users to navigate the map with keyboard 13270 // arrows and `+`/`-` keys. 13271 keyboard: true, 13272 13273 // @option keyboardPanDelta: Number = 80 13274 // Amount of pixels to pan when pressing an arrow key. 13275 keyboardPanDelta: 80 13276}); 13277 13278var Keyboard = Handler.extend({ 13279 13280 keyCodes: { 13281 left: [37], 13282 right: [39], 13283 down: [40], 13284 up: [38], 13285 zoomIn: [187, 107, 61, 171], 13286 zoomOut: [189, 109, 54, 173] 13287 }, 13288 13289 initialize: function (map) { 13290 this._map = map; 13291 13292 this._setPanDelta(map.options.keyboardPanDelta); 13293 this._setZoomDelta(map.options.zoomDelta); 13294 }, 13295 13296 addHooks: function () { 13297 var container = this._map._container; 13298 13299 // make the container focusable by tabbing 13300 if (container.tabIndex <= 0) { 13301 container.tabIndex = '0'; 13302 } 13303 13304 on(container, { 13305 focus: this._onFocus, 13306 blur: this._onBlur, 13307 mousedown: this._onMouseDown 13308 }, this); 13309 13310 this._map.on({ 13311 focus: this._addHooks, 13312 blur: this._removeHooks 13313 }, this); 13314 }, 13315 13316 removeHooks: function () { 13317 this._removeHooks(); 13318 13319 off(this._map._container, { 13320 focus: this._onFocus, 13321 blur: this._onBlur, 13322 mousedown: this._onMouseDown 13323 }, this); 13324 13325 this._map.off({ 13326 focus: this._addHooks, 13327 blur: this._removeHooks 13328 }, this); 13329 }, 13330 13331 _onMouseDown: function () { 13332 if (this._focused) { return; } 13333 13334 var body = document.body, 13335 docEl = document.documentElement, 13336 top = body.scrollTop || docEl.scrollTop, 13337 left = body.scrollLeft || docEl.scrollLeft; 13338 13339 this._map._container.focus(); 13340 13341 window.scrollTo(left, top); 13342 }, 13343 13344 _onFocus: function () { 13345 this._focused = true; 13346 this._map.fire('focus'); 13347 }, 13348 13349 _onBlur: function () { 13350 this._focused = false; 13351 this._map.fire('blur'); 13352 }, 13353 13354 _setPanDelta: function (panDelta) { 13355 var keys = this._panKeys = {}, 13356 codes = this.keyCodes, 13357 i, len; 13358 13359 for (i = 0, len = codes.left.length; i < len; i++) { 13360 keys[codes.left[i]] = [-1 * panDelta, 0]; 13361 } 13362 for (i = 0, len = codes.right.length; i < len; i++) { 13363 keys[codes.right[i]] = [panDelta, 0]; 13364 } 13365 for (i = 0, len = codes.down.length; i < len; i++) { 13366 keys[codes.down[i]] = [0, panDelta]; 13367 } 13368 for (i = 0, len = codes.up.length; i < len; i++) { 13369 keys[codes.up[i]] = [0, -1 * panDelta]; 13370 } 13371 }, 13372 13373 _setZoomDelta: function (zoomDelta) { 13374 var keys = this._zoomKeys = {}, 13375 codes = this.keyCodes, 13376 i, len; 13377 13378 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 13379 keys[codes.zoomIn[i]] = zoomDelta; 13380 } 13381 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 13382 keys[codes.zoomOut[i]] = -zoomDelta; 13383 } 13384 }, 13385 13386 _addHooks: function () { 13387 on(document, 'keydown', this._onKeyDown, this); 13388 }, 13389 13390 _removeHooks: function () { 13391 off(document, 'keydown', this._onKeyDown, this); 13392 }, 13393 13394 _onKeyDown: function (e) { 13395 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 13396 13397 var key = e.keyCode, 13398 map = this._map, 13399 offset; 13400 13401 if (key in this._panKeys) { 13402 if (!map._panAnim || !map._panAnim._inProgress) { 13403 offset = this._panKeys[key]; 13404 if (e.shiftKey) { 13405 offset = toPoint(offset).multiplyBy(3); 13406 } 13407 13408 map.panBy(offset); 13409 13410 if (map.options.maxBounds) { 13411 map.panInsideBounds(map.options.maxBounds); 13412 } 13413 } 13414 } else if (key in this._zoomKeys) { 13415 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 13416 13417 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) { 13418 map.closePopup(); 13419 13420 } else { 13421 return; 13422 } 13423 13424 stop(e); 13425 } 13426}); 13427 13428// @section Handlers 13429// @section Handlers 13430// @property keyboard: Handler 13431// Keyboard navigation handler.
13432Map.addInitHook('addHandler', 'keyboard', Keyboard); 13433 13434/* 13435 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 13436 */ 13437 13438// @namespace Map 13439// @section Interaction Options 13440Map.mergeOptions({ 13441 // @section Mousewheel options 13442 // @option scrollWheelZoom: Boolean|String = true 13443 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 13444 // it will zoom to the center of the view regardless of where the mouse was. 13445 scrollWheelZoom: true, 13446 13447 // @option wheelDebounceTime: Number = 40 13448 // Limits the rate at which a wheel can fire (in milliseconds). By default 13449 // user can't zoom via wheel more often than once per 40 ms. 13450 wheelDebounceTime: 40, 13451 13452 // @option wheelPxPerZoomLevel: Number = 60 13453 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 13454 // mean a change of one full zoom level. Smaller values will make wheel-zooming 13455 // faster (and vice versa). 13456 wheelPxPerZoomLevel: 60 13457}); 13458 13459var ScrollWheelZoom = Handler.extend({ 13460 addHooks: function () { 13461 on(this._map._container, 'mousewheel', this._onWheelScroll, this); 13462 13463 this._delta = 0; 13464 }, 13465 13466 removeHooks: function () { 13467 off(this._map._container, 'mousewheel', this._onWheelScroll, this); 13468 }, 13469 13470 _onWheelScroll: function (e) { 13471 var delta = getWheelDelta(e); 13472 13473 var debounce = this._map.options.wheelDebounceTime; 13474 13475 this._delta += delta; 13476 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 13477 13478 if (!this._startTime) { 13479 this._startTime = +new Date(); 13480 } 13481 13482 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 13483 13484 clearTimeout(this._timer); 13485 this._timer = setTimeout(bind(this._performZoom, this), left); 13486 13487 stop(e); 13488 }, 13489 13490 _performZoom: function () { 13491 var map = this._map, 13492 zoom = map.getZoom(), 13493 snap = this._map.options.zoomSnap || 0; 13494 13495 map._stop(); // stop panning and fly animations if any 13496 13497 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 13498 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 13499 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 13500 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 13501 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 13502 13503 this._delta = 0; 13504 this._startTime = null; 13505 13506 if (!delta) { return; } 13507 13508 if (map.options.scrollWheelZoom === 'center') { 13509 map.setZoom(zoom + delta); 13510 } else { 13511 map.setZoomAround(this._lastMousePos, zoom + delta); 13512 } 13513 } 13514}); 13515 13516// @section Handlers 13517// @property scrollWheelZoom: Handler 13518// Scroll wheel zoom handler. 13519Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); 13520 13521/* 13522 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. 13523 */ 13524 13525// @namespace Map 13526// @section Interaction Options 13527Map.mergeOptions({ 13528 // @section Touch interaction options 13529 // @option tap: Boolean = true 13530 // Enables mobile hacks for supporting instant taps (fixing 200ms click 13531 // delay on iOS/Android) and touch holds (fired as `contextmenu` events). 13532 tap: true, 13533 13534 // @option tapTolerance: Number = 15 13535 // The max number of pixels a user can shift his finger during touch 13536 // for it to be considered a valid tap. 13537 tapTolerance: 15 13538}); 13539 13540var Tap = Handler.extend({ 13541 addHooks: function () { 13542 on(this._map._container, 'touchstart', this._onDown, this); 13543 }, 13544 13545 removeHooks: function () { 13546 off(this._map._container, 'touchstart', this._onDown, this); 13547 }, 13548 13549 _onDown: function (e) { 13550 if (!e.touches) { return; } 13551 13552 preventDefault(e); 13553 13554 this._fireClick = true; 13555 13556 // don't simulate click or track longpress if more than 1 touch 13557 if (e.touches.length > 1) { 13558 this._fireClick = false; 13559 clearTimeout(this._holdTimeout); 13560 return; 13561 } 13562 13563 var first = e.touches[0], 13564 el = first.target; 13565 13566 this._startPos = this._newPos = new Point(first.clientX, first.clientY); 13567 13568 // if touching a link, highlight it 13569 if (el.tagName && el.tagName.toLowerCase() === 'a') { 13570 addClass(el, 'leaflet-active'); 13571 } 13572 13573 // simulate long hold but setting a timeout 13574 this._holdTimeout = setTimeout(bind(function () { 13575 if (this._isTapValid()) { 13576 this._fireClick = false; 13577 this._onUp(); 13578 this._simulateEvent('contextmenu', first); 13579 } 13580 }, this), 1000); 13581 13582 this._simulateEvent('mousedown', first); 13583 13584 on(document, { 13585 touchmove: this._onMove, 13586 touchend: this._onUp 13587 }, this); 13588 }, 13589 13590 _onUp: function (e) { 13591 clearTimeout(this._holdTimeout); 13592 13593 off(document, { 13594 touchmove: this._onMove, 13595 touchend: this._onUp 13596 }, this); 13597 13598 if (this._fireClick && e && e.changedTouches) { 13599 13600 var first = e.changedTouches[0], 13601 el = first.target; 13602 13603 if (el && el.tagName && el.tagName.toLowerCase() === 'a') { 13604 removeClass(el, 'leaflet-active'); 13605 } 13606 13607 this._simulateEvent('mouseup', first); 13608 13609 // simulate click if the touch didn't move too much 13610 if (this._isTapValid()) { 13611 this._simulateEvent('click', first); 13612 } 13613 } 13614 }, 13615 13616 _isTapValid: function () { 13617 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 13618 }, 13619 13620 _onMove: function (e) { 13621 var first = e.touches[0]; 13622 this._newPos = new Point(first.clientX, first.clientY); 13623 this._simulateEvent('mousemove', first); 13624 }, 13625 13626 _simulateEvent: function (type, e) { 13627 var simulatedEvent = document.createEvent('MouseEvents'); 13628 13629 simulatedEvent._simulated = true; 13630 e.target._simulatedClick = true; 13631 13632 simulatedEvent.initMouseEvent( 13633 type, true, true, window, 1, 13634 e.screenX, e.screenY, 13635 e.clientX, e.clientY, 13636 false, false, false, false, 0, null); 13637 13638 e.target.dispatchEvent(simulatedEvent); 13639 } 13640}); 13641 13642// @section Handlers 13643// @property tap: Handler 13644// Mobile touch hacks (quick tap and touch hold) handler. 13645if (touch && !pointer) { 13646 Map.addInitHook('addHandler', 'tap', Tap); 13647} 13648 13649/* 13650 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supp
vendor: 4,276 bytes, lines 13650-13790
13650orted mobile browsers. 13651 */ 13652 13653// @namespace Map 13654// @section Interaction Options 13655Map.mergeOptions({ 13656 // @section Touch interaction options 13657 // @option touchZoom: Boolean|String = * 13658 // Whether the map can be zoomed by touch-dragging with two fingers. If 13659 // passed `'center'`, it will zoom to the center of the view regardless of 13660 // where the touch events (fingers) were. Enabled for touch-capable web 13661 // browsers except for old Androids. 13662 touchZoom: touch && !android23, 13663 13664 // @option bounceAtZoomLimits: Boolean = true 13665 // Set it to false if you don't want the map to zoom beyond min/max zoom 13666 // and then bounce back when pinch-zooming. 13667 bounceAtZoomLimits: true 13668}); 13669 13670var TouchZoom = Handler.extend({ 13671 addHooks: function () { 13672 addClass(this._map._container, 'leaflet-touch-zoom'); 13673 on(this._map._container, 'touchstart', this._onTouchStart, this); 13674 }, 13675 13676 removeHooks: function () { 13677 removeClass(this._map._container, 'leaflet-touch-zoom'); 13678 off(this._map._container, 'touchstart', this._onTouchStart, this); 13679 }, 13680 13681 _onTouchStart: function (e) { 13682 var map = this._map; 13683 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 13684 13685 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 13686 p2 = map.mouseEventToContainerPoint(e.touches[1]); 13687 13688 this._centerPoint = map.getSize()._divideBy(2); 13689 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 13690 if (map.options.touchZoom !== 'center') { 13691 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 13692 } 13693 13694 this._startDist = p1.distanceTo(p2); 13695 this._startZoom = map.getZoom(); 13696 13697 this._moved = false; 13698 this._zooming = true; 13699 13700 map._stop(); 13701 13702 on(document, 'touchmove', this._onTouchMove, this); 13703 on(document, 'touchend', this._onTouchEnd, this); 13704 13705 preventDefault(e); 13706 }, 13707 13708 _onTouchMove: function (e) { 13709 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 13710 13711 var map = this._map, 13712 p1 = map.mouseEventToContainerPoint(e.touches[0]), 13713 p2 = map.mouseEventToContainerPoint(e.touches[1]), 13714 scale = p1.distanceTo(p2) / this._startDist; 13715 13716 this._zoom = map.getScaleZoom(scale, this._startZoom); 13717 13718 if (!map.options.bounceAtZoomLimits && ( 13719 (this._zoom < map.getMinZoom() && scale < 1) || 13720 (this._zoom > map.getMaxZoom() && scale > 1))) { 13721 this._zoom = map._limitZoom(this._zoom); 13722 } 13723 13724 if (map.options.touchZoom === 'center') { 13725 this._center = this._startLatLng; 13726 if (scale === 1) { return; } 13727 } else { 13728 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 13729 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 13730 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 13731 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 13732 } 13733 13734 if (!this._moved) { 13735 map._moveStart(true, false); 13736 this._moved = true; 13737 } 13738 13739 cancelAnimFrame(this._animRequest); 13740 13741 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); 13742 this._animRequest = requestAnimFrame(moveFn, this, true); 13743 13744 preventDefault(e); 13745 }, 13746 13747 _onTouchEnd: function () { 13748 if (!this._moved || !this._zooming) { 13749 this._zooming = false; 13750 return; 13751 } 13752 13753 this._zooming = false; 13754 cancelAnimFrame(this._animRequest); 13755 13756 off(document, 'touchmove', this._onTouchMove); 13757 off(document, 'touchend', this._onTouchEnd); 13758 13759 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 13760 if (this._map.options.zoomAnimation) { 13761 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 13762 } else { 13763 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 13764 } 13765 } 13766}); 13767 13768// @section Handlers 13769// @property touchZoom: Handler 13770// Touch zoom handler. 13771Map.addInitHook('addHandler', 'touchZoom', TouchZoom); 13772 13773Map.BoxZoom = BoxZoom; 13774Map.DoubleClickZoom = DoubleClickZoom; 13775Map.Drag = Drag; 13776Map.Keyboard = Keyboard; 13777Map.ScrollWheelZoom = ScrollWheelZoom; 13778Map.Tap = Tap; 13779Map.TouchZoom = TouchZoom; 13780 13781Object.freeze = freeze; 13782 13783exports.version = version; 13784exports.Control = Control; 13785exports.control = control; 13786exports.Browser = Browser; 13787exports.Evented = Evented; 13788exports.Mixin = Mixin; 13789exports.Util = Util; 13790exports.Class = Class;
13791exports.Handler = Handler; 13792exports.extend = extend; 13793exports.bind = bind; 13794exports.stamp = stamp; 13795exports.setOptions = setOptions; 13796exports.DomEvent = DomEvent; 13797exports.DomUtil = DomUtil; 13798exports.PosAnimation = PosAnimation; 13799exports.Draggable = Draggable; 13800exports.LineUtil = LineUtil; 13801exports.PolyUtil = PolyUtil; 13802exports.Point = Point; 13803exports.point = toPoint; 13804exports.Bounds = Bounds; 13805exports.bounds = toBounds; 13806exports.Transformation = Transformation; 13807exports.transformation = toTransformation; 13808exports.Projection = index; 13809exports.LatLng = LatLng; 13810exports.latLng = toLatLng; 13811exports.LatLngBounds = LatLngBounds; 13812exports.latLngBounds = toLatLngBounds; 13813exports.CRS = CRS; 13814exports.GeoJSON = GeoJSON; 13815exports.geoJSON = geoJSON; 13816exports.geoJson = geoJson; 13817exports.Layer = Layer; 13818exports.LayerGroup = LayerGroup; 13819exports.layerGroup = layerGroup; 13820exports.FeatureGroup = FeatureGroup; 13821exports.featureGroup = featureGroup; 13822exports.ImageOverlay = ImageOverlay; 13823exports.imageOverlay = imageOverlay; 13824exports.VideoOverlay = VideoOverlay; 13825exports.videoOverlay = videoOverlay; 13826exports.DivOverlay = DivOverlay; 13827exports.Popup = Popup; 13828exports.popup = popup; 13829exports.Tooltip = Tooltip; 13830exports.tooltip = tooltip; 13831exports.Icon = Icon; 13832exports.icon = icon; 13833exports.DivIcon = DivIcon; 13834exports.divIcon = divIcon; 13835exports.Marker = Marker; 13836exports.marker = marker; 13837exports.TileLayer = TileLayer; 13838exports.tileLayer = tileLayer; 13839exports.GridLayer = GridLayer; 13840exports.gridLayer = gridLayer; 13841exports.SVG = SVG; 13842exports.svg = svg$1; 13843exports.Renderer = Renderer; 13844exports.Canvas = Canvas; 13845exports.canvas = canvas$1; 13846exports.Path = Path; 13847exports.CircleMarker = CircleMarker; 13848exports.circleMarker = circleMarker; 13849exports.Circle = Circle; 13850exports.circle = circle; 13851exports.Polyline = Polyline; 13852exports.polyline = polyline; 13853exports.Polygon = Polygon; 13854exports.polygon = polygon; 13855exports.Rectangle = Rectangle; 13856exports.rectangle = rectangle; 13857exports.Map = Map; 13858exports.map = createMap; 13859 13860var oldL = window.L; 13861exports.noConflict = function() { 13862 window.L = oldL; 13863 return this; 13864} 13865 13866// Always export us to window global (see #2364) 13867window.L = exports; 13868 13869}))); 13870//# 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.