[ Index ] |
MailPress 7.2 |
[ Index ] [ Classes ] [ Functions ] [ Variables ] [ Constants ] [ Statistics ] |
[Summary view] [Print] [Text view]
1 /* @preserve 2 * Leaflet 1.6.0+Detached: 0c81bdf904d864fd12a286e3d1979f47aba17991.0c81bdf, a JS library for interactive maps. http://leafletjs.com 3 * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade 4 */ 5 6 var version = "1.6.0+HEAD.0c81bdf"; 7 8 /* 9 * @namespace Util 10 * 11 * Various utility functions, used by Leaflet internally. 12 */ 13 14 var freeze = Object.freeze; 15 Object.freeze = function (obj) { return obj; }; 16 17 // @function extend(dest: Object, src?: Object): Object 18 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 19 function extend(dest) { 20 var i, j, len, src; 21 22 for (j = 1, len = arguments.length; j < len; j++) { 23 src = arguments[j]; 24 for (i in src) { 25 dest[i] = src[i]; 26 } 27 } 28 return dest; 29 } 30 31 // @function create(proto: Object, properties?: Object): Object 32 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 33 var create = Object.create || (function () { 34 function F() {} 35 return function (proto) { 36 F.prototype = proto; 37 return new F(); 38 }; 39 })(); 40 41 // @function bind(fn: Function, …): Function 42 // 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). 43 // Has a `L.bind()` shortcut. 44 function bind(fn, obj) { 45 var slice = Array.prototype.slice; 46 47 if (fn.bind) { 48 return fn.bind.apply(fn, slice.call(arguments, 1)); 49 } 50 51 var args = slice.call(arguments, 2); 52 53 return function () { 54 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 55 }; 56 } 57 58 // @property lastId: Number 59 // Last unique ID used by [`stamp()`](#util-stamp) 60 var lastId = 0; 61 62 // @function stamp(obj: Object): Number 63 // Returns the unique ID of an object, assigning it one if it doesn't have it. 64 function stamp(obj) { 65 /*eslint-disable */ 66 obj._leaflet_id = obj._leaflet_id || ++lastId; 67 return obj._leaflet_id; 68 /* eslint-enable */ 69 } 70 71 // @function throttle(fn: Function, time: Number, context: Object): Function 72 // Returns a function which executes function `fn` with the given scope `context` 73 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function 74 // `fn` will be called no more than one time per given amount of `time`. The arguments 75 // received by the bound function will be any arguments passed when binding the 76 // function, followed by any arguments passed when invoking the bound function. 77 // Has an `L.throttle` shortcut. 78 function throttle(fn, time, context) { 79 var lock, args, wrapperFn, later; 80 81 later = function () { 82 // reset lock and call if queued 83 lock = false; 84 if (args) { 85 wrapperFn.apply(context, args); 86 args = false; 87 } 88 }; 89 90 wrapperFn = function () { 91 if (lock) { 92 // called too soon, queue to call later 93 args = arguments; 94 95 } else { 96 // call and lock until later 97 fn.apply(context, arguments); 98 setTimeout(later, time); 99 lock = true; 100 } 101 }; 102 103 return wrapperFn; 104 } 105 106 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number 107 // Returns the number `num` modulo `range` in such a way so it lies within 108 // `range[0]` and `range[1]`. The returned value will be always smaller than 109 // `range[1]` unless `includeMax` is set to `true`. 110 function wrapNum(x, range, includeMax) { 111 var max = range[1], 112 min = range[0], 113 d = max - min; 114 return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 115 } 116 117 // @function falseFn(): Function 118 // Returns a function which always returns `false`. 119 function falseFn() { return false; } 120 121 // @function formatNum(num: Number, digits?: Number): Number 122 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default. 123 function formatNum(num, digits) { 124 var pow = Math.pow(10, (digits === undefined ? 6 : digits)); 125 return Math.round(num * pow) / pow; 126 } 127 128 // @function trim(str: String): String 129 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) 130 function trim(str) { 131 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); 132 } 133 134 // @function splitWords(str: String): String[] 135 // Trims and splits the string on whitespace and returns the array of parts. 136 function splitWords(str) { 137 return trim(str).split(/\s+/); 138 } 139 140 // @function setOptions(obj: Object, options: Object): Object 141 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. 142 function setOptions(obj, options) { 143 if (!obj.hasOwnProperty('options')) { 144 obj.options = obj.options ? create(obj.options) : {}; 145 } 146 for (var i in options) { 147 obj.options[i] = options[i]; 148 } 149 return obj.options; 150 } 151 152 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String 153 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` 154 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will 155 // be appended at the end. If `uppercase` is `true`, the parameter names will 156 // be uppercased (e.g. `'?A=foo&B=bar'`) 157 function getParamString(obj, existingUrl, uppercase) { 158 var params = []; 159 for (var i in obj) { 160 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); 161 } 162 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); 163 } 164 165 var templateRe = /\{ *([\w_-]+) *\}/g; 166 167 // @function template(str: String, data: Object): String 168 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` 169 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string 170 // `('Hello foo, bar')`. You can also specify functions instead of strings for 171 // data values — they will be evaluated passing `data` as an argument. 172 function template(str, data) { 173 return str.replace(templateRe, function (str, key) { 174 var value = data[key]; 175 176 if (value === undefined) { 177 throw new Error('No value provided for variable ' + str); 178 179 } else if (typeof value === 'function') { 180 value = value(data); 181 } 182 return value; 183 }); 184 } 185 186 // @function isArray(obj): Boolean 187 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) 188 var isArray = Array.isArray || function (obj) { 189 return (Object.prototype.toString.call(obj) === '[object Array]'); 190 }; 191 192 // @function indexOf(array: Array, el: Object): Number 193 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) 194 function indexOf(array, el) { 195 for (var i = 0; i < array.length; i++) { 196 if (array[i] === el) { return i; } 197 } 198 return -1; 199 } 200 201 // @property emptyImageUrl: String 202 // Data URI string containing a base64-encoded empty GIF image. 203 // Used as a hack to free memory from unused images on WebKit-powered 204 // mobile devices (by setting image `src` to this string). 205 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; 206 207 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 208 209 function getPrefixed(name) { 210 return window['webkit' + name] || window['moz' + name] || window['ms' + name]; 211 } 212 213 var lastTime = 0; 214 215 // fallback for IE 7-8 216 function timeoutDefer(fn) { 217 var time = +new Date(), 218 timeToCall = Math.max(0, 16 - (time - lastTime)); 219 220 lastTime = time + timeToCall; 221 return window.setTimeout(fn, timeToCall); 222 } 223 224 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; 225 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || 226 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; 227 228 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number 229 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to 230 // `context` if given. When `immediate` is set, `fn` is called immediately if 231 // the browser doesn't have native support for 232 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), 233 // otherwise it's delayed. Returns a request ID that can be used to cancel the request. 234 function requestAnimFrame(fn, context, immediate) { 235 if (immediate && requestFn === timeoutDefer) { 236 fn.call(context); 237 } else { 238 return requestFn.call(window, bind(fn, context)); 239 } 240 } 241 242 // @function cancelAnimFrame(id: Number): undefined 243 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). 244 function cancelAnimFrame(id) { 245 if (id) { 246 cancelFn.call(window, id); 247 } 248 } 249 250 251 var Util = (Object.freeze || Object)({ 252 freeze: freeze, 253 extend: extend, 254 create: create, 255 bind: bind, 256 lastId: lastId, 257 stamp: stamp, 258 throttle: throttle, 259 wrapNum: wrapNum, 260 falseFn: falseFn, 261 formatNum: formatNum, 262 trim: trim, 263 splitWords: splitWords, 264 setOptions: setOptions, 265 getParamString: getParamString, 266 template: template, 267 isArray: isArray, 268 indexOf: indexOf, 269 emptyImageUrl: emptyImageUrl, 270 requestFn: requestFn, 271 cancelFn: cancelFn, 272 requestAnimFrame: requestAnimFrame, 273 cancelAnimFrame: cancelAnimFrame 274 }); 275 276 // @class Class 277 // @aka L.Class 278 279 // @section 280 // @uninheritable 281 282 // Thanks to John Resig and Dean Edwards for inspiration! 283 284 function Class() {} 285 286 Class.extend = function (props) { 287 288 // @function extend(props: Object): Function 289 // [Extends the current class](#class-inheritance) given the properties to be included. 290 // Returns a Javascript function that is a class constructor (to be called with `new`). 291 var NewClass = function () { 292 293 // call the constructor 294 if (this.initialize) { 295 this.initialize.apply(this, arguments); 296 } 297 298 // call all constructor hooks 299 this.callInitHooks(); 300 }; 301 302 var parentProto = NewClass.__super__ = this.prototype; 303 304 var proto = create(parentProto); 305 proto.constructor = NewClass; 306 307 NewClass.prototype = proto; 308 309 // inherit parent's statics 310 for (var i in this) { 311 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') { 312 NewClass[i] = this[i]; 313 } 314 } 315 316 // mix static properties into the class 317 if (props.statics) { 318 extend(NewClass, props.statics); 319 delete props.statics; 320 } 321 322 // mix includes into the prototype 323 if (props.includes) { 324 checkDeprecatedMixinEvents(props.includes); 325 extend.apply(null, [proto].concat(props.includes)); 326 delete props.includes; 327 } 328 329 // merge options 330 if (proto.options) { 331 props.options = extend(create(proto.options), props.options); 332 } 333 334 // mix given properties into the prototype 335 extend(proto, props); 336 337 proto._initHooks = []; 338 339 // add method for calling all hooks 340 proto.callInitHooks = function () { 341 342 if (this._initHooksCalled) { return; } 343 344 if (parentProto.callInitHooks) { 345 parentProto.callInitHooks.call(this); 346 } 347 348 this._initHooksCalled = true; 349 350 for (var i = 0, len = proto._initHooks.length; i < len; i++) { 351 proto._initHooks[i].call(this); 352 } 353 }; 354 355 return NewClass; 356 }; 357 358 359 // @function include(properties: Object): this 360 // [Includes a mixin](#class-includes) into the current class. 361 Class.include = function (props) { 362 extend(this.prototype, props); 363 return this; 364 }; 365 366 // @function mergeOptions(options: Object): this 367 // [Merges `options`](#class-options) into the defaults of the class. 368 Class.mergeOptions = function (options) { 369 extend(this.prototype.options, options); 370 return this; 371 }; 372 373 // @function addInitHook(fn: Function): this 374 // Adds a [constructor hook](#class-constructor-hooks) to the class. 375 Class.addInitHook = function (fn) { // (Function) || (String, args...) 376 var args = Array.prototype.slice.call(arguments, 1); 377 378 var init = typeof fn === 'function' ? fn : function () { 379 this[fn].apply(this, args); 380 }; 381 382 this.prototype._initHooks = this.prototype._initHooks || []; 383 this.prototype._initHooks.push(init); 384 return this; 385 }; 386 387 function checkDeprecatedMixinEvents(includes) { 388 if (typeof L === 'undefined' || !L || !L.Mixin) { return; } 389 390 includes = isArray(includes) ? includes : [includes]; 391 392 for (var i = 0; i < includes.length; i++) { 393 if (includes[i] === L.Mixin.Events) { 394 console.warn('Deprecated include of L.Mixin.Events: ' + 395 'this property will be removed in future releases, ' + 396 'please inherit from L.Evented instead.', new Error().stack); 397 } 398 } 399 } 400 401 /* 402 * @class Evented 403 * @aka L.Evented 404 * @inherits Class 405 * 406 * 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). 407 * 408 * @example 409 * 410 * ```js 411 * map.on('click', function(e) { 412 * alert(e.latlng); 413 * } ); 414 * ``` 415 * 416 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 417 * 418 * ```js 419 * function onClick(e) { ... } 420 * 421 * map.on('click', onClick); 422 * map.off('click', onClick); 423 * ``` 424 */ 425 426 var Events = { 427 /* @method on(type: String, fn: Function, context?: Object): this 428 * 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'`). 429 * 430 * @alternative 431 * @method on(eventMap: Object): this 432 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 433 */ 434 on: function (types, fn, context) { 435 436 // types can be a map of types/handlers 437 if (typeof types === 'object') { 438 for (var type in types) { 439 // we don't process space-separated events here for performance; 440 // it's a hot path since Layer uses the on(obj) syntax 441 this._on(type, types[type], fn); 442 } 443 444 } else { 445 // types can be a string of space-separated words 446 types = splitWords(types); 447 448 for (var i = 0, len = types.length; i < len; i++) { 449 this._on(types[i], fn, context); 450 } 451 } 452 453 return this; 454 }, 455 456 /* @method off(type: String, fn?: Function, context?: Object): this 457 * 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. 458 * 459 * @alternative 460 * @method off(eventMap: Object): this 461 * Removes a set of type/listener pairs. 462 * 463 * @alternative 464 * @method off: this 465 * Removes all listeners to all events on the object. This includes implicitly attached events. 466 */ 467 off: function (types, fn, context) { 468 469 if (!types) { 470 // clear all listeners if called without arguments 471 delete this._events; 472 473 } else if (typeof types === 'object') { 474 for (var type in types) { 475 this._off(type, types[type], fn); 476 } 477 478 } else { 479 types = splitWords(types); 480 481 for (var i = 0, len = types.length; i < len; i++) { 482 this._off(types[i], fn, context); 483 } 484 } 485 486 return this; 487 }, 488 489 // attach listener (without syntactic sugar now) 490 _on: function (type, fn, context) { 491 this._events = this._events || {}; 492 493 /* get/init listeners for type */ 494 var typeListeners = this._events[type]; 495 if (!typeListeners) { 496 typeListeners = []; 497 this._events[type] = typeListeners; 498 } 499 500 if (context === this) { 501 // Less memory footprint. 502 context = undefined; 503 } 504 var newListener = {fn: fn, ctx: context}, 505 listeners = typeListeners; 506 507 // check if fn already there 508 for (var i = 0, len = listeners.length; i < len; i++) { 509 if (listeners[i].fn === fn && listeners[i].ctx === context) { 510 return; 511 } 512 } 513 514 listeners.push(newListener); 515 }, 516 517 _off: function (type, fn, context) { 518 var listeners, 519 i, 520 len; 521 522 if (!this._events) { return; } 523 524 listeners = this._events[type]; 525 526 if (!listeners) { 527 return; 528 } 529 530 if (!fn) { 531 // Set all removed listeners to noop so they are not called if remove happens in fire 532 for (i = 0, len = listeners.length; i < len; i++) { 533 listeners[i].fn = falseFn; 534 } 535 // clear all listeners for a type if function isn't specified 536 delete this._events[type]; 537 return; 538 } 539 540 if (context === this) { 541 context = undefined; 542 } 543 544 if (listeners) { 545 546 // find fn and remove it 547 for (i = 0, len = listeners.length; i < len; i++) { 548 var l = listeners[i]; 549 if (l.ctx !== context) { continue; } 550 if (l.fn === fn) { 551 552 // set the removed listener to noop so that's not called if remove happens in fire 553 l.fn = falseFn; 554 555 if (this._firingCount) { 556 /* copy array in case events are being fired */ 557 this._events[type] = listeners = listeners.slice(); 558 } 559 listeners.splice(i, 1); 560 561 return; 562 } 563 } 564 } 565 }, 566 567 // @method fire(type: String, data?: Object, propagate?: Boolean): this 568 // Fires an event of the specified type. You can optionally provide an data 569 // object — the first argument of the listener function will contain its 570 // properties. The event can optionally be propagated to event parents. 571 fire: function (type, data, propagate) { 572 if (!this.listens(type, propagate)) { return this; } 573 574 var event = extend({}, data, { 575 type: type, 576 target: this, 577 sourceTarget: data && data.sourceTarget || this 578 }); 579 580 if (this._events) { 581 var listeners = this._events[type]; 582 583 if (listeners) { 584 this._firingCount = (this._firingCount + 1) || 1; 585 for (var i = 0, len = listeners.length; i < len; i++) { 586 var l = listeners[i]; 587 l.fn.call(l.ctx || this, event); 588 } 589 590 this._firingCount--; 591 } 592 } 593 594 if (propagate) { 595 // propagate the event to parents (set with addEventParent) 596 this._propagateEvent(event); 597 } 598 599 return this; 600 }, 601 602 // @method listens(type: String): Boolean 603 // Returns `true` if a particular event type has any listeners attached to it. 604 listens: function (type, propagate) { 605 var listeners = this._events && this._events[type]; 606 if (listeners && listeners.length) { return true; } 607 608 if (propagate) { 609 // also check parents for listeners if event propagates 610 for (var id in this._eventParents) { 611 if (this._eventParents[id].listens(type, propagate)) { return true; } 612 } 613 } 614 return false; 615 }, 616 617 // @method once(…): this 618 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed. 619 once: function (types, fn, context) { 620 621 if (typeof types === 'object') { 622 for (var type in types) { 623 this.once(type, types[type], fn); 624 } 625 return this; 626 } 627 628 var handler = bind(function () { 629 this 630 .off(types, fn, context) 631 .off(types, handler, context); 632 }, this); 633 634 // add a listener that's executed once and removed after that 635 return this 636 .on(types, fn, context) 637 .on(types, handler, context); 638 }, 639 640 // @method addEventParent(obj: Evented): this 641 // Adds an event parent - an `Evented` that will receive propagated events 642 addEventParent: function (obj) { 643 this._eventParents = this._eventParents || {}; 644 this._eventParents[stamp(obj)] = obj; 645 return this; 646 }, 647 648 // @method removeEventParent(obj: Evented): this 649 // Removes an event parent, so it will stop receiving propagated events 650 removeEventParent: function (obj) { 651 if (this._eventParents) { 652 delete this._eventParents[stamp(obj)]; 653 } 654 return this; 655 }, 656 657 _propagateEvent: function (e) { 658 for (var id in this._eventParents) { 659 this._eventParents[id].fire(e.type, extend({ 660 layer: e.target, 661 propagatedFrom: e.target 662 }, e), true); 663 } 664 } 665 }; 666 667 // aliases; we should ditch those eventually 668 669 // @method addEventListener(…): this 670 // Alias to [`on(…)`](#evented-on) 671 Events.addEventListener = Events.on; 672 673 // @method removeEventListener(…): this 674 // Alias to [`off(…)`](#evented-off) 675 676 // @method clearAllEventListeners(…): this 677 // Alias to [`off()`](#evented-off) 678 Events.removeEventListener = Events.clearAllEventListeners = Events.off; 679 680 // @method addOneTimeEventListener(…): this 681 // Alias to [`once(…)`](#evented-once) 682 Events.addOneTimeEventListener = Events.once; 683 684 // @method fireEvent(…): this 685 // Alias to [`fire(…)`](#evented-fire) 686 Events.fireEvent = Events.fire; 687 688 // @method hasEventListeners(…): Boolean 689 // Alias to [`listens(…)`](#evented-listens) 690 Events.hasEventListeners = Events.listens; 691 692 var Evented = Class.extend(Events); 693 694 /* 695 * @class Point 696 * @aka L.Point 697 * 698 * Represents a point with `x` and `y` coordinates in pixels. 699 * 700 * @example 701 * 702 * ```js 703 * var point = L.point(200, 300); 704 * ``` 705 * 706 * 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: 707 * 708 * ```js 709 * map.panBy([200, 300]); 710 * map.panBy(L.point(200, 300)); 711 * ``` 712 * 713 * Note that `Point` does not inherit from Leafet's `Class` object, 714 * which means new classes can't inherit from it, and new methods 715 * can't be added to it with the `include` function. 716 */ 717 718 function Point(x, y, round) { 719 // @property x: Number; The `x` coordinate of the point 720 this.x = (round ? Math.round(x) : x); 721 // @property y: Number; The `y` coordinate of the point 722 this.y = (round ? Math.round(y) : y); 723 } 724 725 var trunc = Math.trunc || function (v) { 726 return v > 0 ? Math.floor(v) : Math.ceil(v); 727 }; 728 729 Point.prototype = { 730 731 // @method clone(): Point 732 // Returns a copy of the current point. 733 clone: function () { 734 return new Point(this.x, this.y); 735 }, 736 737 // @method add(otherPoint: Point): Point 738 // Returns the result of addition of the current and the given points. 739 add: function (point) { 740 // non-destructive, returns a new point 741 return this.clone()._add(toPoint(point)); 742 }, 743 744 _add: function (point) { 745 // destructive, used directly for performance in situations where it's safe to modify existing point 746 this.x += point.x; 747 this.y += point.y; 748 return this; 749 }, 750 751 // @method subtract(otherPoint: Point): Point 752 // Returns the result of subtraction of the given point from the current. 753 subtract: function (point) { 754 return this.clone()._subtract(toPoint(point)); 755 }, 756 757 _subtract: function (point) { 758 this.x -= point.x; 759 this.y -= point.y; 760 return this; 761 }, 762 763 // @method divideBy(num: Number): Point 764 // Returns the result of division of the current point by the given number. 765 divideBy: function (num) { 766 return this.clone()._divideBy(num); 767 }, 768 769 _divideBy: function (num) { 770 this.x /= num; 771 this.y /= num; 772 return this; 773 }, 774 775 // @method multiplyBy(num: Number): Point 776 // Returns the result of multiplication of the current point by the given number. 777 multiplyBy: function (num) { 778 return this.clone()._multiplyBy(num); 779 }, 780 781 _multiplyBy: function (num) { 782 this.x *= num; 783 this.y *= num; 784 return this; 785 }, 786 787 // @method scaleBy(scale: Point): Point 788 // Multiply each coordinate of the current point by each coordinate of 789 // `scale`. In linear algebra terms, multiply the point by the 790 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 791 // defined by `scale`. 792 scaleBy: function (point) { 793 return new Point(this.x * point.x, this.y * point.y); 794 }, 795 796 // @method unscaleBy(scale: Point): Point 797 // Inverse of `scaleBy`. Divide each coordinate of the current point by 798 // each coordinate of `scale`. 799 unscaleBy: function (point) { 800 return new Point(this.x / point.x, this.y / point.y); 801 }, 802 803 // @method round(): Point 804 // Returns a copy of the current point with rounded coordinates. 805 round: function () { 806 return this.clone()._round(); 807 }, 808 809 _round: function () { 810 this.x = Math.round(this.x); 811 this.y = Math.round(this.y); 812 return this; 813 }, 814 815 // @method floor(): Point 816 // Returns a copy of the current point with floored coordinates (rounded down). 817 floor: function () { 818 return this.clone()._floor(); 819 }, 820 821 _floor: function () { 822 this.x = Math.floor(this.x); 823 this.y = Math.floor(this.y); 824 return this; 825 }, 826 827 // @method ceil(): Point 828 // Returns a copy of the current point with ceiled coordinates (rounded up). 829 ceil: function () { 830 return this.clone()._ceil(); 831 }, 832 833 _ceil: function () { 834 this.x = Math.ceil(this.x); 835 this.y = Math.ceil(this.y); 836 return this; 837 }, 838 839 // @method trunc(): Point 840 // Returns a copy of the current point with truncated coordinates (rounded towards zero). 841 trunc: function () { 842 return this.clone()._trunc(); 843 }, 844 845 _trunc: function () { 846 this.x = trunc(this.x); 847 this.y = trunc(this.y); 848 return this; 849 }, 850 851 // @method distanceTo(otherPoint: Point): Number 852 // Returns the cartesian distance between the current and the given points. 853 distanceTo: function (point) { 854 point = toPoint(point); 855 856 var x = point.x - this.x, 857 y = point.y - this.y; 858 859 return Math.sqrt(x * x + y * y); 860 }, 861 862 // @method equals(otherPoint: Point): Boolean 863 // Returns `true` if the given point has the same coordinates. 864 equals: function (point) { 865 point = toPoint(point); 866 867 return point.x === this.x && 868 point.y === this.y; 869 }, 870 871 // @method contains(otherPoint: Point): Boolean 872 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 873 contains: function (point) { 874 point = toPoint(point); 875 876 return Math.abs(point.x) <= Math.abs(this.x) && 877 Math.abs(point.y) <= Math.abs(this.y); 878 }, 879 880 // @method toString(): String 881 // Returns a string representation of the point for debugging purposes. 882 toString: function () { 883 return 'Point(' + 884 formatNum(this.x) + ', ' + 885 formatNum(this.y) + ')'; 886 } 887 }; 888 889 // @factory L.point(x: Number, y: Number, round?: Boolean) 890 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 891 892 // @alternative 893 // @factory L.point(coords: Number[]) 894 // Expects an array of the form `[x, y]` instead. 895 896 // @alternative 897 // @factory L.point(coords: Object) 898 // Expects a plain object of the form `{x: Number, y: Number}` instead. 899 function toPoint(x, y, round) { 900 if (x instanceof Point) { 901 return x; 902 } 903 if (isArray(x)) { 904 return new Point(x[0], x[1]); 905 } 906 if (x === undefined || x === null) { 907 return x; 908 } 909 if (typeof x === 'object' && 'x' in x && 'y' in x) { 910 return new Point(x.x, x.y); 911 } 912 return new Point(x, y, round); 913 } 914 915 /* 916 * @class Bounds 917 * @aka L.Bounds 918 * 919 * Represents a rectangular area in pixel coordinates. 920 * 921 * @example 922 * 923 * ```js 924 * var p1 = L.point(10, 10), 925 * p2 = L.point(40, 60), 926 * bounds = L.bounds(p1, p2); 927 * ``` 928 * 929 * 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: 930 * 931 * ```js 932 * otherBounds.intersects([[10, 10], [40, 60]]); 933 * ``` 934 * 935 * Note that `Bounds` does not inherit from Leafet's `Class` object, 936 * which means new classes can't inherit from it, and new methods 937 * can't be added to it with the `include` function. 938 */ 939 940 function Bounds(a, b) { 941 if (!a) { return; } 942 943 var points = b ? [a, b] : a; 944 945 for (var i = 0, len = points.length; i < len; i++) { 946 this.extend(points[i]); 947 } 948 } 949 950 Bounds.prototype = { 951 // @method extend(point: Point): this 952 // Extends the bounds to contain the given point. 953 extend: function (point) { // (Point) 954 point = toPoint(point); 955 956 // @property min: Point 957 // The top left corner of the rectangle. 958 // @property max: Point 959 // The bottom right corner of the rectangle. 960 if (!this.min && !this.max) { 961 this.min = point.clone(); 962 this.max = point.clone(); 963 } else { 964 this.min.x = Math.min(point.x, this.min.x); 965 this.max.x = Math.max(point.x, this.max.x); 966 this.min.y = Math.min(point.y, this.min.y); 967 this.max.y = Math.max(point.y, this.max.y); 968 } 969 return this; 970 }, 971 972 // @method getCenter(round?: Boolean): Point 973 // Returns the center point of the bounds. 974 getCenter: function (round) { 975 return new Point( 976 (this.min.x + this.max.x) / 2, 977 (this.min.y + this.max.y) / 2, round); 978 }, 979 980 // @method getBottomLeft(): Point 981 // Returns the bottom-left point of the bounds. 982 getBottomLeft: function () { 983 return new Point(this.min.x, this.max.y); 984 }, 985 986 // @method getTopRight(): Point 987 // Returns the top-right point of the bounds. 988 getTopRight: function () { // -> Point 989 return new Point(this.max.x, this.min.y); 990 }, 991 992 // @method getTopLeft(): Point 993 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 994 getTopLeft: function () { 995 return this.min; // left, top 996 }, 997 998 // @method getBottomRight(): Point 999 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 1000 getBottomRight: function () { 1001 return this.max; // right, bottom 1002 }, 1003 1004 // @method getSize(): Point 1005 // Returns the size of the given bounds 1006 getSize: function () { 1007 return this.max.subtract(this.min); 1008 }, 1009 1010 // @method contains(otherBounds: Bounds): Boolean 1011 // Returns `true` if the rectangle contains the given one. 1012 // @alternative 1013 // @method contains(point: Point): Boolean 1014 // Returns `true` if the rectangle contains the given point. 1015 contains: function (obj) { 1016 var min, max; 1017 1018 if (typeof obj[0] === 'number' || obj instanceof Point) { 1019 obj = toPoint(obj); 1020 } else { 1021 obj = toBounds(obj); 1022 } 1023 1024 if (obj instanceof Bounds) { 1025 min = obj.min; 1026 max = obj.max; 1027 } else { 1028 min = max = obj; 1029 } 1030 1031 return (min.x >= this.min.x) && 1032 (max.x <= this.max.x) && 1033 (min.y >= this.min.y) && 1034 (max.y <= this.max.y); 1035 }, 1036 1037 // @method intersects(otherBounds: Bounds): Boolean 1038 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1039 // intersect if they have at least one point in common. 1040 intersects: function (bounds) { // (Bounds) -> Boolean 1041 bounds = toBounds(bounds); 1042 1043 var min = this.min, 1044 max = this.max, 1045 min2 = bounds.min, 1046 max2 = bounds.max, 1047 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1048 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1049 1050 return xIntersects && yIntersects; 1051 }, 1052 1053 // @method overlaps(otherBounds: Bounds): Boolean 1054 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1055 // overlap if their intersection is an area. 1056 overlaps: function (bounds) { // (Bounds) -> Boolean 1057 bounds = toBounds(bounds); 1058 1059 var min = this.min, 1060 max = this.max, 1061 min2 = bounds.min, 1062 max2 = bounds.max, 1063 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1064 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1065 1066 return xOverlaps && yOverlaps; 1067 }, 1068 1069 isValid: function () { 1070 return !!(this.min && this.max); 1071 } 1072 }; 1073 1074 1075 // @factory L.bounds(corner1: Point, corner2: Point) 1076 // Creates a Bounds object from two corners coordinate pairs. 1077 // @alternative 1078 // @factory L.bounds(points: Point[]) 1079 // Creates a Bounds object from the given array of points. 1080 function toBounds(a, b) { 1081 if (!a || a instanceof Bounds) { 1082 return a; 1083 } 1084 return new Bounds(a, b); 1085 } 1086 1087 /* 1088 * @class LatLngBounds 1089 * @aka L.LatLngBounds 1090 * 1091 * Represents a rectangular geographical area on a map. 1092 * 1093 * @example 1094 * 1095 * ```js 1096 * var corner1 = L.latLng(40.712, -74.227), 1097 * corner2 = L.latLng(40.774, -74.125), 1098 * bounds = L.latLngBounds(corner1, corner2); 1099 * ``` 1100 * 1101 * 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: 1102 * 1103 * ```js 1104 * map.fitBounds([ 1105 * [40.712, -74.227], 1106 * [40.774, -74.125] 1107 * ]); 1108 * ``` 1109 * 1110 * 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. 1111 * 1112 * Note that `LatLngBounds` does not inherit from Leafet's `Class` object, 1113 * which means new classes can't inherit from it, and new methods 1114 * can't be added to it with the `include` function. 1115 */ 1116 1117 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1118 if (!corner1) { return; } 1119 1120 var latlngs = corner2 ? [corner1, corner2] : corner1; 1121 1122 for (var i = 0, len = latlngs.length; i < len; i++) { 1123 this.extend(latlngs[i]); 1124 } 1125 } 1126 1127 LatLngBounds.prototype = { 1128 1129 // @method extend(latlng: LatLng): this 1130 // Extend the bounds to contain the given point 1131 1132 // @alternative 1133 // @method extend(otherBounds: LatLngBounds): this 1134 // Extend the bounds to contain the given bounds 1135 extend: function (obj) { 1136 var sw = this._southWest, 1137 ne = this._northEast, 1138 sw2, ne2; 1139 1140 if (obj instanceof LatLng) { 1141 sw2 = obj; 1142 ne2 = obj; 1143 1144 } else if (obj instanceof LatLngBounds) { 1145 sw2 = obj._southWest; 1146 ne2 = obj._northEast; 1147 1148 if (!sw2 || !ne2) { return this; } 1149 1150 } else { 1151 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 1152 } 1153 1154 if (!sw && !ne) { 1155 this._southWest = new LatLng(sw2.lat, sw2.lng); 1156 this._northEast = new LatLng(ne2.lat, ne2.lng); 1157 } else { 1158 sw.lat = Math.min(sw2.lat, sw.lat); 1159 sw.lng = Math.min(sw2.lng, sw.lng); 1160 ne.lat = Math.max(ne2.lat, ne.lat); 1161 ne.lng = Math.max(ne2.lng, ne.lng); 1162 } 1163 1164 return this; 1165 }, 1166 1167 // @method pad(bufferRatio: Number): LatLngBounds 1168 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1169 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1170 // Negative values will retract the bounds. 1171 pad: function (bufferRatio) { 1172 var sw = this._southWest, 1173 ne = this._northEast, 1174 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1175 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1176 1177 return new LatLngBounds( 1178 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1179 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1180 }, 1181 1182 // @method getCenter(): LatLng 1183 // Returns the center point of the bounds. 1184 getCenter: function () { 1185 return new LatLng( 1186 (this._southWest.lat + this._northEast.lat) / 2, 1187 (this._southWest.lng + this._northEast.lng) / 2); 1188 }, 1189 1190 // @method getSouthWest(): LatLng 1191 // Returns the south-west point of the bounds. 1192 getSouthWest: function () { 1193 return this._southWest; 1194 }, 1195 1196 // @method getNorthEast(): LatLng 1197 // Returns the north-east point of the bounds. 1198 getNorthEast: function () { 1199 return this._northEast; 1200 }, 1201 1202 // @method getNorthWest(): LatLng 1203 // Returns the north-west point of the bounds. 1204 getNorthWest: function () { 1205 return new LatLng(this.getNorth(), this.getWest()); 1206 }, 1207 1208 // @method getSouthEast(): LatLng 1209 // Returns the south-east point of the bounds. 1210 getSouthEast: function () { 1211 return new LatLng(this.getSouth(), this.getEast()); 1212 }, 1213 1214 // @method getWest(): Number 1215 // Returns the west longitude of the bounds 1216 getWest: function () { 1217 return this._southWest.lng; 1218 }, 1219 1220 // @method getSouth(): Number 1221 // Returns the south latitude of the bounds 1222 getSouth: function () { 1223 return this._southWest.lat; 1224 }, 1225 1226 // @method getEast(): Number 1227 // Returns the east longitude of the bounds 1228 getEast: function () { 1229 return this._northEast.lng; 1230 }, 1231 1232 // @method getNorth(): Number 1233 // Returns the north latitude of the bounds 1234 getNorth: function () { 1235 return this._northEast.lat; 1236 }, 1237 1238 // @method contains(otherBounds: LatLngBounds): Boolean 1239 // Returns `true` if the rectangle contains the given one. 1240 1241 // @alternative 1242 // @method contains (latlng: LatLng): Boolean 1243 // Returns `true` if the rectangle contains the given point. 1244 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1245 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { 1246 obj = toLatLng(obj); 1247 } else { 1248 obj = toLatLngBounds(obj); 1249 } 1250 1251 var sw = this._southWest, 1252 ne = this._northEast, 1253 sw2, ne2; 1254 1255 if (obj instanceof LatLngBounds) { 1256 sw2 = obj.getSouthWest(); 1257 ne2 = obj.getNorthEast(); 1258 } else { 1259 sw2 = ne2 = obj; 1260 } 1261 1262 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1263 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1264 }, 1265 1266 // @method intersects(otherBounds: LatLngBounds): Boolean 1267 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1268 intersects: function (bounds) { 1269 bounds = toLatLngBounds(bounds); 1270 1271 var sw = this._southWest, 1272 ne = this._northEast, 1273 sw2 = bounds.getSouthWest(), 1274 ne2 = bounds.getNorthEast(), 1275 1276 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1277 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1278 1279 return latIntersects && lngIntersects; 1280 }, 1281 1282 // @method overlaps(otherBounds: Bounds): Boolean 1283 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1284 overlaps: function (bounds) { 1285 bounds = toLatLngBounds(bounds); 1286 1287 var sw = this._southWest, 1288 ne = this._northEast, 1289 sw2 = bounds.getSouthWest(), 1290 ne2 = bounds.getNorthEast(), 1291 1292 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1293 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1294 1295 return latOverlaps && lngOverlaps; 1296 }, 1297 1298 // @method toBBoxString(): String 1299 // 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. 1300 toBBoxString: function () { 1301 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1302 }, 1303 1304 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 1305 // 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. 1306 equals: function (bounds, maxMargin) { 1307 if (!bounds) { return false; } 1308 1309 bounds = toLatLngBounds(bounds); 1310 1311 return this._southWest.equals(bounds.getSouthWest(), maxMargin) && 1312 this._northEast.equals(bounds.getNorthEast(), maxMargin); 1313 }, 1314 1315 // @method isValid(): Boolean 1316 // Returns `true` if the bounds are properly initialized. 1317 isValid: function () { 1318 return !!(this._southWest && this._northEast); 1319 } 1320 }; 1321 1322 // TODO International date line? 1323 1324 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1325 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1326 1327 // @alternative 1328 // @factory L.latLngBounds(latlngs: LatLng[]) 1329 // 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). 1330 function toLatLngBounds(a, b) { 1331 if (a instanceof LatLngBounds) { 1332 return a; 1333 } 1334 return new LatLngBounds(a, b); 1335 } 1336 1337 /* @class LatLng 1338 * @aka L.LatLng 1339 * 1340 * Represents a geographical point with a certain latitude and longitude. 1341 * 1342 * @example 1343 * 1344 * ``` 1345 * var latlng = L.latLng(50.5, 30.5); 1346 * ``` 1347 * 1348 * 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: 1349 * 1350 * ``` 1351 * map.panTo([50, 30]); 1352 * map.panTo({lon: 30, lat: 50}); 1353 * map.panTo({lat: 50, lng: 30}); 1354 * map.panTo(L.latLng(50, 30)); 1355 * ``` 1356 * 1357 * Note that `LatLng` does not inherit from Leaflet's `Class` object, 1358 * which means new classes can't inherit from it, and new methods 1359 * can't be added to it with the `include` function. 1360 */ 1361 1362 function LatLng(lat, lng, alt) { 1363 if (isNaN(lat) || isNaN(lng)) { 1364 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1365 } 1366 1367 // @property lat: Number 1368 // Latitude in degrees 1369 this.lat = +lat; 1370 1371 // @property lng: Number 1372 // Longitude in degrees 1373 this.lng = +lng; 1374 1375 // @property alt: Number 1376 // Altitude in meters (optional) 1377 if (alt !== undefined) { 1378 this.alt = +alt; 1379 } 1380 } 1381 1382 LatLng.prototype = { 1383 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1384 // 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. 1385 equals: function (obj, maxMargin) { 1386 if (!obj) { return false; } 1387 1388 obj = toLatLng(obj); 1389 1390 var margin = Math.max( 1391 Math.abs(this.lat - obj.lat), 1392 Math.abs(this.lng - obj.lng)); 1393 1394 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1395 }, 1396 1397 // @method toString(): String 1398 // Returns a string representation of the point (for debugging purposes). 1399 toString: function (precision) { 1400 return 'LatLng(' + 1401 formatNum(this.lat, precision) + ', ' + 1402 formatNum(this.lng, precision) + ')'; 1403 }, 1404 1405 // @method distanceTo(otherLatLng: LatLng): Number 1406 // 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). 1407 distanceTo: function (other) { 1408 return Earth.distance(this, toLatLng(other)); 1409 }, 1410 1411 // @method wrap(): LatLng 1412 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1413 wrap: function () { 1414 return Earth.wrapLatLng(this); 1415 }, 1416 1417 // @method toBounds(sizeInMeters: Number): LatLngBounds 1418 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1419 toBounds: function (sizeInMeters) { 1420 var latAccuracy = 180 * sizeInMeters / 40075017, 1421 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1422 1423 return toLatLngBounds( 1424 [this.lat - latAccuracy, this.lng - lngAccuracy], 1425 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1426 }, 1427 1428 clone: function () { 1429 return new LatLng(this.lat, this.lng, this.alt); 1430 } 1431 }; 1432 1433 1434 1435 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1436 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1437 1438 // @alternative 1439 // @factory L.latLng(coords: Array): LatLng 1440 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1441 1442 // @alternative 1443 // @factory L.latLng(coords: Object): LatLng 1444 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1445 1446 function toLatLng(a, b, c) { 1447 if (a instanceof LatLng) { 1448 return a; 1449 } 1450 if (isArray(a) && typeof a[0] !== 'object') { 1451 if (a.length === 3) { 1452 return new LatLng(a[0], a[1], a[2]); 1453 } 1454 if (a.length === 2) { 1455 return new LatLng(a[0], a[1]); 1456 } 1457 return null; 1458 } 1459 if (a === undefined || a === null) { 1460 return a; 1461 } 1462 if (typeof a === 'object' && 'lat' in a) { 1463 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1464 } 1465 if (b === undefined) { 1466 return null; 1467 } 1468 return new LatLng(a, b, c); 1469 } 1470 1471 /* 1472 * @namespace CRS 1473 * @crs L.CRS.Base 1474 * Object that defines coordinate reference systems for projecting 1475 * geographical points into pixel (screen) coordinates and back (and to 1476 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 1477 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). 1478 * 1479 * Leaflet defines the most usual CRSs by default. If you want to use a 1480 * CRS not defined by default, take a look at the 1481 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 1482 * 1483 * Note that the CRS instances do not inherit from Leafet's `Class` object, 1484 * and can't be instantiated. Also, new classes can't inherit from them, 1485 * and methods can't be added to them with the `include` function. 1486 */ 1487 1488 var CRS = { 1489 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1490 // Projects geographical coordinates into pixel coordinates for a given zoom. 1491 latLngToPoint: function (latlng, zoom) { 1492 var projectedPoint = this.projection.project(latlng), 1493 scale = this.scale(zoom); 1494 1495 return this.transformation._transform(projectedPoint, scale); 1496 }, 1497 1498 // @method pointToLatLng(point: Point, zoom: Number): LatLng 1499 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1500 // zoom into geographical coordinates. 1501 pointToLatLng: function (point, zoom) { 1502 var scale = this.scale(zoom), 1503 untransformedPoint = this.transformation.untransform(point, scale); 1504 1505 return this.projection.unproject(untransformedPoint); 1506 }, 1507 1508 // @method project(latlng: LatLng): Point 1509 // Projects geographical coordinates into coordinates in units accepted for 1510 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1511 project: function (latlng) { 1512 return this.projection.project(latlng); 1513 }, 1514 1515 // @method unproject(point: Point): LatLng 1516 // Given a projected coordinate returns the corresponding LatLng. 1517 // The inverse of `project`. 1518 unproject: function (point) { 1519 return this.projection.unproject(point); 1520 }, 1521 1522 // @method scale(zoom: Number): Number 1523 // Returns the scale used when transforming projected coordinates into 1524 // pixel coordinates for a particular zoom. For example, it returns 1525 // `256 * 2^zoom` for Mercator-based CRS. 1526 scale: function (zoom) { 1527 return 256 * Math.pow(2, zoom); 1528 }, 1529 1530 // @method zoom(scale: Number): Number 1531 // Inverse of `scale()`, returns the zoom level corresponding to a scale 1532 // factor of `scale`. 1533 zoom: function (scale) { 1534 return Math.log(scale / 256) / Math.LN2; 1535 }, 1536 1537 // @method getProjectedBounds(zoom: Number): Bounds 1538 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1539 getProjectedBounds: function (zoom) { 1540 if (this.infinite) { return null; } 1541 1542 var b = this.projection.bounds, 1543 s = this.scale(zoom), 1544 min = this.transformation.transform(b.min, s), 1545 max = this.transformation.transform(b.max, s); 1546 1547 return new Bounds(min, max); 1548 }, 1549 1550 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1551 // Returns the distance between two geographical coordinates. 1552 1553 // @property code: String 1554 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1555 // 1556 // @property wrapLng: Number[] 1557 // An array of two numbers defining whether the longitude (horizontal) coordinate 1558 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1559 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1560 // 1561 // @property wrapLat: Number[] 1562 // Like `wrapLng`, but for the latitude (vertical) axis. 1563 1564 // wrapLng: [min, max], 1565 // wrapLat: [min, max], 1566 1567 // @property infinite: Boolean 1568 // If true, the coordinate space will be unbounded (infinite in both axes) 1569 infinite: false, 1570 1571 // @method wrapLatLng(latlng: LatLng): LatLng 1572 // Returns a `LatLng` where lat and lng has been wrapped according to the 1573 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1574 wrapLatLng: function (latlng) { 1575 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 1576 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 1577 alt = latlng.alt; 1578 1579 return new LatLng(lat, lng, alt); 1580 }, 1581 1582 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1583 // Returns a `LatLngBounds` with the same size as the given one, ensuring 1584 // that its center is within the CRS's bounds. 1585 // Only accepts actual `L.LatLngBounds` instances, not arrays. 1586 wrapLatLngBounds: function (bounds) { 1587 var center = bounds.getCenter(), 1588 newCenter = this.wrapLatLng(center), 1589 latShift = center.lat - newCenter.lat, 1590 lngShift = center.lng - newCenter.lng; 1591 1592 if (latShift === 0 && lngShift === 0) { 1593 return bounds; 1594 } 1595 1596 var sw = bounds.getSouthWest(), 1597 ne = bounds.getNorthEast(), 1598 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), 1599 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1600 1601 return new LatLngBounds(newSw, newNe); 1602 } 1603 }; 1604 1605 /* 1606 * @namespace CRS 1607 * @crs L.CRS.Earth 1608 * 1609 * Serves as the base for CRS that are global such that they cover the earth. 1610 * Can only be used as the base for other CRS and cannot be used directly, 1611 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 1612 * meters. 1613 */ 1614 1615 var Earth = extend({}, CRS, { 1616 wrapLng: [-180, 180], 1617 1618 // Mean Earth Radius, as recommended for use by 1619 // the International Union of Geodesy and Geophysics, 1620 // see http://rosettacode.org/wiki/Haversine_formula 1621 R: 6371000, 1622 1623 // distance between two geographical points using spherical law of cosines approximation 1624 distance: function (latlng1, latlng2) { 1625 var rad = Math.PI / 180, 1626 lat1 = latlng1.lat * rad, 1627 lat2 = latlng2.lat * rad, 1628 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), 1629 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), 1630 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, 1631 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1632 return this.R * c; 1633 } 1634 }); 1635 1636 /* 1637 * @namespace Projection 1638 * @projection L.Projection.SphericalMercator 1639 * 1640 * Spherical Mercator projection — the most common projection for online maps, 1641 * used by almost all free and commercial tile providers. Assumes that Earth is 1642 * a sphere. Used by the `EPSG:3857` CRS. 1643 */ 1644 1645 var earthRadius = 6378137; 1646 1647 var SphericalMercator = { 1648 1649 R: earthRadius, 1650 MAX_LATITUDE: 85.0511287798, 1651 1652 project: function (latlng) { 1653 var d = Math.PI / 180, 1654 max = this.MAX_LATITUDE, 1655 lat = Math.max(Math.min(max, latlng.lat), -max), 1656 sin = Math.sin(lat * d); 1657 1658 return new Point( 1659 this.R * latlng.lng * d, 1660 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 1661 }, 1662 1663 unproject: function (point) { 1664 var d = 180 / Math.PI; 1665 1666 return new LatLng( 1667 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 1668 point.x * d / this.R); 1669 }, 1670 1671 bounds: (function () { 1672 var d = earthRadius * Math.PI; 1673 return new Bounds([-d, -d], [d, d]); 1674 })() 1675 }; 1676 1677 /* 1678 * @class Transformation 1679 * @aka L.Transformation 1680 * 1681 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1682 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1683 * the reverse. Used by Leaflet in its projections code. 1684 * 1685 * @example 1686 * 1687 * ```js 1688 * var transformation = L.transformation(2, 5, -1, 10), 1689 * p = L.point(1, 2), 1690 * p2 = transformation.transform(p), // L.point(7, 8) 1691 * p3 = transformation.untransform(p2); // L.point(1, 2) 1692 * ``` 1693 */ 1694 1695 1696 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1697 // Creates a `Transformation` object with the given coefficients. 1698 function Transformation(a, b, c, d) { 1699 if (isArray(a)) { 1700 // use array properties 1701 this._a = a[0]; 1702 this._b = a[1]; 1703 this._c = a[2]; 1704 this._d = a[3]; 1705 return; 1706 } 1707 this._a = a; 1708 this._b = b; 1709 this._c = c; 1710 this._d = d; 1711 } 1712 1713 Transformation.prototype = { 1714 // @method transform(point: Point, scale?: Number): Point 1715 // Returns a transformed point, optionally multiplied by the given scale. 1716 // Only accepts actual `L.Point` instances, not arrays. 1717 transform: function (point, scale) { // (Point, Number) -> Point 1718 return this._transform(point.clone(), scale); 1719 }, 1720 1721 // destructive transform (faster) 1722 _transform: function (point, scale) { 1723 scale = scale || 1; 1724 point.x = scale * (this._a * point.x + this._b); 1725 point.y = scale * (this._c * point.y + this._d); 1726 return point; 1727 }, 1728 1729 // @method untransform(point: Point, scale?: Number): Point 1730 // Returns the reverse transformation of the given point, optionally divided 1731 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1732 untransform: function (point, scale) { 1733 scale = scale || 1; 1734 return new Point( 1735 (point.x / scale - this._b) / this._a, 1736 (point.y / scale - this._d) / this._c); 1737 } 1738 }; 1739 1740 // factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1741 1742 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1743 // Instantiates a Transformation object with the given coefficients. 1744 1745 // @alternative 1746 // @factory L.transformation(coefficients: Array): Transformation 1747 // Expects an coefficients array of the form 1748 // `[a: Number, b: Number, c: Number, d: Number]`. 1749 1750 function toTransformation(a, b, c, d) { 1751 return new Transformation(a, b, c, d); 1752 } 1753 1754 /* 1755 * @namespace CRS 1756 * @crs L.CRS.EPSG3857 1757 * 1758 * The most common CRS for online maps, used by almost all free and commercial 1759 * tile providers. Uses Spherical Mercator projection. Set in by default in 1760 * Map's `crs` option. 1761 */ 1762 1763 var EPSG3857 = extend({}, Earth, { 1764 code: 'EPSG:3857', 1765 projection: SphericalMercator, 1766 1767 transformation: (function () { 1768 var scale = 0.5 / (Math.PI * SphericalMercator.R); 1769 return toTransformation(scale, 0.5, -scale, 0.5); 1770 }()) 1771 }); 1772 1773 var EPSG900913 = extend({}, EPSG3857, { 1774 code: 'EPSG:900913' 1775 }); 1776 1777 // @namespace SVG; @section 1778 // There are several static functions which can be called without instantiating L.SVG: 1779 1780 // @function create(name: String): SVGElement 1781 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 1782 // corresponding to the class name passed. For example, using 'line' will return 1783 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 1784 function svgCreate(name) { 1785 return document.createElementNS('http://www.w3.org/2000/svg', name); 1786 } 1787 1788 // @function pointsToPath(rings: Point[], closed: Boolean): String 1789 // Generates a SVG path string for multiple rings, with each ring turning 1790 // into "M..L..L.." instructions 1791 function pointsToPath(rings, closed) { 1792 var str = '', 1793 i, j, len, len2, points, p; 1794 1795 for (i = 0, len = rings.length; i < len; i++) { 1796 points = rings[i]; 1797 1798 for (j = 0, len2 = points.length; j < len2; j++) { 1799 p = points[j]; 1800 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 1801 } 1802 1803 // closes the ring for polygons; "x" is VML syntax 1804 str += closed ? (svg ? 'z' : 'x') : ''; 1805 } 1806 1807 // SVG complains about empty path strings 1808 return str || 'M0 0'; 1809 } 1810 1811 /* 1812 * @namespace Browser 1813 * @aka L.Browser 1814 * 1815 * A namespace with static properties for browser/feature detection used by Leaflet internally. 1816 * 1817 * @example 1818 * 1819 * ```js 1820 * if (L.Browser.ielt9) { 1821 * alert('Upgrade your browser, dude!'); 1822 * } 1823 * ``` 1824 */ 1825 1826 var style$1 = document.documentElement.style; 1827 1828 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 1829 var ie = 'ActiveXObject' in window; 1830 1831 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 1832 var ielt9 = ie && !document.addEventListener; 1833 1834 // @property edge: Boolean; `true` for the Edge web browser. 1835 var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 1836 1837 // @property webkit: Boolean; 1838 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 1839 var webkit = userAgentContains('webkit'); 1840 1841 // @property android: Boolean 1842 // `true` for any browser running on an Android platform. 1843 var android = userAgentContains('android'); 1844 1845 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3. 1846 var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 1847 1848 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 1849 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 1850 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome) 1851 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 1852 1853 // @property opera: Boolean; `true` for the Opera browser 1854 var opera = !!window.opera; 1855 1856 // @property chrome: Boolean; `true` for the Chrome browser. 1857 var chrome = userAgentContains('chrome'); 1858 1859 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 1860 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 1861 1862 // @property safari: Boolean; `true` for the Safari browser. 1863 var safari = !chrome && userAgentContains('safari'); 1864 1865 var phantom = userAgentContains('phantom'); 1866 1867 // @property opera12: Boolean 1868 // `true` for the Opera browser supporting CSS transforms (version 12 or later). 1869 var opera12 = 'OTransition' in style$1; 1870 1871 // @property win: Boolean; `true` when the browser is running in a Windows platform 1872 var win = navigator.platform.indexOf('Win') === 0; 1873 1874 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. 1875 var ie3d = ie && ('transition' in style$1); 1876 1877 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 1878 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 1879 1880 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 1881 var gecko3d = 'MozPerspective' in style$1; 1882 1883 // @property any3d: Boolean 1884 // `true` for all browsers supporting CSS transforms. 1885 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1886 1887 // @property mobile: Boolean; `true` for all browsers running in a mobile device. 1888 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 1889 1890 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 1891 var mobileWebkit = mobile && webkit; 1892 1893 // @property mobileWebkit3d: Boolean 1894 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 1895 var mobileWebkit3d = mobile && webkit3d; 1896 1897 // @property msPointer: Boolean 1898 // `true` for browsers implementing the Microsoft touch events model (notably IE10). 1899 var msPointer = !window.PointerEvent && window.MSPointerEvent; 1900 1901 // @property pointer: Boolean 1902 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 1903 var pointer = !webkit && !!(window.PointerEvent || msPointer); 1904 1905 // @property touch: Boolean 1906 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 1907 // This does not necessarily mean that the browser is running in a computer with 1908 // a touchscreen, it only means that the browser is capable of understanding 1909 // touch events. 1910 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || 1911 (window.DocumentTouch && document instanceof window.DocumentTouch)); 1912 1913 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 1914 var mobileOpera = mobile && opera; 1915 1916 // @property mobileGecko: Boolean 1917 // `true` for gecko-based browsers running in a mobile device. 1918 var mobileGecko = mobile && gecko; 1919 1920 // @property retina: Boolean 1921 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 1922 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 1923 1924 // @property passiveEvents: Boolean 1925 // `true` for browsers that support passive events. 1926 var passiveEvents = (function () { 1927 var supportsPassiveOption = false; 1928 try { 1929 var opts = Object.defineProperty({}, 'passive', { 1930 get: function () { 1931 supportsPassiveOption = true; 1932 } 1933 }); 1934 window.addEventListener('testPassiveEventSupport', falseFn, opts); 1935 window.removeEventListener('testPassiveEventSupport', falseFn, opts); 1936 } catch (e) { 1937 // Errors can safely be ignored since this is only a browser support test. 1938 } 1939 return supportsPassiveOption; 1940 }); 1941 1942 // @property canvas: Boolean 1943 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 1944 var canvas = (function () { 1945 return !!document.createElement('canvas').getContext; 1946 }()); 1947 1948 // @property svg: Boolean 1949 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 1950 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect); 1951 1952 // @property vml: Boolean 1953 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 1954 var vml = !svg && (function () { 1955 try { 1956 var div = document.createElement('div'); 1957 div.innerHTML = '<v:shape adj="1"/>'; 1958 1959 var shape = div.firstChild; 1960 shape.style.behavior = 'url(#default#VML)'; 1961 1962 return shape && (typeof shape.adj === 'object'); 1963 1964 } catch (e) { 1965 return false; 1966 } 1967 }()); 1968 1969 1970 function userAgentContains(str) { 1971 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 1972 } 1973 1974 1975 var Browser = (Object.freeze || Object)({ 1976 ie: ie, 1977 ielt9: ielt9, 1978 edge: edge, 1979 webkit: webkit, 1980 android: android, 1981 android23: android23, 1982 androidStock: androidStock, 1983 opera: opera, 1984 chrome: chrome, 1985 gecko: gecko, 1986 safari: safari, 1987 phantom: phantom, 1988 opera12: opera12, 1989 win: win, 1990 ie3d: ie3d, 1991 webkit3d: webkit3d, 1992 gecko3d: gecko3d, 1993 any3d: any3d, 1994 mobile: mobile, 1995 mobileWebkit: mobileWebkit, 1996 mobileWebkit3d: mobileWebkit3d, 1997 msPointer: msPointer, 1998 pointer: pointer, 1999 touch: touch, 2000 mobileOpera: mobileOpera, 2001 mobileGecko: mobileGecko, 2002 retina: retina, 2003 passiveEvents: passiveEvents, 2004 canvas: canvas, 2005 svg: svg, 2006 vml: vml 2007 }); 2008 2009 /* 2010 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 2011 */ 2012 2013 2014 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown'; 2015 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'; 2016 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'; 2017 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'; 2018 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION']; 2019 2020 var _pointers = {}; 2021 var _pointerDocListener = false; 2022 2023 // DomEvent.DoubleTap needs to know about this 2024 var _pointersCount = 0; 2025 2026 // Provides a touch events wrapper for (ms)pointer events. 2027 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2028 2029 function addPointerListener(obj, type, handler, id) { 2030 if (type === 'touchstart') { 2031 _addPointerStart(obj, handler, id); 2032 2033 } else if (type === 'touchmove') { 2034 _addPointerMove(obj, handler, id); 2035 2036 } else if (type === 'touchend') { 2037 _addPointerEnd(obj, handler, id); 2038 } 2039 2040 return this; 2041 } 2042 2043 function removePointerListener(obj, type, id) { 2044 var handler = obj['_leaflet_' + type + id]; 2045 2046 if (type === 'touchstart') { 2047 obj.removeEventListener(POINTER_DOWN, handler, false); 2048 2049 } else if (type === 'touchmove') { 2050 obj.removeEventListener(POINTER_MOVE, handler, false); 2051 2052 } else if (type === 'touchend') { 2053 obj.removeEventListener(POINTER_UP, handler, false); 2054 obj.removeEventListener(POINTER_CANCEL, handler, false); 2055 } 2056 2057 return this; 2058 } 2059 2060 function _addPointerStart(obj, handler, id) { 2061 var onDown = bind(function (e) { 2062 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { 2063 // In IE11, some touch events needs to fire for form controls, or 2064 // the controls will stop working. We keep a whitelist of tag names that 2065 // need these events. For other target tags, we prevent default on the event. 2066 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { 2067 preventDefault(e); 2068 } else { 2069 return; 2070 } 2071 } 2072 2073 _handlePointer(e, handler); 2074 }); 2075 2076 obj['_leaflet_touchstart' + id] = onDown; 2077 obj.addEventListener(POINTER_DOWN, onDown, false); 2078 2079 // need to keep track of what pointers and how many are active to provide e.touches emulation 2080 if (!_pointerDocListener) { 2081 // we listen documentElement as any drags that end by moving the touch off the screen get fired there 2082 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2083 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2084 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true); 2085 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2086 2087 _pointerDocListener = true; 2088 } 2089 } 2090 2091 function _globalPointerDown(e) { 2092 _pointers[e.pointerId] = e; 2093 _pointersCount++; 2094 } 2095 2096 function _globalPointerMove(e) { 2097 if (_pointers[e.pointerId]) { 2098 _pointers[e.pointerId] = e; 2099 } 2100 } 2101 2102 function _globalPointerUp(e) { 2103 delete _pointers[e.pointerId]; 2104 _pointersCount--; 2105 } 2106 2107 function _handlePointer(e, handler) { 2108 e.touches = []; 2109 for (var i in _pointers) { 2110 e.touches.push(_pointers[i]); 2111 } 2112 e.changedTouches = [e]; 2113 2114 handler(e); 2115 } 2116 2117 function _addPointerMove(obj, handler, id) { 2118 var onMove = function (e) { 2119 // don't fire touch moves when mouse isn't down 2120 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } 2121 2122 _handlePointer(e, handler); 2123 }; 2124 2125 obj['_leaflet_touchmove' + id] = onMove; 2126 obj.addEventListener(POINTER_MOVE, onMove, false); 2127 } 2128 2129 function _addPointerEnd(obj, handler, id) { 2130 var onUp = function (e) { 2131 _handlePointer(e, handler); 2132 }; 2133 2134 obj['_leaflet_touchend' + id] = onUp; 2135 obj.addEventListener(POINTER_UP, onUp, false); 2136 obj.addEventListener(POINTER_CANCEL, onUp, false); 2137 } 2138 2139 /* 2140 * Extends the event handling code with double tap support for mobile browsers. 2141 */ 2142 2143 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart'; 2144 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend'; 2145 var _pre = '_leaflet_'; 2146 2147 // inspired by Zepto touch code by Thomas Fuchs 2148 function addDoubleTapListener(obj, handler, id) { 2149 var last, touch$$1, 2150 doubleTap = false, 2151 delay = 250; 2152 2153 function onTouchStart(e) { 2154 var count; 2155 2156 if (pointer) { 2157 if ((!edge) || e.pointerType === 'mouse') { return; } 2158 count = _pointersCount; 2159 } else { 2160 count = e.touches.length; 2161 } 2162 2163 if (count > 1) { return; } 2164 2165 var now = Date.now(), 2166 delta = now - (last || now); 2167 2168 touch$$1 = e.touches ? e.touches[0] : e; 2169 doubleTap = (delta > 0 && delta <= delay); 2170 last = now; 2171 } 2172 2173 function onTouchEnd(e) { 2174 if (doubleTap && !touch$$1.cancelBubble) { 2175 if (pointer) { 2176 if ((!edge) || e.pointerType === 'mouse') { return; } 2177 // work around .type being readonly with MSPointer* events 2178 var newTouch = {}, 2179 prop, i; 2180 2181 for (i in touch$$1) { 2182 prop = touch$$1[i]; 2183 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop; 2184 } 2185 touch$$1 = newTouch; 2186 } 2187 touch$$1.type = 'dblclick'; 2188 touch$$1.button = 0; 2189 handler(touch$$1); 2190 last = null; 2191 } 2192 } 2193 2194 obj[_pre + _touchstart + id] = onTouchStart; 2195 obj[_pre + _touchend + id] = onTouchEnd; 2196 obj[_pre + 'dblclick' + id] = handler; 2197 2198 obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false); 2199 obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false); 2200 2201 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), 2202 // the browser doesn't fire touchend/pointerup events but does fire 2203 // native dblclicks. See #4127. 2204 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. 2205 obj.addEventListener('dblclick', handler, false); 2206 2207 return this; 2208 } 2209 2210 function removeDoubleTapListener(obj, id) { 2211 var touchstart = obj[_pre + _touchstart + id], 2212 touchend = obj[_pre + _touchend + id], 2213 dblclick = obj[_pre + 'dblclick' + id]; 2214 2215 obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false); 2216 obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false); 2217 if (!edge) { 2218 obj.removeEventListener('dblclick', dblclick, false); 2219 } 2220 2221 return this; 2222 } 2223 2224 /* 2225 * @namespace DomUtil 2226 * 2227 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2228 * tree, used by Leaflet internally. 2229 * 2230 * Most functions expecting or returning a `HTMLElement` also work for 2231 * SVG elements. The only difference is that classes refer to CSS classes 2232 * in HTML and SVG classes in SVG. 2233 */ 2234 2235 2236 // @property TRANSFORM: String 2237 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2238 var TRANSFORM = testProp( 2239 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2240 2241 // webkitTransition comes first because some browser versions that drop vendor prefix don't do 2242 // the same for the transitionend event, in particular the Android 4.1 stock browser 2243 2244 // @property TRANSITION: String 2245 // Vendor-prefixed transition style name. 2246 var TRANSITION = testProp( 2247 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2248 2249 // @property TRANSITION_END: String 2250 // Vendor-prefixed transitionend event name. 2251 var TRANSITION_END = 2252 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2253 2254 2255 // @function get(id: String|HTMLElement): HTMLElement 2256 // Returns an element given its DOM id, or returns the element itself 2257 // if it was passed directly. 2258 function get(id) { 2259 return typeof id === 'string' ? document.getElementById(id) : id; 2260 } 2261 2262 // @function getStyle(el: HTMLElement, styleAttrib: String): String 2263 // Returns the value for a certain style attribute on an element, 2264 // including computed values or values set through CSS. 2265 function getStyle(el, style) { 2266 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2267 2268 if ((!value || value === 'auto') && document.defaultView) { 2269 var css = document.defaultView.getComputedStyle(el, null); 2270 value = css ? css[style] : null; 2271 } 2272 return value === 'auto' ? null : value; 2273 } 2274 2275 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2276 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2277 function create$1(tagName, className, container) { 2278 var el = document.createElement(tagName); 2279 el.className = className || ''; 2280 2281 if (container) { 2282 container.appendChild(el); 2283 } 2284 return el; 2285 } 2286 2287 // @function remove(el: HTMLElement) 2288 // Removes `el` from its parent element 2289 function remove(el) { 2290 var parent = el.parentNode; 2291 if (parent) { 2292 parent.removeChild(el); 2293 } 2294 } 2295 2296 // @function empty(el: HTMLElement) 2297 // Removes all of `el`'s children elements from `el` 2298 function empty(el) { 2299 while (el.firstChild) { 2300 el.removeChild(el.firstChild); 2301 } 2302 } 2303 2304 // @function toFront(el: HTMLElement) 2305 // Makes `el` the last child of its parent, so it renders in front of the other children. 2306 function toFront(el) { 2307 var parent = el.parentNode; 2308 if (parent && parent.lastChild !== el) { 2309 parent.appendChild(el); 2310 } 2311 } 2312 2313 // @function toBack(el: HTMLElement) 2314 // Makes `el` the first child of its parent, so it renders behind the other children. 2315 function toBack(el) { 2316 var parent = el.parentNode; 2317 if (parent && parent.firstChild !== el) { 2318 parent.insertBefore(el, parent.firstChild); 2319 } 2320 } 2321 2322 // @function hasClass(el: HTMLElement, name: String): Boolean 2323 // Returns `true` if the element's class attribute contains `name`. 2324 function hasClass(el, name) { 2325 if (el.classList !== undefined) { 2326 return el.classList.contains(name); 2327 } 2328 var className = getClass(el); 2329 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2330 } 2331 2332 // @function addClass(el: HTMLElement, name: String) 2333 // Adds `name` to the element's class attribute. 2334 function addClass(el, name) { 2335 if (el.classList !== undefined) { 2336 var classes = splitWords(name); 2337 for (var i = 0, len = classes.length; i < len; i++) { 2338 el.classList.add(classes[i]); 2339 } 2340 } else if (!hasClass(el, name)) { 2341 var className = getClass(el); 2342 setClass(el, (className ? className + ' ' : '') + name); 2343 } 2344 } 2345 2346 // @function removeClass(el: HTMLElement, name: String) 2347 // Removes `name` from the element's class attribute. 2348 function removeClass(el, name) { 2349 if (el.classList !== undefined) { 2350 el.classList.remove(name); 2351 } else { 2352 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2353 } 2354 } 2355 2356 // @function setClass(el: HTMLElement, name: String) 2357 // Sets the element's class. 2358 function setClass(el, name) { 2359 if (el.className.baseVal === undefined) { 2360 el.className = name; 2361 } else { 2362 // in case of SVG element 2363 el.className.baseVal = name; 2364 } 2365 } 2366 2367 // @function getClass(el: HTMLElement): String 2368 // Returns the element's class. 2369 function getClass(el) { 2370 // Check if the element is an SVGElementInstance and use the correspondingElement instead 2371 // (Required for linked SVG elements in IE11.) 2372 if (el.correspondingElement) { 2373 el = el.correspondingElement; 2374 } 2375 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2376 } 2377 2378 // @function setOpacity(el: HTMLElement, opacity: Number) 2379 // Set the opacity of an element (including old IE support). 2380 // `opacity` must be a number from `0` to `1`. 2381 function setOpacity(el, value) { 2382 if ('opacity' in el.style) { 2383 el.style.opacity = value; 2384 } else if ('filter' in el.style) { 2385 _setOpacityIE(el, value); 2386 } 2387 } 2388 2389 function _setOpacityIE(el, value) { 2390 var filter = false, 2391 filterName = 'DXImageTransform.Microsoft.Alpha'; 2392 2393 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2394 try { 2395 filter = el.filters.item(filterName); 2396 } catch (e) { 2397 // don't set opacity to 1 if we haven't already set an opacity, 2398 // it isn't needed and breaks transparent pngs. 2399 if (value === 1) { return; } 2400 } 2401 2402 value = Math.round(value * 100); 2403 2404 if (filter) { 2405 filter.Enabled = (value !== 100); 2406 filter.Opacity = value; 2407 } else { 2408 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2409 } 2410 } 2411 2412 // @function testProp(props: String[]): String|false 2413 // Goes through the array of style names and returns the first name 2414 // that is a valid style name for an element. If no such name is found, 2415 // it returns false. Useful for vendor-prefixed styles like `transform`. 2416 function testProp(props) { 2417 var style = document.documentElement.style; 2418 2419 for (var i = 0; i < props.length; i++) { 2420 if (props[i] in style) { 2421 return props[i]; 2422 } 2423 } 2424 return false; 2425 } 2426 2427 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2428 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2429 // and optionally scaled by `scale`. Does not have an effect if the 2430 // browser doesn't support 3D CSS transforms. 2431 function setTransform(el, offset, scale) { 2432 var pos = offset || new Point(0, 0); 2433 2434 el.style[TRANSFORM] = 2435 (ie3d ? 2436 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2437 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2438 (scale ? ' scale(' + scale + ')' : ''); 2439 } 2440 2441 // @function setPosition(el: HTMLElement, position: Point) 2442 // Sets the position of `el` to coordinates specified by `position`, 2443 // using CSS translate or top/left positioning depending on the browser 2444 // (used by Leaflet internally to position its layers). 2445 function setPosition(el, point) { 2446 2447 /*eslint-disable */ 2448 el._leaflet_pos = point; 2449 /* eslint-enable */ 2450 2451 if (any3d) { 2452 setTransform(el, point); 2453 } else { 2454 el.style.left = point.x + 'px'; 2455 el.style.top = point.y + 'px'; 2456 } 2457 } 2458 2459 // @function getPosition(el: HTMLElement): Point 2460 // Returns the coordinates of an element previously positioned with setPosition. 2461 function getPosition(el) { 2462 // this method is only used for elements previously positioned using setPosition, 2463 // so it's safe to cache the position for performance 2464 2465 return el._leaflet_pos || new Point(0, 0); 2466 } 2467 2468 // @function disableTextSelection() 2469 // Prevents the user from generating `selectstart` DOM events, usually generated 2470 // when the user drags the mouse through a page with text. Used internally 2471 // by Leaflet to override the behaviour of any click-and-drag interaction on 2472 // the map. Affects drag interactions on the whole document. 2473 2474 // @function enableTextSelection() 2475 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2476 var disableTextSelection; 2477 var enableTextSelection; 2478 var _userSelect; 2479 if ('onselectstart' in document) { 2480 disableTextSelection = function () { 2481 on(window, 'selectstart', preventDefault); 2482 }; 2483 enableTextSelection = function () { 2484 off(window, 'selectstart', preventDefault); 2485 }; 2486 } else { 2487 var userSelectProperty = testProp( 2488 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2489 2490 disableTextSelection = function () { 2491 if (userSelectProperty) { 2492 var style = document.documentElement.style; 2493 _userSelect = style[userSelectProperty]; 2494 style[userSelectProperty] = 'none'; 2495 } 2496 }; 2497 enableTextSelection = function () { 2498 if (userSelectProperty) { 2499 document.documentElement.style[userSelectProperty] = _userSelect; 2500 _userSelect = undefined; 2501 } 2502 }; 2503 } 2504 2505 // @function disableImageDrag() 2506 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2507 // for `dragstart` DOM events, usually generated when the user drags an image. 2508 function disableImageDrag() { 2509 on(window, 'dragstart', preventDefault); 2510 } 2511 2512 // @function enableImageDrag() 2513 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2514 function enableImageDrag() { 2515 off(window, 'dragstart', preventDefault); 2516 } 2517 2518 var _outlineElement; 2519 var _outlineStyle; 2520 // @function preventOutline(el: HTMLElement) 2521 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2522 // of the element `el` invisible. Used internally by Leaflet to prevent 2523 // focusable elements from displaying an outline when the user performs a 2524 // drag interaction on them. 2525 function preventOutline(element) { 2526 while (element.tabIndex === -1) { 2527 element = element.parentNode; 2528 } 2529 if (!element.style) { return; } 2530 restoreOutline(); 2531 _outlineElement = element; 2532 _outlineStyle = element.style.outline; 2533 element.style.outline = 'none'; 2534 on(window, 'keydown', restoreOutline); 2535 } 2536 2537 // @function restoreOutline() 2538 // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2539 function restoreOutline() { 2540 if (!_outlineElement) { return; } 2541 _outlineElement.style.outline = _outlineStyle; 2542 _outlineElement = undefined; 2543 _outlineStyle = undefined; 2544 off(window, 'keydown', restoreOutline); 2545 } 2546 2547 // @function getSizedParentNode(el: HTMLElement): HTMLElement 2548 // Finds the closest parent node which size (width and height) is not null. 2549 function getSizedParentNode(element) { 2550 do { 2551 element = element.parentNode; 2552 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2553 return element; 2554 } 2555 2556 // @function getScale(el: HTMLElement): Object 2557 // Computes the CSS scale currently applied on the element. 2558 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2559 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2560 function getScale(element) { 2561 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2562 2563 return { 2564 x: rect.width / element.offsetWidth || 1, 2565 y: rect.height / element.offsetHeight || 1, 2566 boundingClientRect: rect 2567 }; 2568 } 2569 2570 2571 var DomUtil = (Object.freeze || Object)({ 2572 TRANSFORM: TRANSFORM, 2573 TRANSITION: TRANSITION, 2574 TRANSITION_END: TRANSITION_END, 2575 get: get, 2576 getStyle: getStyle, 2577 create: create$1, 2578 remove: remove, 2579 empty: empty, 2580 toFront: toFront, 2581 toBack: toBack, 2582 hasClass: hasClass, 2583 addClass: addClass, 2584 removeClass: removeClass, 2585 setClass: setClass, 2586 getClass: getClass, 2587 setOpacity: setOpacity, 2588 testProp: testProp, 2589 setTransform: setTransform, 2590 setPosition: setPosition, 2591 getPosition: getPosition, 2592 disableTextSelection: disableTextSelection, 2593 enableTextSelection: enableTextSelection, 2594 disableImageDrag: disableImageDrag, 2595 enableImageDrag: enableImageDrag, 2596 preventOutline: preventOutline, 2597 restoreOutline: restoreOutline, 2598 getSizedParentNode: getSizedParentNode, 2599 getScale: getScale 2600 }); 2601 2602 /* 2603 * @namespace DomEvent 2604 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2605 */ 2606 2607 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2608 2609 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2610 // Adds a listener function (`fn`) to a particular DOM event type of the 2611 // element `el`. You can optionally specify the context of the listener 2612 // (object the `this` keyword will point to). You can also pass several 2613 // space-separated types (e.g. `'click dblclick'`). 2614 2615 // @alternative 2616 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2617 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2618 function on(obj, types, fn, context) { 2619 2620 if (typeof types === 'object') { 2621 for (var type in types) { 2622 addOne(obj, type, types[type], fn); 2623 } 2624 } else { 2625 types = splitWords(types); 2626 2627 for (var i = 0, len = types.length; i < len; i++) { 2628 addOne(obj, types[i], fn, context); 2629 } 2630 } 2631 2632 return this; 2633 } 2634 2635 var eventsKey = '_leaflet_events'; 2636 2637 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2638 // Removes a previously added listener function. 2639 // Note that if you passed a custom context to on, you must pass the same 2640 // context to `off` in order to remove the listener. 2641 2642 // @alternative 2643 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2644 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2645 function off(obj, types, fn, context) { 2646 2647 if (typeof types === 'object') { 2648 for (var type in types) { 2649 removeOne(obj, type, types[type], fn); 2650 } 2651 } else if (types) { 2652 types = splitWords(types); 2653 2654 for (var i = 0, len = types.length; i < len; i++) { 2655 removeOne(obj, types[i], fn, context); 2656 } 2657 } else { 2658 for (var j in obj[eventsKey]) { 2659 removeOne(obj, j, obj[eventsKey][j]); 2660 } 2661 delete obj[eventsKey]; 2662 } 2663 2664 return this; 2665 } 2666 2667 function addOne(obj, type, fn, context) { 2668 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2669 2670 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 2671 2672 var handler = function (e) { 2673 return fn.call(context || obj, e || window.event); 2674 }; 2675 2676 var originalHandler = handler; 2677 2678 if (pointer && type.indexOf('touch') === 0) { 2679 // Needs DomEvent.Pointer.js 2680 addPointerListener(obj, type, handler, id); 2681 2682 } else if (touch && (type === 'dblclick') && addDoubleTapListener && 2683 !(pointer && chrome)) { 2684 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener 2685 // See #5180 2686 addDoubleTapListener(obj, handler, id); 2687 2688 } else if ('addEventListener' in obj) { 2689 2690 if (type === 'mousewheel') { 2691 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false); 2692 2693 } else if ((type === 'mouseenter') || (type === 'mouseleave')) { 2694 handler = function (e) { 2695 e = e || window.event; 2696 if (isExternalTarget(obj, e)) { 2697 originalHandler(e); 2698 } 2699 }; 2700 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); 2701 2702 } else { 2703 if (type === 'click' && android) { 2704 handler = function (e) { 2705 filterClick(e, originalHandler); 2706 }; 2707 } 2708 obj.addEventListener(type, handler, false); 2709 } 2710 2711 } else if ('attachEvent' in obj) { 2712 obj.attachEvent('on' + type, handler); 2713 } 2714 2715 obj[eventsKey] = obj[eventsKey] || {}; 2716 obj[eventsKey][id] = handler; 2717 } 2718 2719 function removeOne(obj, type, fn, context) { 2720 2721 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''), 2722 handler = obj[eventsKey] && obj[eventsKey][id]; 2723 2724 if (!handler) { return this; } 2725 2726 if (pointer && type.indexOf('touch') === 0) { 2727 removePointerListener(obj, type, id); 2728 2729 } else if (touch && (type === 'dblclick') && removeDoubleTapListener && 2730 !(pointer && chrome)) { 2731 removeDoubleTapListener(obj, id); 2732 2733 } else if ('removeEventListener' in obj) { 2734 2735 if (type === 'mousewheel') { 2736 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false); 2737 2738 } else { 2739 obj.removeEventListener( 2740 type === 'mouseenter' ? 'mouseover' : 2741 type === 'mouseleave' ? 'mouseout' : type, handler, false); 2742 } 2743 2744 } else if ('detachEvent' in obj) { 2745 obj.detachEvent('on' + type, handler); 2746 } 2747 2748 obj[eventsKey][id] = null; 2749 } 2750 2751 // @function stopPropagation(ev: DOMEvent): this 2752 // Stop the given event from propagation to parent elements. Used inside the listener functions: 2753 // ```js 2754 // L.DomEvent.on(div, 'click', function (ev) { 2755 // L.DomEvent.stopPropagation(ev); 2756 // }); 2757 // ``` 2758 function stopPropagation(e) { 2759 2760 if (e.stopPropagation) { 2761 e.stopPropagation(); 2762 } else if (e.originalEvent) { // In case of Leaflet event. 2763 e.originalEvent._stopped = true; 2764 } else { 2765 e.cancelBubble = true; 2766 } 2767 skipped(e); 2768 2769 return this; 2770 } 2771 2772 // @function disableScrollPropagation(el: HTMLElement): this 2773 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). 2774 function disableScrollPropagation(el) { 2775 addOne(el, 'mousewheel', stopPropagation); 2776 return this; 2777 } 2778 2779 // @function disableClickPropagation(el: HTMLElement): this 2780 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, 2781 // `'mousedown'` and `'touchstart'` events (plus browser variants). 2782 function disableClickPropagation(el) { 2783 on(el, 'mousedown touchstart dblclick', stopPropagation); 2784 addOne(el, 'click', fakeStop); 2785 return this; 2786 } 2787 2788 // @function preventDefault(ev: DOMEvent): this 2789 // Prevents the default action of the DOM Event `ev` from happening (such as 2790 // following a link in the href of the a element, or doing a POST request 2791 // with page reload when a `<form>` is submitted). 2792 // Use it inside listener functions. 2793 function preventDefault(e) { 2794 if (e.preventDefault) { 2795 e.preventDefault(); 2796 } else { 2797 e.returnValue = false; 2798 } 2799 return this; 2800 } 2801 2802 // @function stop(ev: DOMEvent): this 2803 // Does `stopPropagation` and `preventDefault` at the same time. 2804 function stop(e) { 2805 preventDefault(e); 2806 stopPropagation(e); 2807 return this; 2808 } 2809 2810 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 2811 // Gets normalized mouse position from a DOM event relative to the 2812 // `container` (border excluded) or to the whole page if not specified. 2813 function getMousePosition(e, container) { 2814 if (!container) { 2815 return new Point(e.clientX, e.clientY); 2816 } 2817 2818 var scale = getScale(container), 2819 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 2820 2821 return new Point( 2822 // offset.left/top values are in page scale (like clientX/Y), 2823 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 2824 (e.clientX - offset.left) / scale.x - container.clientLeft, 2825 (e.clientY - offset.top) / scale.y - container.clientTop 2826 ); 2827 } 2828 2829 // Chrome on Win scrolls double the pixels as in other platforms (see #4538), 2830 // and Firefox scrolls device pixels, not CSS pixels 2831 var wheelPxFactor = 2832 (win && chrome) ? 2 * window.devicePixelRatio : 2833 gecko ? window.devicePixelRatio : 1; 2834 2835 // @function getWheelDelta(ev: DOMEvent): Number 2836 // Gets normalized wheel delta from a mousewheel DOM event, in vertical 2837 // pixels scrolled (negative if scrolling down). 2838 // Events from pointing devices without precise scrolling are mapped to 2839 // a best guess of 60 pixels. 2840 function getWheelDelta(e) { 2841 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 2842 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 2843 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 2844 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 2845 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 2846 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 2847 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 2848 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 2849 0; 2850 } 2851 2852 var skipEvents = {}; 2853 2854 function fakeStop(e) { 2855 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) 2856 skipEvents[e.type] = true; 2857 } 2858 2859 function skipped(e) { 2860 var events = skipEvents[e.type]; 2861 // reset when checking, as it's only used in map container and propagates outside of the map 2862 skipEvents[e.type] = false; 2863 return events; 2864 } 2865 2866 // check if element really left/entered the event target (for mouseenter/mouseleave) 2867 function isExternalTarget(el, e) { 2868 2869 var related = e.relatedTarget; 2870 2871 if (!related) { return true; } 2872 2873 try { 2874 while (related && (related !== el)) { 2875 related = related.parentNode; 2876 } 2877 } catch (err) { 2878 return false; 2879 } 2880 return (related !== el); 2881 } 2882 2883 var lastClick; 2884 2885 // this is a horrible workaround for a bug in Android where a single touch triggers two click events 2886 function filterClick(e, handler) { 2887 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), 2888 elapsed = lastClick && (timeStamp - lastClick); 2889 2890 // are they closer together than 500ms yet more than 100ms? 2891 // Android typically triggers them ~300ms apart while multiple listeners 2892 // on the same event should be triggered far faster; 2893 // or check if click is simulated on the element, and if it is, reject any non-simulated events 2894 2895 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { 2896 stop(e); 2897 return; 2898 } 2899 lastClick = timeStamp; 2900 2901 handler(e); 2902 } 2903 2904 2905 2906 2907 var DomEvent = (Object.freeze || Object)({ 2908 on: on, 2909 off: off, 2910 stopPropagation: stopPropagation, 2911 disableScrollPropagation: disableScrollPropagation, 2912 disableClickPropagation: disableClickPropagation, 2913 preventDefault: preventDefault, 2914 stop: stop, 2915 getMousePosition: getMousePosition, 2916 getWheelDelta: getWheelDelta, 2917 fakeStop: fakeStop, 2918 skipped: skipped, 2919 isExternalTarget: isExternalTarget, 2920 addListener: on, 2921 removeListener: off 2922 }); 2923 2924 /* 2925 * @class PosAnimation 2926 * @aka L.PosAnimation 2927 * @inherits Evented 2928 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 2929 * 2930 * @example 2931 * ```js 2932 * var fx = new L.PosAnimation(); 2933 * fx.run(el, [300, 500], 0.5); 2934 * ``` 2935 * 2936 * @constructor L.PosAnimation() 2937 * Creates a `PosAnimation` object. 2938 * 2939 */ 2940 2941 var PosAnimation = Evented.extend({ 2942 2943 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 2944 // Run an animation of a given element to a new position, optionally setting 2945 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 2946 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), 2947 // `0.5` by default). 2948 run: function (el, newPos, duration, easeLinearity) { 2949 this.stop(); 2950 2951 this._el = el; 2952 this._inProgress = true; 2953 this._duration = duration || 0.25; 2954 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 2955 2956 this._startPos = getPosition(el); 2957 this._offset = newPos.subtract(this._startPos); 2958 this._startTime = +new Date(); 2959 2960 // @event start: Event 2961 // Fired when the animation starts 2962 this.fire('start'); 2963 2964 this._animate(); 2965 }, 2966 2967 // @method stop() 2968 // Stops the animation (if currently running). 2969 stop: function () { 2970 if (!this._inProgress) { return; } 2971 2972 this._step(true); 2973 this._complete(); 2974 }, 2975 2976 _animate: function () { 2977 // animation loop 2978 this._animId = requestAnimFrame(this._animate, this); 2979 this._step(); 2980 }, 2981 2982 _step: function (round) { 2983 var elapsed = (+new Date()) - this._startTime, 2984 duration = this._duration * 1000; 2985 2986 if (elapsed < duration) { 2987 this._runFrame(this._easeOut(elapsed / duration), round); 2988 } else { 2989 this._runFrame(1); 2990 this._complete(); 2991 } 2992 }, 2993 2994 _runFrame: function (progress, round) { 2995 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 2996 if (round) { 2997 pos._round(); 2998 } 2999 setPosition(this._el, pos); 3000 3001 // @event step: Event 3002 // Fired continuously during the animation. 3003 this.fire('step'); 3004 }, 3005 3006 _complete: function () { 3007 cancelAnimFrame(this._animId); 3008 3009 this._inProgress = false; 3010 // @event end: Event 3011 // Fired when the animation ends. 3012 this.fire('end'); 3013 }, 3014 3015 _easeOut: function (t) { 3016 return 1 - Math.pow(1 - t, this._easeOutPower); 3017 } 3018 }); 3019 3020 /* 3021 * @class Map 3022 * @aka L.Map 3023 * @inherits Evented 3024 * 3025 * The central class of the API — it is used to create a map on a page and manipulate it. 3026 * 3027 * @example 3028 * 3029 * ```js 3030 * // initialize the map on the "map" div with a given center and zoom 3031 * var map = L.map('map', { 3032 * center: [51.505, -0.09], 3033 * zoom: 13 3034 * }); 3035 * ``` 3036 * 3037 */ 3038 3039 var Map = Evented.extend({ 3040 3041 options: { 3042 // @section Map State Options 3043 // @option crs: CRS = L.CRS.EPSG3857 3044 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 3045 // sure what it means. 3046 crs: EPSG3857, 3047 3048 // @option center: LatLng = undefined 3049 // Initial geographic center of the map 3050 center: undefined, 3051 3052 // @option zoom: Number = undefined 3053 // Initial map zoom level 3054 zoom: undefined, 3055 3056 // @option minZoom: Number = * 3057 // Minimum zoom level of the map. 3058 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3059 // the lowest of their `minZoom` options will be used instead. 3060 minZoom: undefined, 3061 3062 // @option maxZoom: Number = * 3063 // Maximum zoom level of the map. 3064 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3065 // the highest of their `maxZoom` options will be used instead. 3066 maxZoom: undefined, 3067 3068 // @option layers: Layer[] = [] 3069 // Array of layers that will be added to the map initially 3070 layers: [], 3071 3072 // @option maxBounds: LatLngBounds = null 3073 // When this option is set, the map restricts the view to the given 3074 // geographical bounds, bouncing the user back if the user tries to pan 3075 // outside the view. To set the restriction dynamically, use 3076 // [`setMaxBounds`](#map-setmaxbounds) method. 3077 maxBounds: undefined, 3078 3079 // @option renderer: Renderer = * 3080 // The default method for drawing vector layers on the map. `L.SVG` 3081 // or `L.Canvas` by default depending on browser support. 3082 renderer: undefined, 3083 3084 3085 // @section Animation Options 3086 // @option zoomAnimation: Boolean = true 3087 // Whether the map zoom animation is enabled. By default it's enabled 3088 // in all browsers that support CSS3 Transitions except Android. 3089 zoomAnimation: true, 3090 3091 // @option zoomAnimationThreshold: Number = 4 3092 // Won't animate zoom if the zoom difference exceeds this value. 3093 zoomAnimationThreshold: 4, 3094 3095 // @option fadeAnimation: Boolean = true 3096 // Whether the tile fade animation is enabled. By default it's enabled 3097 // in all browsers that support CSS3 Transitions except Android. 3098 fadeAnimation: true, 3099 3100 // @option markerZoomAnimation: Boolean = true 3101 // Whether markers animate their zoom with the zoom animation, if disabled 3102 // they will disappear for the length of the animation. By default it's 3103 // enabled in all browsers that support CSS3 Transitions except Android. 3104 markerZoomAnimation: true, 3105 3106 // @option transform3DLimit: Number = 2^23 3107 // Defines the maximum size of a CSS translation transform. The default 3108 // value should not be changed unless a web browser positions layers in 3109 // the wrong place after doing a large `panBy`. 3110 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3111 3112 // @section Interaction Options 3113 // @option zoomSnap: Number = 1 3114 // Forces the map's zoom level to always be a multiple of this, particularly 3115 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3116 // By default, the zoom level snaps to the nearest integer; lower values 3117 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3118 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3119 zoomSnap: 1, 3120 3121 // @option zoomDelta: Number = 1 3122 // Controls how much the map's zoom level will change after a 3123 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3124 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3125 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3126 zoomDelta: 1, 3127 3128 // @option trackResize: Boolean = true 3129 // Whether the map automatically handles browser window resize to update itself. 3130 trackResize: true 3131 }, 3132 3133 initialize: function (id, options) { // (HTMLElement or String, Object) 3134 options = setOptions(this, options); 3135 3136 // Make sure to assign internal flags at the beginning, 3137 // to avoid inconsistent state in some edge cases. 3138 this._handlers = []; 3139 this._layers = {}; 3140 this._zoomBoundLayers = {}; 3141 this._sizeChanged = true; 3142 3143 this._initContainer(id); 3144 this._initLayout(); 3145 3146 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3147 this._onResize = bind(this._onResize, this); 3148 3149 this._initEvents(); 3150 3151 if (options.maxBounds) { 3152 this.setMaxBounds(options.maxBounds); 3153 } 3154 3155 if (options.zoom !== undefined) { 3156 this._zoom = this._limitZoom(options.zoom); 3157 } 3158 3159 if (options.center && options.zoom !== undefined) { 3160 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3161 } 3162 3163 this.callInitHooks(); 3164 3165 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3166 this._zoomAnimated = TRANSITION && any3d && !mobileOpera && 3167 this.options.zoomAnimation; 3168 3169 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3170 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3171 if (this._zoomAnimated) { 3172 this._createAnimProxy(); 3173 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3174 } 3175 3176 this._addLayers(this.options.layers); 3177 }, 3178 3179 3180 // @section Methods for modifying map state 3181 3182 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3183 // Sets the view of the map (geographical center and zoom) with the given 3184 // animation options. 3185 setView: function (center, zoom, options) { 3186 3187 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3188 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3189 options = options || {}; 3190 3191 this._stop(); 3192 3193 if (this._loaded && !options.reset && options !== true) { 3194 3195 if (options.animate !== undefined) { 3196 options.zoom = extend({animate: options.animate}, options.zoom); 3197 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3198 } 3199 3200 // try animating pan or zoom 3201 var moved = (this._zoom !== zoom) ? 3202 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3203 this._tryAnimatedPan(center, options.pan); 3204 3205 if (moved) { 3206 // prevent resize handler call, the view will refresh after animation anyway 3207 clearTimeout(this._sizeTimer); 3208 return this; 3209 } 3210 } 3211 3212 // animation didn't start, just reset the map view 3213 this._resetView(center, zoom); 3214 3215 return this; 3216 }, 3217 3218 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3219 // Sets the zoom of the map. 3220 setZoom: function (zoom, options) { 3221 if (!this._loaded) { 3222 this._zoom = zoom; 3223 return this; 3224 } 3225 return this.setView(this.getCenter(), zoom, {zoom: options}); 3226 }, 3227 3228 // @method zoomIn(delta?: Number, options?: Zoom options): this 3229 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3230 zoomIn: function (delta, options) { 3231 delta = delta || (any3d ? this.options.zoomDelta : 1); 3232 return this.setZoom(this._zoom + delta, options); 3233 }, 3234 3235 // @method zoomOut(delta?: Number, options?: Zoom options): this 3236 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3237 zoomOut: function (delta, options) { 3238 delta = delta || (any3d ? this.options.zoomDelta : 1); 3239 return this.setZoom(this._zoom - delta, options); 3240 }, 3241 3242 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3243 // Zooms the map while keeping a specified geographical point on the map 3244 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3245 // @alternative 3246 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3247 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3248 setZoomAround: function (latlng, zoom, options) { 3249 var scale = this.getZoomScale(zoom), 3250 viewHalf = this.getSize().divideBy(2), 3251 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3252 3253 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3254 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3255 3256 return this.setView(newCenter, zoom, {zoom: options}); 3257 }, 3258 3259 _getBoundsCenterZoom: function (bounds, options) { 3260 3261 options = options || {}; 3262 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3263 3264 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3265 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3266 3267 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3268 3269 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3270 3271 if (zoom === Infinity) { 3272 return { 3273 center: bounds.getCenter(), 3274 zoom: zoom 3275 }; 3276 } 3277 3278 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3279 3280 swPoint = this.project(bounds.getSouthWest(), zoom), 3281 nePoint = this.project(bounds.getNorthEast(), zoom), 3282 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3283 3284 return { 3285 center: center, 3286 zoom: zoom 3287 }; 3288 }, 3289 3290 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3291 // Sets a map view that contains the given geographical bounds with the 3292 // maximum zoom level possible. 3293 fitBounds: function (bounds, options) { 3294 3295 bounds = toLatLngBounds(bounds); 3296 3297 if (!bounds.isValid()) { 3298 throw new Error('Bounds are not valid.'); 3299 } 3300 3301 var target = this._getBoundsCenterZoom(bounds, options); 3302 return this.setView(target.center, target.zoom, options); 3303 }, 3304 3305 // @method fitWorld(options?: fitBounds options): this 3306 // Sets a map view that mostly contains the whole world with the maximum 3307 // zoom level possible. 3308 fitWorld: function (options) { 3309 return this.fitBounds([[-90, -180], [90, 180]], options); 3310 }, 3311 3312 // @method panTo(latlng: LatLng, options?: Pan options): this 3313 // Pans the map to a given center. 3314 panTo: function (center, options) { // (LatLng) 3315 return this.setView(center, this._zoom, {pan: options}); 3316 }, 3317 3318 // @method panBy(offset: Point, options?: Pan options): this 3319 // Pans the map by a given number of pixels (animated). 3320 panBy: function (offset, options) { 3321 offset = toPoint(offset).round(); 3322 options = options || {}; 3323 3324 if (!offset.x && !offset.y) { 3325 return this.fire('moveend'); 3326 } 3327 // If we pan too far, Chrome gets issues with tiles 3328 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3329 if (options.animate !== true && !this.getSize().contains(offset)) { 3330 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3331 return this; 3332 } 3333 3334 if (!this._panAnim) { 3335 this._panAnim = new PosAnimation(); 3336 3337 this._panAnim.on({ 3338 'step': this._onPanTransitionStep, 3339 'end': this._onPanTransitionEnd 3340 }, this); 3341 } 3342 3343 // don't fire movestart if animating inertia 3344 if (!options.noMoveStart) { 3345 this.fire('movestart'); 3346 } 3347 3348 // animate pan unless animate: false specified 3349 if (options.animate !== false) { 3350 addClass(this._mapPane, 'leaflet-pan-anim'); 3351 3352 var newPos = this._getMapPanePos().subtract(offset).round(); 3353 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3354 } else { 3355 this._rawPanBy(offset); 3356 this.fire('move').fire('moveend'); 3357 } 3358 3359 return this; 3360 }, 3361 3362 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3363 // Sets the view of the map (geographical center and zoom) performing a smooth 3364 // pan-zoom animation. 3365 flyTo: function (targetCenter, targetZoom, options) { 3366 3367 options = options || {}; 3368 if (options.animate === false || !any3d) { 3369 return this.setView(targetCenter, targetZoom, options); 3370 } 3371 3372 this._stop(); 3373 3374 var from = this.project(this.getCenter()), 3375 to = this.project(targetCenter), 3376 size = this.getSize(), 3377 startZoom = this._zoom; 3378 3379 targetCenter = toLatLng(targetCenter); 3380 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3381 3382 var w0 = Math.max(size.x, size.y), 3383 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3384 u1 = (to.distanceTo(from)) || 1, 3385 rho = 1.42, 3386 rho2 = rho * rho; 3387 3388 function r(i) { 3389 var s1 = i ? -1 : 1, 3390 s2 = i ? w1 : w0, 3391 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3392 b1 = 2 * s2 * rho2 * u1, 3393 b = t1 / b1, 3394 sq = Math.sqrt(b * b + 1) - b; 3395 3396 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3397 // thus triggering an infinite loop in flyTo 3398 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3399 3400 return log; 3401 } 3402 3403 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3404 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3405 function tanh(n) { return sinh(n) / cosh(n); } 3406 3407 var r0 = r(0); 3408 3409 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3410 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3411 3412 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3413 3414 var start = Date.now(), 3415 S = (r(1) - r0) / rho, 3416 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3417 3418 function frame() { 3419 var t = (Date.now() - start) / duration, 3420 s = easeOut(t) * S; 3421 3422 if (t <= 1) { 3423 this._flyToFrame = requestAnimFrame(frame, this); 3424 3425 this._move( 3426 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3427 this.getScaleZoom(w0 / w(s), startZoom), 3428 {flyTo: true}); 3429 3430 } else { 3431 this 3432 ._move(targetCenter, targetZoom) 3433 ._moveEnd(true); 3434 } 3435 } 3436 3437 this._moveStart(true, options.noMoveStart); 3438 3439 frame.call(this); 3440 return this; 3441 }, 3442 3443 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3444 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3445 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3446 flyToBounds: function (bounds, options) { 3447 var target = this._getBoundsCenterZoom(bounds, options); 3448 return this.flyTo(target.center, target.zoom, options); 3449 }, 3450 3451 // @method setMaxBounds(bounds: Bounds): this 3452 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3453 setMaxBounds: function (bounds) { 3454 bounds = toLatLngBounds(bounds); 3455 3456 if (!bounds.isValid()) { 3457 this.options.maxBounds = null; 3458 return this.off('moveend', this._panInsideMaxBounds); 3459 } else if (this.options.maxBounds) { 3460 this.off('moveend', this._panInsideMaxBounds); 3461 } 3462 3463 this.options.maxBounds = bounds; 3464 3465 if (this._loaded) { 3466 this._panInsideMaxBounds(); 3467 } 3468 3469 return this.on('moveend', this._panInsideMaxBounds); 3470 }, 3471 3472 // @method setMinZoom(zoom: Number): this 3473 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3474 setMinZoom: function (zoom) { 3475 var oldZoom = this.options.minZoom; 3476 this.options.minZoom = zoom; 3477 3478 if (this._loaded && oldZoom !== zoom) { 3479 this.fire('zoomlevelschange'); 3480 3481 if (this.getZoom() < this.options.minZoom) { 3482 return this.setZoom(zoom); 3483 } 3484 } 3485 3486 return this; 3487 }, 3488 3489 // @method setMaxZoom(zoom: Number): this 3490 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3491 setMaxZoom: function (zoom) { 3492 var oldZoom = this.options.maxZoom; 3493 this.options.maxZoom = zoom; 3494 3495 if (this._loaded && oldZoom !== zoom) { 3496 this.fire('zoomlevelschange'); 3497 3498 if (this.getZoom() > this.options.maxZoom) { 3499 return this.setZoom(zoom); 3500 } 3501 } 3502 3503 return this; 3504 }, 3505 3506 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3507 // 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. 3508 panInsideBounds: function (bounds, options) { 3509 this._enforcingBounds = true; 3510 var center = this.getCenter(), 3511 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3512 3513 if (!center.equals(newCenter)) { 3514 this.panTo(newCenter, options); 3515 } 3516 3517 this._enforcingBounds = false; 3518 return this; 3519 }, 3520 3521 // @method panInside(latlng: LatLng, options?: options): this 3522 // Pans the map the minimum amount to make the `latlng` visible. Use 3523 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit 3524 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds). 3525 // If `latlng` is already within the (optionally padded) display bounds, 3526 // the map will not be panned. 3527 panInside: function (latlng, options) { 3528 options = options || {}; 3529 3530 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3531 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3532 center = this.getCenter(), 3533 pixelCenter = this.project(center), 3534 pixelPoint = this.project(latlng), 3535 pixelBounds = this.getPixelBounds(), 3536 halfPixelBounds = pixelBounds.getSize().divideBy(2), 3537 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]); 3538 3539 if (!paddedBounds.contains(pixelPoint)) { 3540 this._enforcingBounds = true; 3541 var diff = pixelCenter.subtract(pixelPoint), 3542 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y); 3543 3544 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) { 3545 newCenter.x = pixelCenter.x - diff.x; 3546 if (diff.x > 0) { 3547 newCenter.x += halfPixelBounds.x - paddingTL.x; 3548 } else { 3549 newCenter.x -= halfPixelBounds.x - paddingBR.x; 3550 } 3551 } 3552 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) { 3553 newCenter.y = pixelCenter.y - diff.y; 3554 if (diff.y > 0) { 3555 newCenter.y += halfPixelBounds.y - paddingTL.y; 3556 } else { 3557 newCenter.y -= halfPixelBounds.y - paddingBR.y; 3558 } 3559 } 3560 this.panTo(this.unproject(newCenter), options); 3561 this._enforcingBounds = false; 3562 } 3563 return this; 3564 }, 3565 3566 // @method invalidateSize(options: Zoom/pan options): this 3567 // Checks if the map container size changed and updates the map if so — 3568 // call it after you've changed the map size dynamically, also animating 3569 // pan by default. If `options.pan` is `false`, panning will not occur. 3570 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3571 // that it doesn't happen often even if the method is called many 3572 // times in a row. 3573 3574 // @alternative 3575 // @method invalidateSize(animate: Boolean): this 3576 // Checks if the map container size changed and updates the map if so — 3577 // call it after you've changed the map size dynamically, also animating 3578 // pan by default. 3579 invalidateSize: function (options) { 3580 if (!this._loaded) { return this; } 3581 3582 options = extend({ 3583 animate: false, 3584 pan: true 3585 }, options === true ? {animate: true} : options); 3586 3587 var oldSize = this.getSize(); 3588 this._sizeChanged = true; 3589 this._lastCenter = null; 3590 3591 var newSize = this.getSize(), 3592 oldCenter = oldSize.divideBy(2).round(), 3593 newCenter = newSize.divideBy(2).round(), 3594 offset = oldCenter.subtract(newCenter); 3595 3596 if (!offset.x && !offset.y) { return this; } 3597 3598 if (options.animate && options.pan) { 3599 this.panBy(offset); 3600 3601 } else { 3602 if (options.pan) { 3603 this._rawPanBy(offset); 3604 } 3605 3606 this.fire('move'); 3607 3608 if (options.debounceMoveend) { 3609 clearTimeout(this._sizeTimer); 3610 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3611 } else { 3612 this.fire('moveend'); 3613 } 3614 } 3615 3616 // @section Map state change events 3617 // @event resize: ResizeEvent 3618 // Fired when the map is resized. 3619 return this.fire('resize', { 3620 oldSize: oldSize, 3621 newSize: newSize 3622 }); 3623 }, 3624 3625 // @section Methods for modifying map state 3626 // @method stop(): this 3627 // Stops the currently running `panTo` or `flyTo` animation, if any. 3628 stop: function () { 3629 this.setZoom(this._limitZoom(this._zoom)); 3630 if (!this.options.zoomSnap) { 3631 this.fire('viewreset'); 3632 } 3633 return this._stop(); 3634 }, 3635 3636 // @section Geolocation methods 3637 // @method locate(options?: Locate options): this 3638 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3639 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3640 // and optionally sets the map view to the user's location with respect to 3641 // detection accuracy (or to the world view if geolocation failed). 3642 // Note that, if your page doesn't use HTTPS, this method will fail in 3643 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3644 // See `Locate options` for more details. 3645 locate: function (options) { 3646 3647 options = this._locateOptions = extend({ 3648 timeout: 10000, 3649 watch: false 3650 // setView: false 3651 // maxZoom: <Number> 3652 // maximumAge: 0 3653 // enableHighAccuracy: false 3654 }, options); 3655 3656 if (!('geolocation' in navigator)) { 3657 this._handleGeolocationError({ 3658 code: 0, 3659 message: 'Geolocation not supported.' 3660 }); 3661 return this; 3662 } 3663 3664 var onResponse = bind(this._handleGeolocationResponse, this), 3665 onError = bind(this._handleGeolocationError, this); 3666 3667 if (options.watch) { 3668 this._locationWatchId = 3669 navigator.geolocation.watchPosition(onResponse, onError, options); 3670 } else { 3671 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 3672 } 3673 return this; 3674 }, 3675 3676 // @method stopLocate(): this 3677 // Stops watching location previously initiated by `map.locate({watch: true})` 3678 // and aborts resetting the map view if map.locate was called with 3679 // `{setView: true}`. 3680 stopLocate: function () { 3681 if (navigator.geolocation && navigator.geolocation.clearWatch) { 3682 navigator.geolocation.clearWatch(this._locationWatchId); 3683 } 3684 if (this._locateOptions) { 3685 this._locateOptions.setView = false; 3686 } 3687 return this; 3688 }, 3689 3690 _handleGeolocationError: function (error) { 3691 var c = error.code, 3692 message = error.message || 3693 (c === 1 ? 'permission denied' : 3694 (c === 2 ? 'position unavailable' : 'timeout')); 3695 3696 if (this._locateOptions.setView && !this._loaded) { 3697 this.fitWorld(); 3698 } 3699 3700 // @section Location events 3701 // @event locationerror: ErrorEvent 3702 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 3703 this.fire('locationerror', { 3704 code: c, 3705 message: 'Geolocation error: ' + message + '.' 3706 }); 3707 }, 3708 3709 _handleGeolocationResponse: function (pos) { 3710 var lat = pos.coords.latitude, 3711 lng = pos.coords.longitude, 3712 latlng = new LatLng(lat, lng), 3713 bounds = latlng.toBounds(pos.coords.accuracy * 2), 3714 options = this._locateOptions; 3715 3716 if (options.setView) { 3717 var zoom = this.getBoundsZoom(bounds); 3718 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 3719 } 3720 3721 var data = { 3722 latlng: latlng, 3723 bounds: bounds, 3724 timestamp: pos.timestamp 3725 }; 3726 3727 for (var i in pos.coords) { 3728 if (typeof pos.coords[i] === 'number') { 3729 data[i] = pos.coords[i]; 3730 } 3731 } 3732 3733 // @event locationfound: LocationEvent 3734 // Fired when geolocation (using the [`locate`](#map-locate) method) 3735 // went successfully. 3736 this.fire('locationfound', data); 3737 }, 3738 3739 // TODO Appropriate docs section? 3740 // @section Other Methods 3741 // @method addHandler(name: String, HandlerClass: Function): this 3742 // Adds a new `Handler` to the map, given its name and constructor function. 3743 addHandler: function (name, HandlerClass) { 3744 if (!HandlerClass) { return this; } 3745 3746 var handler = this[name] = new HandlerClass(this); 3747 3748 this._handlers.push(handler); 3749 3750 if (this.options[name]) { 3751 handler.enable(); 3752 } 3753 3754 return this; 3755 }, 3756 3757 // @method remove(): this 3758 // Destroys the map and clears all related event listeners. 3759 remove: function () { 3760 3761 this._initEvents(true); 3762 3763 if (this._containerId !== this._container._leaflet_id) { 3764 throw new Error('Map container is being reused by another instance'); 3765 } 3766 3767 try { 3768 // throws error in IE6-8 3769 delete this._container._leaflet_id; 3770 delete this._containerId; 3771 } catch (e) { 3772 /*eslint-disable */ 3773 this._container._leaflet_id = undefined; 3774 /* eslint-enable */ 3775 this._containerId = undefined; 3776 } 3777 3778 if (this._locationWatchId !== undefined) { 3779 this.stopLocate(); 3780 } 3781 3782 this._stop(); 3783 3784 remove(this._mapPane); 3785 3786 if (this._clearControlPos) { 3787 this._clearControlPos(); 3788 } 3789 if (this._resizeRequest) { 3790 cancelAnimFrame(this._resizeRequest); 3791 this._resizeRequest = null; 3792 } 3793 3794 this._clearHandlers(); 3795 3796 if (this._loaded) { 3797 // @section Map state change events 3798 // @event unload: Event 3799 // Fired when the map is destroyed with [remove](#map-remove) method. 3800 this.fire('unload'); 3801 } 3802 3803 var i; 3804 for (i in this._layers) { 3805 this._layers[i].remove(); 3806 } 3807 for (i in this._panes) { 3808 remove(this._panes[i]); 3809 } 3810 3811 this._layers = []; 3812 this._panes = []; 3813 delete this._mapPane; 3814 delete this._renderer; 3815 3816 return this; 3817 }, 3818 3819 // @section Other Methods 3820 // @method createPane(name: String, container?: HTMLElement): HTMLElement 3821 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 3822 // then returns it. The pane is created as a child of `container`, or 3823 // as a child of the main map pane if not set. 3824 createPane: function (name, container) { 3825 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3826 pane = create$1('div', className, container || this._mapPane); 3827 3828 if (name) { 3829 this._panes[name] = pane; 3830 } 3831 return pane; 3832 }, 3833 3834 // @section Methods for Getting Map State 3835 3836 // @method getCenter(): LatLng 3837 // Returns the geographical center of the map view 3838 getCenter: function () { 3839 this._checkIfLoaded(); 3840 3841 if (this._lastCenter && !this._moved()) { 3842 return this._lastCenter; 3843 } 3844 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3845 }, 3846 3847 // @method getZoom(): Number 3848 // Returns the current zoom level of the map view 3849 getZoom: function () { 3850 return this._zoom; 3851 }, 3852 3853 // @method getBounds(): LatLngBounds 3854 // Returns the geographical bounds visible in the current map view 3855 getBounds: function () { 3856 var bounds = this.getPixelBounds(), 3857 sw = this.unproject(bounds.getBottomLeft()), 3858 ne = this.unproject(bounds.getTopRight()); 3859 3860 return new LatLngBounds(sw, ne); 3861 }, 3862 3863 // @method getMinZoom(): Number 3864 // 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. 3865 getMinZoom: function () { 3866 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3867 }, 3868 3869 // @method getMaxZoom(): Number 3870 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3871 getMaxZoom: function () { 3872 return this.options.maxZoom === undefined ? 3873 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3874 this.options.maxZoom; 3875 }, 3876 3877 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 3878 // Returns the maximum zoom level on which the given bounds fit to the map 3879 // view in its entirety. If `inside` (optional) is set to `true`, the method 3880 // instead returns the minimum zoom level on which the map view fits into 3881 // the given bounds in its entirety. 3882 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3883 bounds = toLatLngBounds(bounds); 3884 padding = toPoint(padding || [0, 0]); 3885 3886 var zoom = this.getZoom() || 0, 3887 min = this.getMinZoom(), 3888 max = this.getMaxZoom(), 3889 nw = bounds.getNorthWest(), 3890 se = bounds.getSouthEast(), 3891 size = this.getSize().subtract(padding), 3892 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3893 snap = any3d ? this.options.zoomSnap : 1, 3894 scalex = size.x / boundsSize.x, 3895 scaley = size.y / boundsSize.y, 3896 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 3897 3898 zoom = this.getScaleZoom(scale, zoom); 3899 3900 if (snap) { 3901 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3902 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3903 } 3904 3905 return Math.max(min, Math.min(max, zoom)); 3906 }, 3907 3908 // @method getSize(): Point 3909 // Returns the current size of the map container (in pixels). 3910 getSize: function () { 3911 if (!this._size || this._sizeChanged) { 3912 this._size = new Point( 3913 this._container.clientWidth || 0, 3914 this._container.clientHeight || 0); 3915 3916 this._sizeChanged = false; 3917 } 3918 return this._size.clone(); 3919 }, 3920 3921 // @method getPixelBounds(): Bounds 3922 // Returns the bounds of the current map view in projected pixel 3923 // coordinates (sometimes useful in layer and overlay implementations). 3924 getPixelBounds: function (center, zoom) { 3925 var topLeftPoint = this._getTopLeftPoint(center, zoom); 3926 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 3927 }, 3928 3929 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 3930 // the map pane? "left point of the map layer" can be confusing, specially 3931 // since there can be negative offsets. 3932 // @method getPixelOrigin(): Point 3933 // Returns the projected pixel coordinates of the top left point of 3934 // the map layer (useful in custom layer and overlay implementations). 3935 getPixelOrigin: function () { 3936 this._checkIfLoaded(); 3937 return this._pixelOrigin; 3938 }, 3939 3940 // @method getPixelWorldBounds(zoom?: Number): Bounds 3941 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 3942 // If `zoom` is omitted, the map's current zoom level is used. 3943 getPixelWorldBounds: function (zoom) { 3944 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 3945 }, 3946 3947 // @section Other Methods 3948 3949 // @method getPane(pane: String|HTMLElement): HTMLElement 3950 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 3951 getPane: function (pane) { 3952 return typeof pane === 'string' ? this._panes[pane] : pane; 3953 }, 3954 3955 // @method getPanes(): Object 3956 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 3957 // the panes as values. 3958 getPanes: function () { 3959 return this._panes; 3960 }, 3961 3962 // @method getContainer: HTMLElement 3963 // Returns the HTML element that contains the map. 3964 getContainer: function () { 3965 return this._container; 3966 }, 3967 3968 3969 // @section Conversion Methods 3970 3971 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 3972 // Returns the scale factor to be applied to a map transition from zoom level 3973 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 3974 getZoomScale: function (toZoom, fromZoom) { 3975 // TODO replace with universal implementation after refactoring projections 3976 var crs = this.options.crs; 3977 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3978 return crs.scale(toZoom) / crs.scale(fromZoom); 3979 }, 3980 3981 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 3982 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 3983 // level and everything is scaled by a factor of `scale`. Inverse of 3984 // [`getZoomScale`](#map-getZoomScale). 3985 getScaleZoom: function (scale, fromZoom) { 3986 var crs = this.options.crs; 3987 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3988 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 3989 return isNaN(zoom) ? Infinity : zoom; 3990 }, 3991 3992 // @method project(latlng: LatLng, zoom: Number): Point 3993 // Projects a geographical coordinate `LatLng` according to the projection 3994 // of the map's CRS, then scales it according to `zoom` and the CRS's 3995 // `Transformation`. The result is pixel coordinate relative to 3996 // the CRS origin. 3997 project: function (latlng, zoom) { 3998 zoom = zoom === undefined ? this._zoom : zoom; 3999 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 4000 }, 4001 4002 // @method unproject(point: Point, zoom: Number): LatLng 4003 // Inverse of [`project`](#map-project). 4004 unproject: function (point, zoom) { 4005 zoom = zoom === undefined ? this._zoom : zoom; 4006 return this.options.crs.pointToLatLng(toPoint(point), zoom); 4007 }, 4008 4009 // @method layerPointToLatLng(point: Point): LatLng 4010 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4011 // returns the corresponding geographical coordinate (for the current zoom level). 4012 layerPointToLatLng: function (point) { 4013 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 4014 return this.unproject(projectedPoint); 4015 }, 4016 4017 // @method latLngToLayerPoint(latlng: LatLng): Point 4018 // Given a geographical coordinate, returns the corresponding pixel coordinate 4019 // relative to the [origin pixel](#map-getpixelorigin). 4020 latLngToLayerPoint: function (latlng) { 4021 var projectedPoint = this.project(toLatLng(latlng))._round(); 4022 return projectedPoint._subtract(this.getPixelOrigin()); 4023 }, 4024 4025 // @method wrapLatLng(latlng: LatLng): LatLng 4026 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 4027 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 4028 // CRS's bounds. 4029 // By default this means longitude is wrapped around the dateline so its 4030 // value is between -180 and +180 degrees. 4031 wrapLatLng: function (latlng) { 4032 return this.options.crs.wrapLatLng(toLatLng(latlng)); 4033 }, 4034 4035 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 4036 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 4037 // its center is within the CRS's bounds. 4038 // By default this means the center longitude is wrapped around the dateline so its 4039 // value is between -180 and +180 degrees, and the majority of the bounds 4040 // overlaps the CRS's bounds. 4041 wrapLatLngBounds: function (latlng) { 4042 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 4043 }, 4044 4045 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 4046 // Returns the distance between two geographical coordinates according to 4047 // the map's CRS. By default this measures distance in meters. 4048 distance: function (latlng1, latlng2) { 4049 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 4050 }, 4051 4052 // @method containerPointToLayerPoint(point: Point): Point 4053 // Given a pixel coordinate relative to the map container, returns the corresponding 4054 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 4055 containerPointToLayerPoint: function (point) { // (Point) 4056 return toPoint(point).subtract(this._getMapPanePos()); 4057 }, 4058 4059 // @method layerPointToContainerPoint(point: Point): Point 4060 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4061 // returns the corresponding pixel coordinate relative to the map container. 4062 layerPointToContainerPoint: function (point) { // (Point) 4063 return toPoint(point).add(this._getMapPanePos()); 4064 }, 4065 4066 // @method containerPointToLatLng(point: Point): LatLng 4067 // Given a pixel coordinate relative to the map container, returns 4068 // the corresponding geographical coordinate (for the current zoom level). 4069 containerPointToLatLng: function (point) { 4070 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4071 return this.layerPointToLatLng(layerPoint); 4072 }, 4073 4074 // @method latLngToContainerPoint(latlng: LatLng): Point 4075 // Given a geographical coordinate, returns the corresponding pixel coordinate 4076 // relative to the map container. 4077 latLngToContainerPoint: function (latlng) { 4078 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4079 }, 4080 4081 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4082 // Given a MouseEvent object, returns the pixel coordinate relative to the 4083 // map container where the event took place. 4084 mouseEventToContainerPoint: function (e) { 4085 return getMousePosition(e, this._container); 4086 }, 4087 4088 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4089 // Given a MouseEvent object, returns the pixel coordinate relative to 4090 // the [origin pixel](#map-getpixelorigin) where the event took place. 4091 mouseEventToLayerPoint: function (e) { 4092 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4093 }, 4094 4095 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4096 // Given a MouseEvent object, returns geographical coordinate where the 4097 // event took place. 4098 mouseEventToLatLng: function (e) { // (MouseEvent) 4099 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4100 }, 4101 4102 4103 // map initialization methods 4104 4105 _initContainer: function (id) { 4106 var container = this._container = get(id); 4107 4108 if (!container) { 4109 throw new Error('Map container not found.'); 4110 } else if (container._leaflet_id) { 4111 throw new Error('Map container is already initialized.'); 4112 } 4113 4114 on(container, 'scroll', this._onScroll, this); 4115 this._containerId = stamp(container); 4116 }, 4117 4118 _initLayout: function () { 4119 var container = this._container; 4120 4121 this._fadeAnimated = this.options.fadeAnimation && any3d; 4122 4123 addClass(container, 'leaflet-container' + 4124 (touch ? ' leaflet-touch' : '') + 4125 (retina ? ' leaflet-retina' : '') + 4126 (ielt9 ? ' leaflet-oldie' : '') + 4127 (safari ? ' leaflet-safari' : '') + 4128 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4129 4130 var position = getStyle(container, 'position'); 4131 4132 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { 4133 container.style.position = 'relative'; 4134 } 4135 4136 this._initPanes(); 4137 4138 if (this._initControlPos) { 4139 this._initControlPos(); 4140 } 4141 }, 4142 4143 _initPanes: function () { 4144 var panes = this._panes = {}; 4145 this._paneRenderers = {}; 4146 4147 // @section 4148 // 4149 // Panes are DOM elements used to control the ordering of layers on the map. You 4150 // can access panes with [`map.getPane`](#map-getpane) or 4151 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4152 // [`map.createPane`](#map-createpane) method. 4153 // 4154 // Every map has the following default panes that differ only in zIndex. 4155 // 4156 // @pane mapPane: HTMLElement = 'auto' 4157 // Pane that contains all other map panes 4158 4159 this._mapPane = this.createPane('mapPane', this._container); 4160 setPosition(this._mapPane, new Point(0, 0)); 4161 4162 // @pane tilePane: HTMLElement = 200 4163 // Pane for `GridLayer`s and `TileLayer`s 4164 this.createPane('tilePane'); 4165 // @pane overlayPane: HTMLElement = 400 4166 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4167 this.createPane('shadowPane'); 4168 // @pane shadowPane: HTMLElement = 500 4169 // Pane for overlay shadows (e.g. `Marker` shadows) 4170 this.createPane('overlayPane'); 4171 // @pane markerPane: HTMLElement = 600 4172 // Pane for `Icon`s of `Marker`s 4173 this.createPane('markerPane'); 4174 // @pane tooltipPane: HTMLElement = 650 4175 // Pane for `Tooltip`s. 4176 this.createPane('tooltipPane'); 4177 // @pane popupPane: HTMLElement = 700 4178 // Pane for `Popup`s. 4179 this.createPane('popupPane'); 4180 4181 if (!this.options.markerZoomAnimation) { 4182 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4183 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4184 } 4185 }, 4186 4187 4188 // private methods that modify map state 4189 4190 // @section Map state change events 4191 _resetView: function (center, zoom) { 4192 setPosition(this._mapPane, new Point(0, 0)); 4193 4194 var loading = !this._loaded; 4195 this._loaded = true; 4196 zoom = this._limitZoom(zoom); 4197 4198 this.fire('viewprereset'); 4199 4200 var zoomChanged = this._zoom !== zoom; 4201 this 4202 ._moveStart(zoomChanged, false) 4203 ._move(center, zoom) 4204 ._moveEnd(zoomChanged); 4205 4206 // @event viewreset: Event 4207 // Fired when the map needs to redraw its content (this usually happens 4208 // on map zoom or load). Very useful for creating custom overlays. 4209 this.fire('viewreset'); 4210 4211 // @event load: Event 4212 // Fired when the map is initialized (when its center and zoom are set 4213 // for the first time). 4214 if (loading) { 4215 this.fire('load'); 4216 } 4217 }, 4218 4219 _moveStart: function (zoomChanged, noMoveStart) { 4220 // @event zoomstart: Event 4221 // Fired when the map zoom is about to change (e.g. before zoom animation). 4222 // @event movestart: Event 4223 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4224 if (zoomChanged) { 4225 this.fire('zoomstart'); 4226 } 4227 if (!noMoveStart) { 4228 this.fire('movestart'); 4229 } 4230 return this; 4231 }, 4232 4233 _move: function (center, zoom, data) { 4234 if (zoom === undefined) { 4235 zoom = this._zoom; 4236 } 4237 var zoomChanged = this._zoom !== zoom; 4238 4239 this._zoom = zoom; 4240 this._lastCenter = center; 4241 this._pixelOrigin = this._getNewPixelOrigin(center); 4242 4243 // @event zoom: Event 4244 // Fired repeatedly during any change in zoom level, including zoom 4245 // and fly animations. 4246 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4247 this.fire('zoom', data); 4248 } 4249 4250 // @event move: Event 4251 // Fired repeatedly during any movement of the map, including pan and 4252 // fly animations. 4253 return this.fire('move', data); 4254 }, 4255 4256 _moveEnd: function (zoomChanged) { 4257 // @event zoomend: Event 4258 // Fired when the map has changed, after any animations. 4259 if (zoomChanged) { 4260 this.fire('zoomend'); 4261 } 4262 4263 // @event moveend: Event 4264 // Fired when the center of the map stops changing (e.g. user stopped 4265 // dragging the map). 4266 return this.fire('moveend'); 4267 }, 4268 4269 _stop: function () { 4270 cancelAnimFrame(this._flyToFrame); 4271 if (this._panAnim) { 4272 this._panAnim.stop(); 4273 } 4274 return this; 4275 }, 4276 4277 _rawPanBy: function (offset) { 4278 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4279 }, 4280 4281 _getZoomSpan: function () { 4282 return this.getMaxZoom() - this.getMinZoom(); 4283 }, 4284 4285 _panInsideMaxBounds: function () { 4286 if (!this._enforcingBounds) { 4287 this.panInsideBounds(this.options.maxBounds); 4288 } 4289 }, 4290 4291 _checkIfLoaded: function () { 4292 if (!this._loaded) { 4293 throw new Error('Set map center and zoom first.'); 4294 } 4295 }, 4296 4297 // DOM event handling 4298 4299 // @section Interaction events 4300 _initEvents: function (remove$$1) { 4301 this._targets = {}; 4302 this._targets[stamp(this._container)] = this; 4303 4304 var onOff = remove$$1 ? off : on; 4305 4306 // @event click: MouseEvent 4307 // Fired when the user clicks (or taps) the map. 4308 // @event dblclick: MouseEvent 4309 // Fired when the user double-clicks (or double-taps) the map. 4310 // @event mousedown: MouseEvent 4311 // Fired when the user pushes the mouse button on the map. 4312 // @event mouseup: MouseEvent 4313 // Fired when the user releases the mouse button on the map. 4314 // @event mouseover: MouseEvent 4315 // Fired when the mouse enters the map. 4316 // @event mouseout: MouseEvent 4317 // Fired when the mouse leaves the map. 4318 // @event mousemove: MouseEvent 4319 // Fired while the mouse moves over the map. 4320 // @event contextmenu: MouseEvent 4321 // Fired when the user pushes the right mouse button on the map, prevents 4322 // default browser context menu from showing if there are listeners on 4323 // this event. Also fired on mobile when the user holds a single touch 4324 // for a second (also called long press). 4325 // @event keypress: KeyboardEvent 4326 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused. 4327 // @event keydown: KeyboardEvent 4328 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event, 4329 // the `keydown` event is fired for keys that produce a character value and for keys 4330 // that do not produce a character value. 4331 // @event keyup: KeyboardEvent 4332 // Fired when the user releases a key from the keyboard while the map is focused. 4333 onOff(this._container, 'click dblclick mousedown mouseup ' + 4334 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this); 4335 4336 if (this.options.trackResize) { 4337 onOff(window, 'resize', this._onResize, this); 4338 } 4339 4340 if (any3d && this.options.transform3DLimit) { 4341 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4342 } 4343 }, 4344 4345 _onResize: function () { 4346 cancelAnimFrame(this._resizeRequest); 4347 this._resizeRequest = requestAnimFrame( 4348 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4349 }, 4350 4351 _onScroll: function () { 4352 this._container.scrollTop = 0; 4353 this._container.scrollLeft = 0; 4354 }, 4355 4356 _onMoveEnd: function () { 4357 var pos = this._getMapPanePos(); 4358 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4359 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4360 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ 4361 this._resetView(this.getCenter(), this.getZoom()); 4362 } 4363 }, 4364 4365 _findEventTargets: function (e, type) { 4366 var targets = [], 4367 target, 4368 isHover = type === 'mouseout' || type === 'mouseover', 4369 src = e.target || e.srcElement, 4370 dragging = false; 4371 4372 while (src) { 4373 target = this._targets[stamp(src)]; 4374 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { 4375 // Prevent firing click after you just dragged an object. 4376 dragging = true; 4377 break; 4378 } 4379 if (target && target.listens(type, true)) { 4380 if (isHover && !isExternalTarget(src, e)) { break; } 4381 targets.push(target); 4382 if (isHover) { break; } 4383 } 4384 if (src === this._container) { break; } 4385 src = src.parentNode; 4386 } 4387 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) { 4388 targets = [this]; 4389 } 4390 return targets; 4391 }, 4392 4393 _handleDOMEvent: function (e) { 4394 if (!this._loaded || skipped(e)) { return; } 4395 4396 var type = e.type; 4397 4398 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') { 4399 // prevents outline when clicking on keyboard-focusable element 4400 preventOutline(e.target || e.srcElement); 4401 } 4402 4403 this._fireDOMEvent(e, type); 4404 }, 4405 4406 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4407 4408 _fireDOMEvent: function (e, type, targets) { 4409 4410 if (e.type === 'click') { 4411 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4412 // @event preclick: MouseEvent 4413 // Fired before mouse click on the map (sometimes useful when you 4414 // want something to happen on click before any existing click 4415 // handlers start running). 4416 var synth = extend({}, e); 4417 synth.type = 'preclick'; 4418 this._fireDOMEvent(synth, synth.type, targets); 4419 } 4420 4421 if (e._stopped) { return; } 4422 4423 // Find the layer the event is propagating from and its parents. 4424 targets = (targets || []).concat(this._findEventTargets(e, type)); 4425 4426 if (!targets.length) { return; } 4427 4428 var target = targets[0]; 4429 if (type === 'contextmenu' && target.listens(type, true)) { 4430 preventDefault(e); 4431 } 4432 4433 var data = { 4434 originalEvent: e 4435 }; 4436 4437 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') { 4438 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4439 data.containerPoint = isMarker ? 4440 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4441 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4442 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4443 } 4444 4445 for (var i = 0; i < targets.length; i++) { 4446 targets[i].fire(type, data, true); 4447 if (data.originalEvent._stopped || 4448 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4449 } 4450 }, 4451 4452 _draggableMoved: function (obj) { 4453 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4454 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4455 }, 4456 4457 _clearHandlers: function () { 4458 for (var i = 0, len = this._handlers.length; i < len; i++) { 4459 this._handlers[i].disable(); 4460 } 4461 }, 4462 4463 // @section Other Methods 4464 4465 // @method whenReady(fn: Function, context?: Object): this 4466 // Runs the given function `fn` when the map gets initialized with 4467 // a view (center and zoom) and at least one layer, or immediately 4468 // if it's already initialized, optionally passing a function context. 4469 whenReady: function (callback, context) { 4470 if (this._loaded) { 4471 callback.call(context || this, {target: this}); 4472 } else { 4473 this.on('load', callback, context); 4474 } 4475 return this; 4476 }, 4477 4478 4479 // private methods for getting map state 4480 4481 _getMapPanePos: function () { 4482 return getPosition(this._mapPane) || new Point(0, 0); 4483 }, 4484 4485 _moved: function () { 4486 var pos = this._getMapPanePos(); 4487 return pos && !pos.equals([0, 0]); 4488 }, 4489 4490 _getTopLeftPoint: function (center, zoom) { 4491 var pixelOrigin = center && zoom !== undefined ? 4492 this._getNewPixelOrigin(center, zoom) : 4493 this.getPixelOrigin(); 4494 return pixelOrigin.subtract(this._getMapPanePos()); 4495 }, 4496 4497 _getNewPixelOrigin: function (center, zoom) { 4498 var viewHalf = this.getSize()._divideBy(2); 4499 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4500 }, 4501 4502 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4503 var topLeft = this._getNewPixelOrigin(center, zoom); 4504 return this.project(latlng, zoom)._subtract(topLeft); 4505 }, 4506 4507 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4508 var topLeft = this._getNewPixelOrigin(center, zoom); 4509 return toBounds([ 4510 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4511 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4512 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4513 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4514 ]); 4515 }, 4516 4517 // layer point of the current center 4518 _getCenterLayerPoint: function () { 4519 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4520 }, 4521 4522 // offset of the specified place to the current center in pixels 4523 _getCenterOffset: function (latlng) { 4524 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4525 }, 4526 4527 // adjust center for view to get inside bounds 4528 _limitCenter: function (center, zoom, bounds) { 4529 4530 if (!bounds) { return center; } 4531 4532 var centerPoint = this.project(center, zoom), 4533 viewHalf = this.getSize().divideBy(2), 4534 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4535 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4536 4537 // If offset is less than a pixel, ignore. 4538 // This prevents unstable projections from getting into 4539 // an infinite loop of tiny offsets. 4540 if (offset.round().equals([0, 0])) { 4541 return center; 4542 } 4543 4544 return this.unproject(centerPoint.add(offset), zoom); 4545 }, 4546 4547 // adjust offset for view to get inside bounds 4548 _limitOffset: function (offset, bounds) { 4549 if (!bounds) { return offset; } 4550 4551 var viewBounds = this.getPixelBounds(), 4552 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4553 4554 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4555 }, 4556 4557 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4558 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4559 var projectedMaxBounds = toBounds( 4560 this.project(maxBounds.getNorthEast(), zoom), 4561 this.project(maxBounds.getSouthWest(), zoom) 4562 ), 4563 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4564 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4565 4566 dx = this._rebound(minOffset.x, -maxOffset.x), 4567 dy = this._rebound(minOffset.y, -maxOffset.y); 4568 4569 return new Point(dx, dy); 4570 }, 4571 4572 _rebound: function (left, right) { 4573 return left + right > 0 ? 4574 Math.round(left - right) / 2 : 4575 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4576 }, 4577 4578 _limitZoom: function (zoom) { 4579 var min = this.getMinZoom(), 4580 max = this.getMaxZoom(), 4581 snap = any3d ? this.options.zoomSnap : 1; 4582 if (snap) { 4583 zoom = Math.round(zoom / snap) * snap; 4584 } 4585 return Math.max(min, Math.min(max, zoom)); 4586 }, 4587 4588 _onPanTransitionStep: function () { 4589 this.fire('move'); 4590 }, 4591 4592 _onPanTransitionEnd: function () { 4593 removeClass(this._mapPane, 'leaflet-pan-anim'); 4594 this.fire('moveend'); 4595 }, 4596 4597 _tryAnimatedPan: function (center, options) { 4598 // difference between the new and current centers in pixels 4599 var offset = this._getCenterOffset(center)._trunc(); 4600 4601 // don't animate too far unless animate: true specified in options 4602 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4603 4604 this.panBy(offset, options); 4605 4606 return true; 4607 }, 4608 4609 _createAnimProxy: function () { 4610 4611 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4612 this._panes.mapPane.appendChild(proxy); 4613 4614 this.on('zoomanim', function (e) { 4615 var prop = TRANSFORM, 4616 transform = this._proxy.style[prop]; 4617 4618 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4619 4620 // workaround for case when transform is the same and so transitionend event is not fired 4621 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4622 this._onZoomTransitionEnd(); 4623 } 4624 }, this); 4625 4626 this.on('load moveend', this._animMoveEnd, this); 4627 4628 this._on('unload', this._destroyAnimProxy, this); 4629 }, 4630 4631 _destroyAnimProxy: function () { 4632 remove(this._proxy); 4633 this.off('load moveend', this._animMoveEnd, this); 4634 delete this._proxy; 4635 }, 4636 4637 _animMoveEnd: function () { 4638 var c = this.getCenter(), 4639 z = this.getZoom(); 4640 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 4641 }, 4642 4643 _catchTransitionEnd: function (e) { 4644 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 4645 this._onZoomTransitionEnd(); 4646 } 4647 }, 4648 4649 _nothingToAnimate: function () { 4650 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 4651 }, 4652 4653 _tryAnimatedZoom: function (center, zoom, options) { 4654 4655 if (this._animatingZoom) { return true; } 4656 4657 options = options || {}; 4658 4659 // don't animate if disabled, not supported or zoom difference is too large 4660 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 4661 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 4662 4663 // offset is the pixel coords of the zoom origin relative to the current center 4664 var scale = this.getZoomScale(zoom), 4665 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 4666 4667 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 4668 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 4669 4670 requestAnimFrame(function () { 4671 this 4672 ._moveStart(true, false) 4673 ._animateZoom(center, zoom, true); 4674 }, this); 4675 4676 return true; 4677 }, 4678 4679 _animateZoom: function (center, zoom, startAnim, noUpdate) { 4680 if (!this._mapPane) { return; } 4681 4682 if (startAnim) { 4683 this._animatingZoom = true; 4684 4685 // remember what center/zoom to set after animation 4686 this._animateToCenter = center; 4687 this._animateToZoom = zoom; 4688 4689 addClass(this._mapPane, 'leaflet-zoom-anim'); 4690 } 4691 4692 // @section Other Events 4693 // @event zoomanim: ZoomAnimEvent 4694 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom. 4695 this.fire('zoomanim', { 4696 center: center, 4697 zoom: zoom, 4698 noUpdate: noUpdate 4699 }); 4700 4701 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 4702 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 4703 }, 4704 4705 _onZoomTransitionEnd: function () { 4706 if (!this._animatingZoom) { return; } 4707 4708 if (this._mapPane) { 4709 removeClass(this._mapPane, 'leaflet-zoom-anim'); 4710 } 4711 4712 this._animatingZoom = false; 4713 4714 this._move(this._animateToCenter, this._animateToZoom); 4715 4716 // This anim frame should prevent an obscure iOS webkit tile loading race condition. 4717 requestAnimFrame(function () { 4718 this._moveEnd(true); 4719 }, this); 4720 } 4721 }); 4722 4723 // @section 4724 4725 // @factory L.map(id: String, options?: Map options) 4726 // Instantiates a map object given the DOM ID of a `<div>` element 4727 // and optionally an object literal with `Map options`. 4728 // 4729 // @alternative 4730 // @factory L.map(el: HTMLElement, options?: Map options) 4731 // Instantiates a map object given an instance of a `<div>` HTML element 4732 // and optionally an object literal with `Map options`. 4733 function createMap(id, options) { 4734 return new Map(id, options); 4735 } 4736 4737 /* 4738 * @class Control 4739 * @aka L.Control 4740 * @inherits Class 4741 * 4742 * L.Control is a base class for implementing map controls. Handles positioning. 4743 * All other controls extend from this class. 4744 */ 4745 4746 var Control = Class.extend({ 4747 // @section 4748 // @aka Control options 4749 options: { 4750 // @option position: String = 'topright' 4751 // The position of the control (one of the map corners). Possible values are `'topleft'`, 4752 // `'topright'`, `'bottomleft'` or `'bottomright'` 4753 position: 'topright' 4754 }, 4755 4756 initialize: function (options) { 4757 setOptions(this, options); 4758 }, 4759 4760 /* @section 4761 * Classes extending L.Control will inherit the following methods: 4762 * 4763 * @method getPosition: string 4764 * Returns the position of the control. 4765 */ 4766 getPosition: function () { 4767 return this.options.position; 4768 }, 4769 4770 // @method setPosition(position: string): this 4771 // Sets the position of the control. 4772 setPosition: function (position) { 4773 var map = this._map; 4774 4775 if (map) { 4776 map.removeControl(this); 4777 } 4778 4779 this.options.position = position; 4780 4781 if (map) { 4782 map.addControl(this); 4783 } 4784 4785 return this; 4786 }, 4787 4788 // @method getContainer: HTMLElement 4789 // Returns the HTMLElement that contains the control. 4790 getContainer: function () { 4791 return this._container; 4792 }, 4793 4794 // @method addTo(map: Map): this 4795 // Adds the control to the given map. 4796 addTo: function (map) { 4797 this.remove(); 4798 this._map = map; 4799 4800 var container = this._container = this.onAdd(map), 4801 pos = this.getPosition(), 4802 corner = map._controlCorners[pos]; 4803 4804 addClass(container, 'leaflet-control'); 4805 4806 if (pos.indexOf('bottom') !== -1) { 4807 corner.insertBefore(container, corner.firstChild); 4808 } else { 4809 corner.appendChild(container); 4810 } 4811 4812 this._map.on('unload', this.remove, this); 4813 4814 return this; 4815 }, 4816 4817 // @method remove: this 4818 // Removes the control from the map it is currently active on. 4819 remove: function () { 4820 if (!this._map) { 4821 return this; 4822 } 4823 4824 remove(this._container); 4825 4826 if (this.onRemove) { 4827 this.onRemove(this._map); 4828 } 4829 4830 this._map.off('unload', this.remove, this); 4831 this._map = null; 4832 4833 return this; 4834 }, 4835 4836 _refocusOnMap: function (e) { 4837 // if map exists and event is not a keyboard event 4838 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 4839 this._map.getContainer().focus(); 4840 } 4841 } 4842 }); 4843 4844 var control = function (options) { 4845 return new Control(options); 4846 }; 4847 4848 /* @section Extension methods 4849 * @uninheritable 4850 * 4851 * Every control should extend from `L.Control` and (re-)implement the following methods. 4852 * 4853 * @method onAdd(map: Map): HTMLElement 4854 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 4855 * 4856 * @method onRemove(map: Map) 4857 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 4858 */ 4859 4860 /* @namespace Map 4861 * @section Methods for Layers and Controls 4862 */ 4863 Map.include({ 4864 // @method addControl(control: Control): this 4865 // Adds the given control to the map 4866 addControl: function (control) { 4867 control.addTo(this); 4868 return this; 4869 }, 4870 4871 // @method removeControl(control: Control): this 4872 // Removes the given control from the map 4873 removeControl: function (control) { 4874 control.remove(); 4875 return this; 4876 }, 4877 4878 _initControlPos: function () { 4879 var corners = this._controlCorners = {}, 4880 l = 'leaflet-', 4881 container = this._controlContainer = 4882 create$1('div', l + 'control-container', this._container); 4883 4884 function createCorner(vSide, hSide) { 4885 var className = l + vSide + ' ' + l + hSide; 4886 4887 corners[vSide + hSide] = create$1('div', className, container); 4888 } 4889 4890 createCorner('top', 'left'); 4891 createCorner('top', 'right'); 4892 createCorner('bottom', 'left'); 4893 createCorner('bottom', 'right'); 4894 }, 4895 4896 _clearControlPos: function () { 4897 for (var i in this._controlCorners) { 4898 remove(this._controlCorners[i]); 4899 } 4900 remove(this._controlContainer); 4901 delete this._controlCorners; 4902 delete this._controlContainer; 4903 } 4904 }); 4905 4906 /* 4907 * @class Control.Layers 4908 * @aka L.Control.Layers 4909 * @inherits Control 4910 * 4911 * 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`. 4912 * 4913 * @example 4914 * 4915 * ```js 4916 * var baseLayers = { 4917 * "Mapbox": mapbox, 4918 * "OpenStreetMap": osm 4919 * }; 4920 * 4921 * var overlays = { 4922 * "Marker": marker, 4923 * "Roads": roadsLayer 4924 * }; 4925 * 4926 * L.control.layers(baseLayers, overlays).addTo(map); 4927 * ``` 4928 * 4929 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 4930 * 4931 * ```js 4932 * { 4933 * "<someName1>": layer1, 4934 * "<someName2>": layer2 4935 * } 4936 * ``` 4937 * 4938 * The layer names can contain HTML, which allows you to add additional styling to the items: 4939 * 4940 * ```js 4941 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 4942 * ``` 4943 */ 4944 4945 var Layers = Control.extend({ 4946 // @section 4947 // @aka Control.Layers options 4948 options: { 4949 // @option collapsed: Boolean = true 4950 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. 4951 collapsed: true, 4952 position: 'topright', 4953 4954 // @option autoZIndex: Boolean = true 4955 // 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. 4956 autoZIndex: true, 4957 4958 // @option hideSingleBase: Boolean = false 4959 // If `true`, the base layers in the control will be hidden when there is only one. 4960 hideSingleBase: false, 4961 4962 // @option sortLayers: Boolean = false 4963 // Whether to sort the layers. When `false`, layers will keep the order 4964 // in which they were added to the control. 4965 sortLayers: false, 4966 4967 // @option sortFunction: Function = * 4968 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 4969 // that will be used for sorting the layers, when `sortLayers` is `true`. 4970 // The function receives both the `L.Layer` instances and their names, as in 4971 // `sortFunction(layerA, layerB, nameA, nameB)`. 4972 // By default, it sorts layers alphabetically by their name. 4973 sortFunction: function (layerA, layerB, nameA, nameB) { 4974 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 4975 } 4976 }, 4977 4978 initialize: function (baseLayers, overlays, options) { 4979 setOptions(this, options); 4980 4981 this._layerControlInputs = []; 4982 this._layers = []; 4983 this._lastZIndex = 0; 4984 this._handlingClick = false; 4985 4986 for (var i in baseLayers) { 4987 this._addLayer(baseLayers[i], i); 4988 } 4989 4990 for (i in overlays) { 4991 this._addLayer(overlays[i], i, true); 4992 } 4993 }, 4994 4995 onAdd: function (map) { 4996 this._initLayout(); 4997 this._update(); 4998 4999 this._map = map; 5000 map.on('zoomend', this._checkDisabledLayers, this); 5001 5002 for (var i = 0; i < this._layers.length; i++) { 5003 this._layers[i].layer.on('add remove', this._onLayerChange, this); 5004 } 5005 5006 return this._container; 5007 }, 5008 5009 addTo: function (map) { 5010 Control.prototype.addTo.call(this, map); 5011 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 5012 return this._expandIfNotCollapsed(); 5013 }, 5014 5015 onRemove: function () { 5016 this._map.off('zoomend', this._checkDisabledLayers, this); 5017 5018 for (var i = 0; i < this._layers.length; i++) { 5019 this._layers[i].layer.off('add remove', this._onLayerChange, this); 5020 } 5021 }, 5022 5023 // @method addBaseLayer(layer: Layer, name: String): this 5024 // Adds a base layer (radio button entry) with the given name to the control. 5025 addBaseLayer: function (layer, name) { 5026 this._addLayer(layer, name); 5027 return (this._map) ? this._update() : this; 5028 }, 5029 5030 // @method addOverlay(layer: Layer, name: String): this 5031 // Adds an overlay (checkbox entry) with the given name to the control. 5032 addOverlay: function (layer, name) { 5033 this._addLayer(layer, name, true); 5034 return (this._map) ? this._update() : this; 5035 }, 5036 5037 // @method removeLayer(layer: Layer): this 5038 // Remove the given layer from the control. 5039 removeLayer: function (layer) { 5040 layer.off('add remove', this._onLayerChange, this); 5041 5042 var obj = this._getLayer(stamp(layer)); 5043 if (obj) { 5044 this._layers.splice(this._layers.indexOf(obj), 1); 5045 } 5046 return (this._map) ? this._update() : this; 5047 }, 5048 5049 // @method expand(): this 5050 // Expand the control container if collapsed. 5051 expand: function () { 5052 addClass(this._container, 'leaflet-control-layers-expanded'); 5053 this._section.style.height = null; 5054 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 5055 if (acceptableHeight < this._section.clientHeight) { 5056 addClass(this._section, 'leaflet-control-layers-scrollbar'); 5057 this._section.style.height = acceptableHeight + 'px'; 5058 } else { 5059 removeClass(this._section, 'leaflet-control-layers-scrollbar'); 5060 } 5061 this._checkDisabledLayers(); 5062 return this; 5063 }, 5064 5065 // @method collapse(): this 5066 // Collapse the control container if expanded. 5067 collapse: function () { 5068 removeClass(this._container, 'leaflet-control-layers-expanded'); 5069 return this; 5070 }, 5071 5072 _initLayout: function () { 5073 var className = 'leaflet-control-layers', 5074 container = this._container = create$1('div', className), 5075 collapsed = this.options.collapsed; 5076 5077 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 5078 container.setAttribute('aria-haspopup', true); 5079 5080 disableClickPropagation(container); 5081 disableScrollPropagation(container); 5082 5083 var section = this._section = create$1('section', className + '-list'); 5084 5085 if (collapsed) { 5086 this._map.on('click', this.collapse, this); 5087 5088 if (!android) { 5089 on(container, { 5090 mouseenter: this.expand, 5091 mouseleave: this.collapse 5092 }, this); 5093 } 5094 } 5095 5096 var link = this._layersLink = create$1('a', className + '-toggle', container); 5097 link.href = '#'; 5098 link.title = 'Layers'; 5099 5100 if (touch) { 5101 on(link, 'click', stop); 5102 on(link, 'click', this.expand, this); 5103 } else { 5104 on(link, 'focus', this.expand, this); 5105 } 5106 5107 if (!collapsed) { 5108 this.expand(); 5109 } 5110 5111 this._baseLayersList = create$1('div', className + '-base', section); 5112 this._separator = create$1('div', className + '-separator', section); 5113 this._overlaysList = create$1('div', className + '-overlays', section); 5114 5115 container.appendChild(section); 5116 }, 5117 5118 _getLayer: function (id) { 5119 for (var i = 0; i < this._layers.length; i++) { 5120 5121 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5122 return this._layers[i]; 5123 } 5124 } 5125 }, 5126 5127 _addLayer: function (layer, name, overlay) { 5128 if (this._map) { 5129 layer.on('add remove', this._onLayerChange, this); 5130 } 5131 5132 this._layers.push({ 5133 layer: layer, 5134 name: name, 5135 overlay: overlay 5136 }); 5137 5138 if (this.options.sortLayers) { 5139 this._layers.sort(bind(function (a, b) { 5140 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5141 }, this)); 5142 } 5143 5144 if (this.options.autoZIndex && layer.setZIndex) { 5145 this._lastZIndex++; 5146 layer.setZIndex(this._lastZIndex); 5147 } 5148 5149 this._expandIfNotCollapsed(); 5150 }, 5151 5152 _update: function () { 5153 if (!this._container) { return this; } 5154 5155 empty(this._baseLayersList); 5156 empty(this._overlaysList); 5157 5158 this._layerControlInputs = []; 5159 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5160 5161 for (i = 0; i < this._layers.length; i++) { 5162 obj = this._layers[i]; 5163 this._addItem(obj); 5164 overlaysPresent = overlaysPresent || obj.overlay; 5165 baseLayersPresent = baseLayersPresent || !obj.overlay; 5166 baseLayersCount += !obj.overlay ? 1 : 0; 5167 } 5168 5169 // Hide base layers section if there's only one layer. 5170 if (this.options.hideSingleBase) { 5171 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5172 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5173 } 5174 5175 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5176 5177 return this; 5178 }, 5179 5180 _onLayerChange: function (e) { 5181 if (!this._handlingClick) { 5182 this._update(); 5183 } 5184 5185 var obj = this._getLayer(stamp(e.target)); 5186 5187 // @namespace Map 5188 // @section Layer events 5189 // @event baselayerchange: LayersControlEvent 5190 // Fired when the base layer is changed through the [layer control](#control-layers). 5191 // @event overlayadd: LayersControlEvent 5192 // Fired when an overlay is selected through the [layer control](#control-layers). 5193 // @event overlayremove: LayersControlEvent 5194 // Fired when an overlay is deselected through the [layer control](#control-layers). 5195 // @namespace Control.Layers 5196 var type = obj.overlay ? 5197 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5198 (e.type === 'add' ? 'baselayerchange' : null); 5199 5200 if (type) { 5201 this._map.fire(type, obj); 5202 } 5203 }, 5204 5205 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) 5206 _createRadioElement: function (name, checked) { 5207 5208 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5209 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5210 5211 var radioFragment = document.createElement('div'); 5212 radioFragment.innerHTML = radioHtml; 5213 5214 return radioFragment.firstChild; 5215 }, 5216 5217 _addItem: function (obj) { 5218 var label = document.createElement('label'), 5219 checked = this._map.hasLayer(obj.layer), 5220 input; 5221 5222 if (obj.overlay) { 5223 input = document.createElement('input'); 5224 input.type = 'checkbox'; 5225 input.className = 'leaflet-control-layers-selector'; 5226 input.defaultChecked = checked; 5227 } else { 5228 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked); 5229 } 5230 5231 this._layerControlInputs.push(input); 5232 input.layerId = stamp(obj.layer); 5233 5234 on(input, 'click', this._onInputClick, this); 5235 5236 var name = document.createElement('span'); 5237 name.innerHTML = ' ' + obj.name; 5238 5239 // Helps from preventing layer control flicker when checkboxes are disabled 5240 // https://github.com/Leaflet/Leaflet/issues/2771 5241 var holder = document.createElement('div'); 5242 5243 label.appendChild(holder); 5244 holder.appendChild(input); 5245 holder.appendChild(name); 5246 5247 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5248 container.appendChild(label); 5249 5250 this._checkDisabledLayers(); 5251 return label; 5252 }, 5253 5254 _onInputClick: function () { 5255 var inputs = this._layerControlInputs, 5256 input, layer; 5257 var addedLayers = [], 5258 removedLayers = []; 5259 5260 this._handlingClick = true; 5261 5262 for (var i = inputs.length - 1; i >= 0; i--) { 5263 input = inputs[i]; 5264 layer = this._getLayer(input.layerId).layer; 5265 5266 if (input.checked) { 5267 addedLayers.push(layer); 5268 } else if (!input.checked) { 5269 removedLayers.push(layer); 5270 } 5271 } 5272 5273 // Bugfix issue 2318: Should remove all old layers before readding new ones 5274 for (i = 0; i < removedLayers.length; i++) { 5275 if (this._map.hasLayer(removedLayers[i])) { 5276 this._map.removeLayer(removedLayers[i]); 5277 } 5278 } 5279 for (i = 0; i < addedLayers.length; i++) { 5280 if (!this._map.hasLayer(addedLayers[i])) { 5281 this._map.addLayer(addedLayers[i]); 5282 } 5283 } 5284 5285 this._handlingClick = false; 5286 5287 this._refocusOnMap(); 5288 }, 5289 5290 _checkDisabledLayers: function () { 5291 var inputs = this._layerControlInputs, 5292 input, 5293 layer, 5294 zoom = this._map.getZoom(); 5295 5296 for (var i = inputs.length - 1; i >= 0; i--) { 5297 input = inputs[i]; 5298 layer = this._getLayer(input.layerId).layer; 5299 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5300 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5301 5302 } 5303 }, 5304 5305 _expandIfNotCollapsed: function () { 5306 if (this._map && !this.options.collapsed) { 5307 this.expand(); 5308 } 5309 return this; 5310 }, 5311 5312 _expand: function () { 5313 // Backward compatibility, remove me in 1.1. 5314 return this.expand(); 5315 }, 5316 5317 _collapse: function () { 5318 // Backward compatibility, remove me in 1.1. 5319 return this.collapse(); 5320 } 5321 5322 }); 5323 5324 5325 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5326 // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. 5327 var layers = function (baseLayers, overlays, options) { 5328 return new Layers(baseLayers, overlays, options); 5329 }; 5330 5331 /* 5332 * @class Control.Zoom 5333 * @aka L.Control.Zoom 5334 * @inherits Control 5335 * 5336 * 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`. 5337 */ 5338 5339 var Zoom = Control.extend({ 5340 // @section 5341 // @aka Control.Zoom options 5342 options: { 5343 position: 'topleft', 5344 5345 // @option zoomInText: String = '+' 5346 // The text set on the 'zoom in' button. 5347 zoomInText: '+', 5348 5349 // @option zoomInTitle: String = 'Zoom in' 5350 // The title set on the 'zoom in' button. 5351 zoomInTitle: 'Zoom in', 5352 5353 // @option zoomOutText: String = '−' 5354 // The text set on the 'zoom out' button. 5355 zoomOutText: '−', 5356 5357 // @option zoomOutTitle: String = 'Zoom out' 5358 // The title set on the 'zoom out' button. 5359 zoomOutTitle: 'Zoom out' 5360 }, 5361 5362 onAdd: function (map) { 5363 var zoomName = 'leaflet-control-zoom', 5364 container = create$1('div', zoomName + ' leaflet-bar'), 5365 options = this.options; 5366 5367 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5368 zoomName + '-in', container, this._zoomIn); 5369 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5370 zoomName + '-out', container, this._zoomOut); 5371 5372 this._updateDisabled(); 5373 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5374 5375 return container; 5376 }, 5377 5378 onRemove: function (map) { 5379 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5380 }, 5381 5382 disable: function () { 5383 this._disabled = true; 5384 this._updateDisabled(); 5385 return this; 5386 }, 5387 5388 enable: function () { 5389 this._disabled = false; 5390 this._updateDisabled(); 5391 return this; 5392 }, 5393 5394 _zoomIn: function (e) { 5395 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5396 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5397 } 5398 }, 5399 5400 _zoomOut: function (e) { 5401 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5402 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5403 } 5404 }, 5405 5406 _createButton: function (html, title, className, container, fn) { 5407 var link = create$1('a', className, container); 5408 link.innerHTML = html; 5409 link.href = '#'; 5410 link.title = title; 5411 5412 /* 5413 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5414 */ 5415 link.setAttribute('role', 'button'); 5416 link.setAttribute('aria-label', title); 5417 5418 disableClickPropagation(link); 5419 on(link, 'click', stop); 5420 on(link, 'click', fn, this); 5421 on(link, 'click', this._refocusOnMap, this); 5422 5423 return link; 5424 }, 5425 5426 _updateDisabled: function () { 5427 var map = this._map, 5428 className = 'leaflet-disabled'; 5429 5430 removeClass(this._zoomInButton, className); 5431 removeClass(this._zoomOutButton, className); 5432 5433 if (this._disabled || map._zoom === map.getMinZoom()) { 5434 addClass(this._zoomOutButton, className); 5435 } 5436 if (this._disabled || map._zoom === map.getMaxZoom()) { 5437 addClass(this._zoomInButton, className); 5438 } 5439 } 5440 }); 5441 5442 // @namespace Map 5443 // @section Control options 5444 // @option zoomControl: Boolean = true 5445 // Whether a [zoom control](#control-zoom) is added to the map by default. 5446 Map.mergeOptions({ 5447 zoomControl: true 5448 }); 5449 5450 Map.addInitHook(function () { 5451 if (this.options.zoomControl) { 5452 // @section Controls 5453 // @property zoomControl: Control.Zoom 5454 // The default zoom control (only available if the 5455 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5456 this.zoomControl = new Zoom(); 5457 this.addControl(this.zoomControl); 5458 } 5459 }); 5460 5461 // @namespace Control.Zoom 5462 // @factory L.control.zoom(options: Control.Zoom options) 5463 // Creates a zoom control 5464 var zoom = function (options) { 5465 return new Zoom(options); 5466 }; 5467 5468 /* 5469 * @class Control.Scale 5470 * @aka L.Control.Scale 5471 * @inherits Control 5472 * 5473 * 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`. 5474 * 5475 * @example 5476 * 5477 * ```js 5478 * L.control.scale().addTo(map); 5479 * ``` 5480 */ 5481 5482 var Scale = Control.extend({ 5483 // @section 5484 // @aka Control.Scale options 5485 options: { 5486 position: 'bottomleft', 5487 5488 // @option maxWidth: Number = 100 5489 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5490 maxWidth: 100, 5491 5492 // @option metric: Boolean = True 5493 // Whether to show the metric scale line (m/km). 5494 metric: true, 5495 5496 // @option imperial: Boolean = True 5497 // Whether to show the imperial scale line (mi/ft). 5498 imperial: true 5499 5500 // @option updateWhenIdle: Boolean = false 5501 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5502 }, 5503 5504 onAdd: function (map) { 5505 var className = 'leaflet-control-scale', 5506 container = create$1('div', className), 5507 options = this.options; 5508 5509 this._addScales(options, className + '-line', container); 5510 5511 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5512 map.whenReady(this._update, this); 5513 5514 return container; 5515 }, 5516 5517 onRemove: function (map) { 5518 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5519 }, 5520 5521 _addScales: function (options, className, container) { 5522 if (options.metric) { 5523 this._mScale = create$1('div', className, container); 5524 } 5525 if (options.imperial) { 5526 this._iScale = create$1('div', className, container); 5527 } 5528 }, 5529 5530 _update: function () { 5531 var map = this._map, 5532 y = map.getSize().y / 2; 5533 5534 var maxMeters = map.distance( 5535 map.containerPointToLatLng([0, y]), 5536 map.containerPointToLatLng([this.options.maxWidth, y])); 5537 5538 this._updateScales(maxMeters); 5539 }, 5540 5541 _updateScales: function (maxMeters) { 5542 if (this.options.metric && maxMeters) { 5543 this._updateMetric(maxMeters); 5544 } 5545 if (this.options.imperial && maxMeters) { 5546 this._updateImperial(maxMeters); 5547 } 5548 }, 5549 5550 _updateMetric: function (maxMeters) { 5551 var meters = this._getRoundNum(maxMeters), 5552 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5553 5554 this._updateScale(this._mScale, label, meters / maxMeters); 5555 }, 5556 5557 _updateImperial: function (maxMeters) { 5558 var maxFeet = maxMeters * 3.2808399, 5559 maxMiles, miles, feet; 5560 5561 if (maxFeet > 5280) { 5562 maxMiles = maxFeet / 5280; 5563 miles = this._getRoundNum(maxMiles); 5564 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5565 5566 } else { 5567 feet = this._getRoundNum(maxFeet); 5568 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5569 } 5570 }, 5571 5572 _updateScale: function (scale, text, ratio) { 5573 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5574 scale.innerHTML = text; 5575 }, 5576 5577 _getRoundNum: function (num) { 5578 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5579 d = num / pow10; 5580 5581 d = d >= 10 ? 10 : 5582 d >= 5 ? 5 : 5583 d >= 3 ? 3 : 5584 d >= 2 ? 2 : 1; 5585 5586 return pow10 * d; 5587 } 5588 }); 5589 5590 5591 // @factory L.control.scale(options?: Control.Scale options) 5592 // Creates an scale control with the given options. 5593 var scale = function (options) { 5594 return new Scale(options); 5595 }; 5596 5597 /* 5598 * @class Control.Attribution 5599 * @aka L.Control.Attribution 5600 * @inherits Control 5601 * 5602 * 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. 5603 */ 5604 5605 var Attribution = Control.extend({ 5606 // @section 5607 // @aka Control.Attribution options 5608 options: { 5609 position: 'bottomright', 5610 5611 // @option prefix: String = 'Leaflet' 5612 // The HTML text shown before the attributions. Pass `false` to disable. 5613 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' 5614 }, 5615 5616 initialize: function (options) { 5617 setOptions(this, options); 5618 5619 this._attributions = {}; 5620 }, 5621 5622 onAdd: function (map) { 5623 map.attributionControl = this; 5624 this._container = create$1('div', 'leaflet-control-attribution'); 5625 disableClickPropagation(this._container); 5626 5627 // TODO ugly, refactor 5628 for (var i in map._layers) { 5629 if (map._layers[i].getAttribution) { 5630 this.addAttribution(map._layers[i].getAttribution()); 5631 } 5632 } 5633 5634 this._update(); 5635 5636 return this._container; 5637 }, 5638 5639 // @method setPrefix(prefix: String): this 5640 // Sets the text before the attributions. 5641 setPrefix: function (prefix) { 5642 this.options.prefix = prefix; 5643 this._update(); 5644 return this; 5645 }, 5646 5647 // @method addAttribution(text: String): this 5648 // Adds an attribution text (e.g. `'Vector data © Mapbox'`). 5649 addAttribution: function (text) { 5650 if (!text) { return this; } 5651 5652 if (!this._attributions[text]) { 5653 this._attributions[text] = 0; 5654 } 5655 this._attributions[text]++; 5656 5657 this._update(); 5658 5659 return this; 5660 }, 5661 5662 // @method removeAttribution(text: String): this 5663 // Removes an attribution text. 5664 removeAttribution: function (text) { 5665 if (!text) { return this; } 5666 5667 if (this._attributions[text]) { 5668 this._attributions[text]--; 5669 this._update(); 5670 } 5671 5672 return this; 5673 }, 5674 5675 _update: function () { 5676 if (!this._map) { return; } 5677 5678 var attribs = []; 5679 5680 for (var i in this._attributions) { 5681 if (this._attributions[i]) { 5682 attribs.push(i); 5683 } 5684 } 5685 5686 var prefixAndAttribs = []; 5687 5688 if (this.options.prefix) { 5689 prefixAndAttribs.push(this.options.prefix); 5690 } 5691 if (attribs.length) { 5692 prefixAndAttribs.push(attribs.join(', ')); 5693 } 5694 5695 this._container.innerHTML = prefixAndAttribs.join(' | '); 5696 } 5697 }); 5698 5699 // @namespace Map 5700 // @section Control options 5701 // @option attributionControl: Boolean = true 5702 // Whether a [attribution control](#control-attribution) is added to the map by default. 5703 Map.mergeOptions({ 5704 attributionControl: true 5705 }); 5706 5707 Map.addInitHook(function () { 5708 if (this.options.attributionControl) { 5709 new Attribution().addTo(this); 5710 } 5711 }); 5712 5713 // @namespace Control.Attribution 5714 // @factory L.control.attribution(options: Control.Attribution options) 5715 // Creates an attribution control. 5716 var attribution = function (options) { 5717 return new Attribution(options); 5718 }; 5719 5720 Control.Layers = Layers; 5721 Control.Zoom = Zoom; 5722 Control.Scale = Scale; 5723 Control.Attribution = Attribution; 5724 5725 control.layers = layers; 5726 control.zoom = zoom; 5727 control.scale = scale; 5728 control.attribution = attribution; 5729 5730 /* 5731 L.Handler is a base class for handler classes that are used internally to inject 5732 interaction features like dragging to classes like Map and Marker. 5733 */ 5734 5735 // @class Handler 5736 // @aka L.Handler 5737 // Abstract class for map interaction handlers 5738 5739 var Handler = Class.extend({ 5740 initialize: function (map) { 5741 this._map = map; 5742 }, 5743 5744 // @method enable(): this 5745 // Enables the handler 5746 enable: function () { 5747 if (this._enabled) { return this; } 5748 5749 this._enabled = true; 5750 this.addHooks(); 5751 return this; 5752 }, 5753 5754 // @method disable(): this 5755 // Disables the handler 5756 disable: function () { 5757 if (!this._enabled) { return this; } 5758 5759 this._enabled = false; 5760 this.removeHooks(); 5761 return this; 5762 }, 5763 5764 // @method enabled(): Boolean 5765 // Returns `true` if the handler is enabled 5766 enabled: function () { 5767 return !!this._enabled; 5768 } 5769 5770 // @section Extension methods 5771 // Classes inheriting from `Handler` must implement the two following methods: 5772 // @method addHooks() 5773 // Called when the handler is enabled, should add event hooks. 5774 // @method removeHooks() 5775 // Called when the handler is disabled, should remove the event hooks added previously. 5776 }); 5777 5778 // @section There is static function which can be called without instantiating L.Handler: 5779 // @function addTo(map: Map, name: String): this 5780 // Adds a new Handler to the given map with the given name. 5781 Handler.addTo = function (map, name) { 5782 map.addHandler(name, this); 5783 return this; 5784 }; 5785 5786 var Mixin = {Events: Events}; 5787 5788 /* 5789 * @class Draggable 5790 * @aka L.Draggable 5791 * @inherits Evented 5792 * 5793 * A class for making DOM elements draggable (including touch support). 5794 * Used internally for map and marker dragging. Only works for elements 5795 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 5796 * 5797 * @example 5798 * ```js 5799 * var draggable = new L.Draggable(elementToDrag); 5800 * draggable.enable(); 5801 * ``` 5802 */ 5803 5804 var START = touch ? 'touchstart mousedown' : 'mousedown'; 5805 var END = { 5806 mousedown: 'mouseup', 5807 touchstart: 'touchend', 5808 pointerdown: 'touchend', 5809 MSPointerDown: 'touchend' 5810 }; 5811 var MOVE = { 5812 mousedown: 'mousemove', 5813 touchstart: 'touchmove', 5814 pointerdown: 'touchmove', 5815 MSPointerDown: 'touchmove' 5816 }; 5817 5818 5819 var Draggable = Evented.extend({ 5820 5821 options: { 5822 // @section 5823 // @aka Draggable options 5824 // @option clickTolerance: Number = 3 5825 // The max number of pixels a user can shift the mouse pointer during a click 5826 // for it to be considered a valid click (as opposed to a mouse drag). 5827 clickTolerance: 3 5828 }, 5829 5830 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 5831 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 5832 initialize: function (element, dragStartTarget, preventOutline$$1, options) { 5833 setOptions(this, options); 5834 5835 this._element = element; 5836 this._dragStartTarget = dragStartTarget || element; 5837 this._preventOutline = preventOutline$$1; 5838 }, 5839 5840 // @method enable() 5841 // Enables the dragging ability 5842 enable: function () { 5843 if (this._enabled) { return; } 5844 5845 on(this._dragStartTarget, START, this._onDown, this); 5846 5847 this._enabled = true; 5848 }, 5849 5850 // @method disable() 5851 // Disables the dragging ability 5852 disable: function () { 5853 if (!this._enabled) { return; } 5854 5855 // If we're currently dragging this draggable, 5856 // disabling it counts as first ending the drag. 5857 if (Draggable._dragging === this) { 5858 this.finishDrag(); 5859 } 5860 5861 off(this._dragStartTarget, START, this._onDown, this); 5862 5863 this._enabled = false; 5864 this._moved = false; 5865 }, 5866 5867 _onDown: function (e) { 5868 // Ignore simulated events, since we handle both touch and 5869 // mouse explicitly; otherwise we risk getting duplicates of 5870 // touch events, see #4315. 5871 // Also ignore the event if disabled; this happens in IE11 5872 // under some circumstances, see #3666. 5873 if (e._simulated || !this._enabled) { return; } 5874 5875 this._moved = false; 5876 5877 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 5878 5879 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 5880 Draggable._dragging = this; // Prevent dragging multiple objects at once. 5881 5882 if (this._preventOutline) { 5883 preventOutline(this._element); 5884 } 5885 5886 disableImageDrag(); 5887 disableTextSelection(); 5888 5889 if (this._moving) { return; } 5890 5891 // @event down: Event 5892 // Fired when a drag is about to start. 5893 this.fire('down'); 5894 5895 var first = e.touches ? e.touches[0] : e, 5896 sizedParent = getSizedParentNode(this._element); 5897 5898 this._startPoint = new Point(first.clientX, first.clientY); 5899 5900 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 5901 this._parentScale = getScale(sizedParent); 5902 5903 on(document, MOVE[e.type], this._onMove, this); 5904 on(document, END[e.type], this._onUp, this); 5905 }, 5906 5907 _onMove: function (e) { 5908 // Ignore simulated events, since we handle both touch and 5909 // mouse explicitly; otherwise we risk getting duplicates of 5910 // touch events, see #4315. 5911 // Also ignore the event if disabled; this happens in IE11 5912 // under some circumstances, see #3666. 5913 if (e._simulated || !this._enabled) { return; } 5914 5915 if (e.touches && e.touches.length > 1) { 5916 this._moved = true; 5917 return; 5918 } 5919 5920 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 5921 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 5922 5923 if (!offset.x && !offset.y) { return; } 5924 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 5925 5926 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 5927 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 5928 // and we can use the cached value for the scale. 5929 offset.x /= this._parentScale.x; 5930 offset.y /= this._parentScale.y; 5931 5932 preventDefault(e); 5933 5934 if (!this._moved) { 5935 // @event dragstart: Event 5936 // Fired when a drag starts 5937 this.fire('dragstart'); 5938 5939 this._moved = true; 5940 this._startPos = getPosition(this._element).subtract(offset); 5941 5942 addClass(document.body, 'leaflet-dragging'); 5943 5944 this._lastTarget = e.target || e.srcElement; 5945 // IE and Edge do not give the <use> element, so fetch it 5946 // if necessary 5947 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { 5948 this._lastTarget = this._lastTarget.correspondingUseElement; 5949 } 5950 addClass(this._lastTarget, 'leaflet-drag-target'); 5951 } 5952 5953 this._newPos = this._startPos.add(offset); 5954 this._moving = true; 5955 5956 cancelAnimFrame(this._animRequest); 5957 this._lastEvent = e; 5958 this._animRequest = requestAnimFrame(this._updatePosition, this, true); 5959 }, 5960 5961 _updatePosition: function () { 5962 var e = {originalEvent: this._lastEvent}; 5963 5964 // @event predrag: Event 5965 // Fired continuously during dragging *before* each corresponding 5966 // update of the element's position. 5967 this.fire('predrag', e); 5968 setPosition(this._element, this._newPos); 5969 5970 // @event drag: Event 5971 // Fired continuously during dragging. 5972 this.fire('drag', e); 5973 }, 5974 5975 _onUp: function (e) { 5976 // Ignore simulated events, since we handle both touch and 5977 // mouse explicitly; otherwise we risk getting duplicates of 5978 // touch events, see #4315. 5979 // Also ignore the event if disabled; this happens in IE11 5980 // under some circumstances, see #3666. 5981 if (e._simulated || !this._enabled) { return; } 5982 this.finishDrag(); 5983 }, 5984 5985 finishDrag: function () { 5986 removeClass(document.body, 'leaflet-dragging'); 5987 5988 if (this._lastTarget) { 5989 removeClass(this._lastTarget, 'leaflet-drag-target'); 5990 this._lastTarget = null; 5991 } 5992 5993 for (var i in MOVE) { 5994 off(document, MOVE[i], this._onMove, this); 5995 off(document, END[i], this._onUp, this); 5996 } 5997 5998 enableImageDrag(); 5999 enableTextSelection(); 6000 6001 if (this._moved && this._moving) { 6002 // ensure drag is not fired after dragend 6003 cancelAnimFrame(this._animRequest); 6004 6005 // @event dragend: DragEndEvent 6006 // Fired when the drag ends. 6007 this.fire('dragend', { 6008 distance: this._newPos.distanceTo(this._startPos) 6009 }); 6010 } 6011 6012 this._moving = false; 6013 Draggable._dragging = false; 6014 } 6015 6016 }); 6017 6018 /* 6019 * @namespace LineUtil 6020 * 6021 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 6022 */ 6023 6024 // Simplify polyline with vertex reduction and Douglas-Peucker simplification. 6025 // Improves rendering performance dramatically by lessening the number of points to draw. 6026 6027 // @function simplify(points: Point[], tolerance: Number): Point[] 6028 // Dramatically reduces the number of points in a polyline while retaining 6029 // its shape and returns a new array of simplified points, using the 6030 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). 6031 // Used for a huge performance boost when processing/displaying Leaflet polylines for 6032 // each zoom level and also reducing visual noise. tolerance affects the amount of 6033 // simplification (lesser value means higher quality but slower and with more points). 6034 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). 6035 function simplify(points, tolerance) { 6036 if (!tolerance || !points.length) { 6037 return points.slice(); 6038 } 6039 6040 var sqTolerance = tolerance * tolerance; 6041 6042 // stage 1: vertex reduction 6043 points = _reducePoints(points, sqTolerance); 6044 6045 // stage 2: Douglas-Peucker simplification 6046 points = _simplifyDP(points, sqTolerance); 6047 6048 return points; 6049 } 6050 6051 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 6052 // Returns the distance between point `p` and segment `p1` to `p2`. 6053 function pointToSegmentDistance(p, p1, p2) { 6054 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 6055 } 6056 6057 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 6058 // Returns the closest point from a point `p` on a segment `p1` to `p2`. 6059 function closestPointOnSegment(p, p1, p2) { 6060 return _sqClosestPointOnSegment(p, p1, p2); 6061 } 6062 6063 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm 6064 function _simplifyDP(points, sqTolerance) { 6065 6066 var len = points.length, 6067 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 6068 markers = new ArrayConstructor(len); 6069 6070 markers[0] = markers[len - 1] = 1; 6071 6072 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 6073 6074 var i, 6075 newPoints = []; 6076 6077 for (i = 0; i < len; i++) { 6078 if (markers[i]) { 6079 newPoints.push(points[i]); 6080 } 6081 } 6082 6083 return newPoints; 6084 } 6085 6086 function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6087 6088 var maxSqDist = 0, 6089 index, i, sqDist; 6090 6091 for (i = first + 1; i <= last - 1; i++) { 6092 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6093 6094 if (sqDist > maxSqDist) { 6095 index = i; 6096 maxSqDist = sqDist; 6097 } 6098 } 6099 6100 if (maxSqDist > sqTolerance) { 6101 markers[index] = 1; 6102 6103 _simplifyDPStep(points, markers, sqTolerance, first, index); 6104 _simplifyDPStep(points, markers, sqTolerance, index, last); 6105 } 6106 } 6107 6108 // reduce points that are too close to each other to a single point 6109 function _reducePoints(points, sqTolerance) { 6110 var reducedPoints = [points[0]]; 6111 6112 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6113 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6114 reducedPoints.push(points[i]); 6115 prev = i; 6116 } 6117 } 6118 if (prev < len - 1) { 6119 reducedPoints.push(points[len - 1]); 6120 } 6121 return reducedPoints; 6122 } 6123 6124 var _lastCode; 6125 6126 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean 6127 // Clips the segment a to b by rectangular bounds with the 6128 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6129 // (modifying the segment points directly!). Used by Leaflet to only show polyline 6130 // points that are on the screen or near, increasing performance. 6131 function clipSegment(a, b, bounds, useLastCode, round) { 6132 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6133 codeB = _getBitCode(b, bounds), 6134 6135 codeOut, p, newCode; 6136 6137 // save 2nd code to avoid calculating it on the next segment 6138 _lastCode = codeB; 6139 6140 while (true) { 6141 // if a,b is inside the clip window (trivial accept) 6142 if (!(codeA | codeB)) { 6143 return [a, b]; 6144 } 6145 6146 // if a,b is outside the clip window (trivial reject) 6147 if (codeA & codeB) { 6148 return false; 6149 } 6150 6151 // other cases 6152 codeOut = codeA || codeB; 6153 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6154 newCode = _getBitCode(p, bounds); 6155 6156 if (codeOut === codeA) { 6157 a = p; 6158 codeA = newCode; 6159 } else { 6160 b = p; 6161 codeB = newCode; 6162 } 6163 } 6164 } 6165 6166 function _getEdgeIntersection(a, b, code, bounds, round) { 6167 var dx = b.x - a.x, 6168 dy = b.y - a.y, 6169 min = bounds.min, 6170 max = bounds.max, 6171 x, y; 6172 6173 if (code & 8) { // top 6174 x = a.x + dx * (max.y - a.y) / dy; 6175 y = max.y; 6176 6177 } else if (code & 4) { // bottom 6178 x = a.x + dx * (min.y - a.y) / dy; 6179 y = min.y; 6180 6181 } else if (code & 2) { // right 6182 x = max.x; 6183 y = a.y + dy * (max.x - a.x) / dx; 6184 6185 } else if (code & 1) { // left 6186 x = min.x; 6187 y = a.y + dy * (min.x - a.x) / dx; 6188 } 6189 6190 return new Point(x, y, round); 6191 } 6192 6193 function _getBitCode(p, bounds) { 6194 var code = 0; 6195 6196 if (p.x < bounds.min.x) { // left 6197 code |= 1; 6198 } else if (p.x > bounds.max.x) { // right 6199 code |= 2; 6200 } 6201 6202 if (p.y < bounds.min.y) { // bottom 6203 code |= 4; 6204 } else if (p.y > bounds.max.y) { // top 6205 code |= 8; 6206 } 6207 6208 return code; 6209 } 6210 6211 // square distance (to avoid unnecessary Math.sqrt calls) 6212 function _sqDist(p1, p2) { 6213 var dx = p2.x - p1.x, 6214 dy = p2.y - p1.y; 6215 return dx * dx + dy * dy; 6216 } 6217 6218 // return closest point on segment or distance to that point 6219 function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6220 var x = p1.x, 6221 y = p1.y, 6222 dx = p2.x - x, 6223 dy = p2.y - y, 6224 dot = dx * dx + dy * dy, 6225 t; 6226 6227 if (dot > 0) { 6228 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6229 6230 if (t > 1) { 6231 x = p2.x; 6232 y = p2.y; 6233 } else if (t > 0) { 6234 x += dx * t; 6235 y += dy * t; 6236 } 6237 } 6238 6239 dx = p.x - x; 6240 dy = p.y - y; 6241 6242 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6243 } 6244 6245 6246 // @function isFlat(latlngs: LatLng[]): Boolean 6247 // Returns true if `latlngs` is a flat array, false is nested. 6248 function isFlat(latlngs) { 6249 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6250 } 6251 6252 function _flat(latlngs) { 6253 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6254 return isFlat(latlngs); 6255 } 6256 6257 6258 var LineUtil = (Object.freeze || Object)({ 6259 simplify: simplify, 6260 pointToSegmentDistance: pointToSegmentDistance, 6261 closestPointOnSegment: closestPointOnSegment, 6262 clipSegment: clipSegment, 6263 _getEdgeIntersection: _getEdgeIntersection, 6264 _getBitCode: _getBitCode, 6265 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6266 isFlat: isFlat, 6267 _flat: _flat 6268 }); 6269 6270 /* 6271 * @namespace PolyUtil 6272 * Various utility functions for polygon geometries. 6273 */ 6274 6275 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6276 * 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)). 6277 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6278 * performance. Note that polygon points needs different algorithm for clipping 6279 * than polyline, so there's a separate method for it. 6280 */ 6281 function clipPolygon(points, bounds, round) { 6282 var clippedPoints, 6283 edges = [1, 4, 2, 8], 6284 i, j, k, 6285 a, b, 6286 len, edge, p; 6287 6288 for (i = 0, len = points.length; i < len; i++) { 6289 points[i]._code = _getBitCode(points[i], bounds); 6290 } 6291 6292 // for each edge (left, bottom, right, top) 6293 for (k = 0; k < 4; k++) { 6294 edge = edges[k]; 6295 clippedPoints = []; 6296 6297 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6298 a = points[i]; 6299 b = points[j]; 6300 6301 // if a is inside the clip window 6302 if (!(a._code & edge)) { 6303 // if b is outside the clip window (a->b goes out of screen) 6304 if (b._code & edge) { 6305 p = _getEdgeIntersection(b, a, edge, bounds, round); 6306 p._code = _getBitCode(p, bounds); 6307 clippedPoints.push(p); 6308 } 6309 clippedPoints.push(a); 6310 6311 // else if b is inside the clip window (a->b enters the screen) 6312 } else if (!(b._code & edge)) { 6313 p = _getEdgeIntersection(b, a, edge, bounds, round); 6314 p._code = _getBitCode(p, bounds); 6315 clippedPoints.push(p); 6316 } 6317 } 6318 points = clippedPoints; 6319 } 6320 6321 return points; 6322 } 6323 6324 6325 var PolyUtil = (Object.freeze || Object)({ 6326 clipPolygon: clipPolygon 6327 }); 6328 6329 /* 6330 * @namespace Projection 6331 * @section 6332 * Leaflet comes with a set of already defined Projections out of the box: 6333 * 6334 * @projection L.Projection.LonLat 6335 * 6336 * Equirectangular, or Plate Carree projection — the most simple projection, 6337 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6338 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6339 * `EPSG:4326` and `Simple` CRS. 6340 */ 6341 6342 var LonLat = { 6343 project: function (latlng) { 6344 return new Point(latlng.lng, latlng.lat); 6345 }, 6346 6347 unproject: function (point) { 6348 return new LatLng(point.y, point.x); 6349 }, 6350 6351 bounds: new Bounds([-180, -90], [180, 90]) 6352 }; 6353 6354 /* 6355 * @namespace Projection 6356 * @projection L.Projection.Mercator 6357 * 6358 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS. 6359 */ 6360 6361 var Mercator = { 6362 R: 6378137, 6363 R_MINOR: 6356752.314245179, 6364 6365 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6366 6367 project: function (latlng) { 6368 var d = Math.PI / 180, 6369 r = this.R, 6370 y = latlng.lat * d, 6371 tmp = this.R_MINOR / r, 6372 e = Math.sqrt(1 - tmp * tmp), 6373 con = e * Math.sin(y); 6374 6375 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6376 y = -r * Math.log(Math.max(ts, 1E-10)); 6377 6378 return new Point(latlng.lng * d * r, y); 6379 }, 6380 6381 unproject: function (point) { 6382 var d = 180 / Math.PI, 6383 r = this.R, 6384 tmp = this.R_MINOR / r, 6385 e = Math.sqrt(1 - tmp * tmp), 6386 ts = Math.exp(-point.y / r), 6387 phi = Math.PI / 2 - 2 * Math.atan(ts); 6388 6389 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6390 con = e * Math.sin(phi); 6391 con = Math.pow((1 - con) / (1 + con), e / 2); 6392 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6393 phi += dphi; 6394 } 6395 6396 return new LatLng(phi * d, point.x * d / r); 6397 } 6398 }; 6399 6400 /* 6401 * @class Projection 6402 6403 * An object with methods for projecting geographical coordinates of the world onto 6404 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). 6405 6406 * @property bounds: Bounds 6407 * The bounds (specified in CRS units) where the projection is valid 6408 6409 * @method project(latlng: LatLng): Point 6410 * Projects geographical coordinates into a 2D point. 6411 * Only accepts actual `L.LatLng` instances, not arrays. 6412 6413 * @method unproject(point: Point): LatLng 6414 * The inverse of `project`. Projects a 2D point into a geographical location. 6415 * Only accepts actual `L.Point` instances, not arrays. 6416 6417 * Note that the projection instances do not inherit from Leafet's `Class` object, 6418 * and can't be instantiated. Also, new classes can't inherit from them, 6419 * and methods can't be added to them with the `include` function. 6420 6421 */ 6422 6423 6424 6425 6426 var index = (Object.freeze || Object)({ 6427 LonLat: LonLat, 6428 Mercator: Mercator, 6429 SphericalMercator: SphericalMercator 6430 }); 6431 6432 /* 6433 * @namespace CRS 6434 * @crs L.CRS.EPSG3395 6435 * 6436 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6437 */ 6438 var EPSG3395 = extend({}, Earth, { 6439 code: 'EPSG:3395', 6440 projection: Mercator, 6441 6442 transformation: (function () { 6443 var scale = 0.5 / (Math.PI * Mercator.R); 6444 return toTransformation(scale, 0.5, -scale, 0.5); 6445 }()) 6446 }); 6447 6448 /* 6449 * @namespace CRS 6450 * @crs L.CRS.EPSG4326 6451 * 6452 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6453 * 6454 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6455 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6456 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6457 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 6458 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6459 */ 6460 6461 var EPSG4326 = extend({}, Earth, { 6462 code: 'EPSG:4326', 6463 projection: LonLat, 6464 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 6465 }); 6466 6467 /* 6468 * @namespace CRS 6469 * @crs L.CRS.Simple 6470 * 6471 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6472 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6473 * axis should still be inverted (going from bottom to top). `distance()` returns 6474 * simple euclidean distance. 6475 */ 6476 6477 var Simple = extend({}, CRS, { 6478 projection: LonLat, 6479 transformation: toTransformation(1, 0, -1, 0), 6480 6481 scale: function (zoom) { 6482 return Math.pow(2, zoom); 6483 }, 6484 6485 zoom: function (scale) { 6486 return Math.log(scale) / Math.LN2; 6487 }, 6488 6489 distance: function (latlng1, latlng2) { 6490 var dx = latlng2.lng - latlng1.lng, 6491 dy = latlng2.lat - latlng1.lat; 6492 6493 return Math.sqrt(dx * dx + dy * dy); 6494 }, 6495 6496 infinite: true 6497 }); 6498 6499 CRS.Earth = Earth; 6500 CRS.EPSG3395 = EPSG3395; 6501 CRS.EPSG3857 = EPSG3857; 6502 CRS.EPSG900913 = EPSG900913; 6503 CRS.EPSG4326 = EPSG4326; 6504 CRS.Simple = Simple; 6505 6506 /* 6507 * @class Layer 6508 * @inherits Evented 6509 * @aka L.Layer 6510 * @aka ILayer 6511 * 6512 * A set of methods from the Layer base class that all Leaflet layers use. 6513 * Inherits all methods, options and events from `L.Evented`. 6514 * 6515 * @example 6516 * 6517 * ```js 6518 * var layer = L.marker(latlng).addTo(map); 6519 * layer.addTo(map); 6520 * layer.remove(); 6521 * ``` 6522 * 6523 * @event add: Event 6524 * Fired after the layer is added to a map 6525 * 6526 * @event remove: Event 6527 * Fired after the layer is removed from a map 6528 */ 6529 6530 6531 var Layer = Evented.extend({ 6532 6533 // Classes extending `L.Layer` will inherit the following options: 6534 options: { 6535 // @option pane: String = 'overlayPane' 6536 // 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. 6537 pane: 'overlayPane', 6538 6539 // @option attribution: String = null 6540 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers. 6541 attribution: null, 6542 6543 bubblingMouseEvents: true 6544 }, 6545 6546 /* @section 6547 * Classes extending `L.Layer` will inherit the following methods: 6548 * 6549 * @method addTo(map: Map|LayerGroup): this 6550 * Adds the layer to the given map or layer group. 6551 */ 6552 addTo: function (map) { 6553 map.addLayer(this); 6554 return this; 6555 }, 6556 6557 // @method remove: this 6558 // Removes the layer from the map it is currently active on. 6559 remove: function () { 6560 return this.removeFrom(this._map || this._mapToAdd); 6561 }, 6562 6563 // @method removeFrom(map: Map): this 6564 // Removes the layer from the given map 6565 removeFrom: function (obj) { 6566 if (obj) { 6567 obj.removeLayer(this); 6568 } 6569 return this; 6570 }, 6571 6572 // @method getPane(name? : String): HTMLElement 6573 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 6574 getPane: function (name) { 6575 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 6576 }, 6577 6578 addInteractiveTarget: function (targetEl) { 6579 this._map._targets[stamp(targetEl)] = this; 6580 return this; 6581 }, 6582 6583 removeInteractiveTarget: function (targetEl) { 6584 delete this._map._targets[stamp(targetEl)]; 6585 return this; 6586 }, 6587 6588 // @method getAttribution: String 6589 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 6590 getAttribution: function () { 6591 return this.options.attribution; 6592 }, 6593 6594 _layerAdd: function (e) { 6595 var map = e.target; 6596 6597 // check in case layer gets added and then removed before the map is ready 6598 if (!map.hasLayer(this)) { return; } 6599 6600 this._map = map; 6601 this._zoomAnimated = map._zoomAnimated; 6602 6603 if (this.getEvents) { 6604 var events = this.getEvents(); 6605 map.on(events, this); 6606 this.once('remove', function () { 6607 map.off(events, this); 6608 }, this); 6609 } 6610 6611 this.onAdd(map); 6612 6613 if (this.getAttribution && map.attributionControl) { 6614 map.attributionControl.addAttribution(this.getAttribution()); 6615 } 6616 6617 this.fire('add'); 6618 map.fire('layeradd', {layer: this}); 6619 } 6620 }); 6621 6622 /* @section Extension methods 6623 * @uninheritable 6624 * 6625 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 6626 * 6627 * @method onAdd(map: Map): this 6628 * 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). 6629 * 6630 * @method onRemove(map: Map): this 6631 * 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). 6632 * 6633 * @method getEvents(): Object 6634 * 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. 6635 * 6636 * @method getAttribution(): String 6637 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 6638 * 6639 * @method beforeAdd(map: Map): this 6640 * 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. 6641 */ 6642 6643 6644 /* @namespace Map 6645 * @section Layer events 6646 * 6647 * @event layeradd: LayerEvent 6648 * Fired when a new layer is added to the map. 6649 * 6650 * @event layerremove: LayerEvent 6651 * Fired when some layer is removed from the map 6652 * 6653 * @section Methods for Layers and Controls 6654 */ 6655 Map.include({ 6656 // @method addLayer(layer: Layer): this 6657 // Adds the given layer to the map 6658 addLayer: function (layer) { 6659 if (!layer._layerAdd) { 6660 throw new Error('The provided object is not a Layer.'); 6661 } 6662 6663 var id = stamp(layer); 6664 if (this._layers[id]) { return this; } 6665 this._layers[id] = layer; 6666 6667 layer._mapToAdd = this; 6668 6669 if (layer.beforeAdd) { 6670 layer.beforeAdd(this); 6671 } 6672 6673 this.whenReady(layer._layerAdd, layer); 6674 6675 return this; 6676 }, 6677 6678 // @method removeLayer(layer: Layer): this 6679 // Removes the given layer from the map. 6680 removeLayer: function (layer) { 6681 var id = stamp(layer); 6682 6683 if (!this._layers[id]) { return this; } 6684 6685 if (this._loaded) { 6686 layer.onRemove(this); 6687 } 6688 6689 if (layer.getAttribution && this.attributionControl) { 6690 this.attributionControl.removeAttribution(layer.getAttribution()); 6691 } 6692 6693 delete this._layers[id]; 6694 6695 if (this._loaded) { 6696 this.fire('layerremove', {layer: layer}); 6697 layer.fire('remove'); 6698 } 6699 6700 layer._map = layer._mapToAdd = null; 6701 6702 return this; 6703 }, 6704 6705 // @method hasLayer(layer: Layer): Boolean 6706 // Returns `true` if the given layer is currently added to the map 6707 hasLayer: function (layer) { 6708 return !!layer && (stamp(layer) in this._layers); 6709 }, 6710 6711 /* @method eachLayer(fn: Function, context?: Object): this 6712 * Iterates over the layers of the map, optionally specifying context of the iterator function. 6713 * ``` 6714 * map.eachLayer(function(layer){ 6715 * layer.bindPopup('Hello'); 6716 * }); 6717 * ``` 6718 */ 6719 eachLayer: function (method, context) { 6720 for (var i in this._layers) { 6721 method.call(context, this._layers[i]); 6722 } 6723 return this; 6724 }, 6725 6726 _addLayers: function (layers) { 6727 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 6728 6729 for (var i = 0, len = layers.length; i < len; i++) { 6730 this.addLayer(layers[i]); 6731 } 6732 }, 6733 6734 _addZoomLimit: function (layer) { 6735 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 6736 this._zoomBoundLayers[stamp(layer)] = layer; 6737 this._updateZoomLevels(); 6738 } 6739 }, 6740 6741 _removeZoomLimit: function (layer) { 6742 var id = stamp(layer); 6743 6744 if (this._zoomBoundLayers[id]) { 6745 delete this._zoomBoundLayers[id]; 6746 this._updateZoomLevels(); 6747 } 6748 }, 6749 6750 _updateZoomLevels: function () { 6751 var minZoom = Infinity, 6752 maxZoom = -Infinity, 6753 oldZoomSpan = this._getZoomSpan(); 6754 6755 for (var i in this._zoomBoundLayers) { 6756 var options = this._zoomBoundLayers[i].options; 6757 6758 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 6759 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 6760 } 6761 6762 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 6763 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 6764 6765 // @section Map state change events 6766 // @event zoomlevelschange: Event 6767 // Fired when the number of zoomlevels on the map is changed due 6768 // to adding or removing a layer. 6769 if (oldZoomSpan !== this._getZoomSpan()) { 6770 this.fire('zoomlevelschange'); 6771 } 6772 6773 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 6774 this.setZoom(this._layersMaxZoom); 6775 } 6776 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 6777 this.setZoom(this._layersMinZoom); 6778 } 6779 } 6780 }); 6781 6782 /* 6783 * @class LayerGroup 6784 * @aka L.LayerGroup 6785 * @inherits Layer 6786 * 6787 * Used to group several layers and handle them as one. If you add it to the map, 6788 * any layers added or removed from the group will be added/removed on the map as 6789 * well. Extends `Layer`. 6790 * 6791 * @example 6792 * 6793 * ```js 6794 * L.layerGroup([marker1, marker2]) 6795 * .addLayer(polyline) 6796 * .addTo(map); 6797 * ``` 6798 */ 6799 6800 var LayerGroup = Layer.extend({ 6801 6802 initialize: function (layers, options) { 6803 setOptions(this, options); 6804 6805 this._layers = {}; 6806 6807 var i, len; 6808 6809 if (layers) { 6810 for (i = 0, len = layers.length; i < len; i++) { 6811 this.addLayer(layers[i]); 6812 } 6813 } 6814 }, 6815 6816 // @method addLayer(layer: Layer): this 6817 // Adds the given layer to the group. 6818 addLayer: function (layer) { 6819 var id = this.getLayerId(layer); 6820 6821 this._layers[id] = layer; 6822 6823 if (this._map) { 6824 this._map.addLayer(layer); 6825 } 6826 6827 return this; 6828 }, 6829 6830 // @method removeLayer(layer: Layer): this 6831 // Removes the given layer from the group. 6832 // @alternative 6833 // @method removeLayer(id: Number): this 6834 // Removes the layer with the given internal ID from the group. 6835 removeLayer: function (layer) { 6836 var id = layer in this._layers ? layer : this.getLayerId(layer); 6837 6838 if (this._map && this._layers[id]) { 6839 this._map.removeLayer(this._layers[id]); 6840 } 6841 6842 delete this._layers[id]; 6843 6844 return this; 6845 }, 6846 6847 // @method hasLayer(layer: Layer): Boolean 6848 // Returns `true` if the given layer is currently added to the group. 6849 // @alternative 6850 // @method hasLayer(id: Number): Boolean 6851 // Returns `true` if the given internal ID is currently added to the group. 6852 hasLayer: function (layer) { 6853 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); 6854 }, 6855 6856 // @method clearLayers(): this 6857 // Removes all the layers from the group. 6858 clearLayers: function () { 6859 return this.eachLayer(this.removeLayer, this); 6860 }, 6861 6862 // @method invoke(methodName: String, …): this 6863 // Calls `methodName` on every layer contained in this group, passing any 6864 // additional parameters. Has no effect if the layers contained do not 6865 // implement `methodName`. 6866 invoke: function (methodName) { 6867 var args = Array.prototype.slice.call(arguments, 1), 6868 i, layer; 6869 6870 for (i in this._layers) { 6871 layer = this._layers[i]; 6872 6873 if (layer[methodName]) { 6874 layer[methodName].apply(layer, args); 6875 } 6876 } 6877 6878 return this; 6879 }, 6880 6881 onAdd: function (map) { 6882 this.eachLayer(map.addLayer, map); 6883 }, 6884 6885 onRemove: function (map) { 6886 this.eachLayer(map.removeLayer, map); 6887 }, 6888 6889 // @method eachLayer(fn: Function, context?: Object): this 6890 // Iterates over the layers of the group, optionally specifying context of the iterator function. 6891 // ```js 6892 // group.eachLayer(function (layer) { 6893 // layer.bindPopup('Hello'); 6894 // }); 6895 // ``` 6896 eachLayer: function (method, context) { 6897 for (var i in this._layers) { 6898 method.call(context, this._layers[i]); 6899 } 6900 return this; 6901 }, 6902 6903 // @method getLayer(id: Number): Layer 6904 // Returns the layer with the given internal ID. 6905 getLayer: function (id) { 6906 return this._layers[id]; 6907 }, 6908 6909 // @method getLayers(): Layer[] 6910 // Returns an array of all the layers added to the group. 6911 getLayers: function () { 6912 var layers = []; 6913 this.eachLayer(layers.push, layers); 6914 return layers; 6915 }, 6916 6917 // @method setZIndex(zIndex: Number): this 6918 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 6919 setZIndex: function (zIndex) { 6920 return this.invoke('setZIndex', zIndex); 6921 }, 6922 6923 // @method getLayerId(layer: Layer): Number 6924 // Returns the internal ID for a layer 6925 getLayerId: function (layer) { 6926 return stamp(layer); 6927 } 6928 }); 6929 6930 6931 // @factory L.layerGroup(layers?: Layer[], options?: Object) 6932 // Create a layer group, optionally given an initial set of layers and an `options` object. 6933 var layerGroup = function (layers, options) { 6934 return new LayerGroup(layers, options); 6935 }; 6936 6937 /* 6938 * @class FeatureGroup 6939 * @aka L.FeatureGroup 6940 * @inherits LayerGroup 6941 * 6942 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 6943 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 6944 * * Events are propagated to the `FeatureGroup`, so if the group has an event 6945 * handler, it will handle events from any of the layers. This includes mouse events 6946 * and custom events. 6947 * * Has `layeradd` and `layerremove` events 6948 * 6949 * @example 6950 * 6951 * ```js 6952 * L.featureGroup([marker1, marker2, polyline]) 6953 * .bindPopup('Hello world!') 6954 * .on('click', function() { alert('Clicked on a member of the group!'); }) 6955 * .addTo(map); 6956 * ``` 6957 */ 6958 6959 var FeatureGroup = LayerGroup.extend({ 6960 6961 addLayer: function (layer) { 6962 if (this.hasLayer(layer)) { 6963 return this; 6964 } 6965 6966 layer.addEventParent(this); 6967 6968 LayerGroup.prototype.addLayer.call(this, layer); 6969 6970 // @event layeradd: LayerEvent 6971 // Fired when a layer is added to this `FeatureGroup` 6972 return this.fire('layeradd', {layer: layer}); 6973 }, 6974 6975 removeLayer: function (layer) { 6976 if (!this.hasLayer(layer)) { 6977 return this; 6978 } 6979 if (layer in this._layers) { 6980 layer = this._layers[layer]; 6981 } 6982 6983 layer.removeEventParent(this); 6984 6985 LayerGroup.prototype.removeLayer.call(this, layer); 6986 6987 // @event layerremove: LayerEvent 6988 // Fired when a layer is removed from this `FeatureGroup` 6989 return this.fire('layerremove', {layer: layer}); 6990 }, 6991 6992 // @method setStyle(style: Path options): this 6993 // Sets the given path options to each layer of the group that has a `setStyle` method. 6994 setStyle: function (style) { 6995 return this.invoke('setStyle', style); 6996 }, 6997 6998 // @method bringToFront(): this 6999 // Brings the layer group to the top of all other layers 7000 bringToFront: function () { 7001 return this.invoke('bringToFront'); 7002 }, 7003 7004 // @method bringToBack(): this 7005 // Brings the layer group to the back of all other layers 7006 bringToBack: function () { 7007 return this.invoke('bringToBack'); 7008 }, 7009 7010 // @method getBounds(): LatLngBounds 7011 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 7012 getBounds: function () { 7013 var bounds = new LatLngBounds(); 7014 7015 for (var id in this._layers) { 7016 var layer = this._layers[id]; 7017 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 7018 } 7019 return bounds; 7020 } 7021 }); 7022 7023 // @factory L.featureGroup(layers: Layer[]) 7024 // Create a feature group, optionally given an initial set of layers. 7025 var featureGroup = function (layers) { 7026 return new FeatureGroup(layers); 7027 }; 7028 7029 /* 7030 * @class Icon 7031 * @aka L.Icon 7032 * 7033 * Represents an icon to provide when creating a marker. 7034 * 7035 * @example 7036 * 7037 * ```js 7038 * var myIcon = L.icon({ 7039 * iconUrl: 'my-icon.png', 7040 * iconRetinaUrl: 'my-icon@2x.png', 7041 * iconSize: [38, 95], 7042 * iconAnchor: [22, 94], 7043 * popupAnchor: [-3, -76], 7044 * shadowUrl: 'my-icon-shadow.png', 7045 * shadowRetinaUrl: 'my-icon-shadow@2x.png', 7046 * shadowSize: [68, 95], 7047 * shadowAnchor: [22, 94] 7048 * }); 7049 * 7050 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 7051 * ``` 7052 * 7053 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 7054 * 7055 */ 7056 7057 var Icon = Class.extend({ 7058 7059 /* @section 7060 * @aka Icon options 7061 * 7062 * @option iconUrl: String = null 7063 * **(required)** The URL to the icon image (absolute or relative to your script path). 7064 * 7065 * @option iconRetinaUrl: String = null 7066 * The URL to a retina sized version of the icon image (absolute or relative to your 7067 * script path). Used for Retina screen devices. 7068 * 7069 * @option iconSize: Point = null 7070 * Size of the icon image in pixels. 7071 * 7072 * @option iconAnchor: Point = null 7073 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 7074 * will be aligned so that this point is at the marker's geographical location. Centered 7075 * by default if size is specified, also can be set in CSS with negative margins. 7076 * 7077 * @option popupAnchor: Point = [0, 0] 7078 * The coordinates of the point from which popups will "open", relative to the icon anchor. 7079 * 7080 * @option tooltipAnchor: Point = [0, 0] 7081 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7082 * 7083 * @option shadowUrl: String = null 7084 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7085 * 7086 * @option shadowRetinaUrl: String = null 7087 * 7088 * @option shadowSize: Point = null 7089 * Size of the shadow image in pixels. 7090 * 7091 * @option shadowAnchor: Point = null 7092 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7093 * as iconAnchor if not specified). 7094 * 7095 * @option className: String = '' 7096 * A custom class name to assign to both icon and shadow images. Empty by default. 7097 */ 7098 7099 options: { 7100 popupAnchor: [0, 0], 7101 tooltipAnchor: [0, 0] 7102 }, 7103 7104 initialize: function (options) { 7105 setOptions(this, options); 7106 }, 7107 7108 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7109 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7110 // styled according to the options. 7111 createIcon: function (oldIcon) { 7112 return this._createIcon('icon', oldIcon); 7113 }, 7114 7115 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7116 // As `createIcon`, but for the shadow beneath it. 7117 createShadow: function (oldIcon) { 7118 return this._createIcon('shadow', oldIcon); 7119 }, 7120 7121 _createIcon: function (name, oldIcon) { 7122 var src = this._getIconUrl(name); 7123 7124 if (!src) { 7125 if (name === 'icon') { 7126 throw new Error('iconUrl not set in Icon options (see the docs).'); 7127 } 7128 return null; 7129 } 7130 7131 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7132 this._setIconStyles(img, name); 7133 7134 return img; 7135 }, 7136 7137 _setIconStyles: function (img, name) { 7138 var options = this.options; 7139 var sizeOption = options[name + 'Size']; 7140 7141 if (typeof sizeOption === 'number') { 7142 sizeOption = [sizeOption, sizeOption]; 7143 } 7144 7145 var size = toPoint(sizeOption), 7146 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7147 size && size.divideBy(2, true)); 7148 7149 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7150 7151 if (anchor) { 7152 img.style.marginLeft = (-anchor.x) + 'px'; 7153 img.style.marginTop = (-anchor.y) + 'px'; 7154 } 7155 7156 if (size) { 7157 img.style.width = size.x + 'px'; 7158 img.style.height = size.y + 'px'; 7159 } 7160 }, 7161 7162 _createImg: function (src, el) { 7163 el = el || document.createElement('img'); 7164 el.src = src; 7165 return el; 7166 }, 7167 7168 _getIconUrl: function (name) { 7169 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7170 } 7171 }); 7172 7173 7174 // @factory L.icon(options: Icon options) 7175 // Creates an icon instance with the given options. 7176 function icon(options) { 7177 return new Icon(options); 7178 } 7179 7180 /* 7181 * @miniclass Icon.Default (Icon) 7182 * @aka L.Icon.Default 7183 * @section 7184 * 7185 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7186 * no icon is specified. Points to the blue marker image distributed with Leaflet 7187 * releases. 7188 * 7189 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7190 * (which is a set of `Icon options`). 7191 * 7192 * If you want to _completely_ replace the default icon, override the 7193 * `L.Marker.prototype.options.icon` with your own icon instead. 7194 */ 7195 7196 var IconDefault = Icon.extend({ 7197 7198 options: { 7199 iconUrl: 'marker-icon.png', 7200 iconRetinaUrl: 'marker-icon-2x.png', 7201 shadowUrl: 'marker-shadow.png', 7202 iconSize: [25, 41], 7203 iconAnchor: [12, 41], 7204 popupAnchor: [1, -34], 7205 tooltipAnchor: [16, -28], 7206 shadowSize: [41, 41] 7207 }, 7208 7209 _getIconUrl: function (name) { 7210 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only 7211 IconDefault.imagePath = this._detectIconPath(); 7212 } 7213 7214 // @option imagePath: String 7215 // `Icon.Default` will try to auto-detect the location of the 7216 // blue icon images. If you are placing these images in a non-standard 7217 // way, set this option to point to the right path. 7218 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7219 }, 7220 7221 _detectIconPath: function () { 7222 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7223 var path = getStyle(el, 'background-image') || 7224 getStyle(el, 'backgroundImage'); // IE8 7225 7226 document.body.removeChild(el); 7227 7228 if (path === null || path.indexOf('url') !== 0) { 7229 path = ''; 7230 } else { 7231 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, ''); 7232 } 7233 7234 return path; 7235 } 7236 }); 7237 7238 /* 7239 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7240 */ 7241 7242 7243 /* @namespace Marker 7244 * @section Interaction handlers 7245 * 7246 * 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: 7247 * 7248 * ```js 7249 * marker.dragging.disable(); 7250 * ``` 7251 * 7252 * @property dragging: Handler 7253 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7254 */ 7255 7256 var MarkerDrag = Handler.extend({ 7257 initialize: function (marker) { 7258 this._marker = marker; 7259 }, 7260 7261 addHooks: function () { 7262 var icon = this._marker._icon; 7263 7264 if (!this._draggable) { 7265 this._draggable = new Draggable(icon, icon, true); 7266 } 7267 7268 this._draggable.on({ 7269 dragstart: this._onDragStart, 7270 predrag: this._onPreDrag, 7271 drag: this._onDrag, 7272 dragend: this._onDragEnd 7273 }, this).enable(); 7274 7275 addClass(icon, 'leaflet-marker-draggable'); 7276 }, 7277 7278 removeHooks: function () { 7279 this._draggable.off({ 7280 dragstart: this._onDragStart, 7281 predrag: this._onPreDrag, 7282 drag: this._onDrag, 7283 dragend: this._onDragEnd 7284 }, this).disable(); 7285 7286 if (this._marker._icon) { 7287 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7288 } 7289 }, 7290 7291 moved: function () { 7292 return this._draggable && this._draggable._moved; 7293 }, 7294 7295 _adjustPan: function (e) { 7296 var marker = this._marker, 7297 map = marker._map, 7298 speed = this._marker.options.autoPanSpeed, 7299 padding = this._marker.options.autoPanPadding, 7300 iconPos = getPosition(marker._icon), 7301 bounds = map.getPixelBounds(), 7302 origin = map.getPixelOrigin(); 7303 7304 var panBounds = toBounds( 7305 bounds.min._subtract(origin).add(padding), 7306 bounds.max._subtract(origin).subtract(padding) 7307 ); 7308 7309 if (!panBounds.contains(iconPos)) { 7310 // Compute incremental movement 7311 var movement = toPoint( 7312 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7313 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7314 7315 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7316 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7317 ).multiplyBy(speed); 7318 7319 map.panBy(movement, {animate: false}); 7320 7321 this._draggable._newPos._add(movement); 7322 this._draggable._startPos._add(movement); 7323 7324 setPosition(marker._icon, this._draggable._newPos); 7325 this._onDrag(e); 7326 7327 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7328 } 7329 }, 7330 7331 _onDragStart: function () { 7332 // @section Dragging events 7333 // @event dragstart: Event 7334 // Fired when the user starts dragging the marker. 7335 7336 // @event movestart: Event 7337 // Fired when the marker starts moving (because of dragging). 7338 7339 this._oldLatLng = this._marker.getLatLng(); 7340 this._marker 7341 .closePopup() 7342 .fire('movestart') 7343 .fire('dragstart'); 7344 }, 7345 7346 _onPreDrag: function (e) { 7347 if (this._marker.options.autoPan) { 7348 cancelAnimFrame(this._panRequest); 7349 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7350 } 7351 }, 7352 7353 _onDrag: function (e) { 7354 var marker = this._marker, 7355 shadow = marker._shadow, 7356 iconPos = getPosition(marker._icon), 7357 latlng = marker._map.layerPointToLatLng(iconPos); 7358 7359 // update shadow position 7360 if (shadow) { 7361 setPosition(shadow, iconPos); 7362 } 7363 7364 marker._latlng = latlng; 7365 e.latlng = latlng; 7366 e.oldLatLng = this._oldLatLng; 7367 7368 // @event drag: Event 7369 // Fired repeatedly while the user drags the marker. 7370 marker 7371 .fire('move', e) 7372 .fire('drag', e); 7373 }, 7374 7375 _onDragEnd: function (e) { 7376 // @event dragend: DragEndEvent 7377 // Fired when the user stops dragging the marker. 7378 7379 cancelAnimFrame(this._panRequest); 7380 7381 // @event moveend: Event 7382 // Fired when the marker stops moving (because of dragging). 7383 delete this._oldLatLng; 7384 this._marker 7385 .fire('moveend') 7386 .fire('dragend', e); 7387 } 7388 }); 7389 7390 /* 7391 * @class Marker 7392 * @inherits Interactive layer 7393 * @aka L.Marker 7394 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7395 * 7396 * @example 7397 * 7398 * ```js 7399 * L.marker([50.5, 30.5]).addTo(map); 7400 * ``` 7401 */ 7402 7403 var Marker = Layer.extend({ 7404 7405 // @section 7406 // @aka Marker options 7407 options: { 7408 // @option icon: Icon = * 7409 // Icon instance to use for rendering the marker. 7410 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7411 // If not specified, a common instance of `L.Icon.Default` is used. 7412 icon: new IconDefault(), 7413 7414 // Option inherited from "Interactive layer" abstract class 7415 interactive: true, 7416 7417 // @option keyboard: Boolean = true 7418 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7419 keyboard: true, 7420 7421 // @option title: String = '' 7422 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7423 title: '', 7424 7425 // @option alt: String = '' 7426 // Text for the `alt` attribute of the icon image (useful for accessibility). 7427 alt: '', 7428 7429 // @option zIndexOffset: Number = 0 7430 // 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). 7431 zIndexOffset: 0, 7432 7433 // @option opacity: Number = 1.0 7434 // The opacity of the marker. 7435 opacity: 1, 7436 7437 // @option riseOnHover: Boolean = false 7438 // If `true`, the marker will get on top of others when you hover the mouse over it. 7439 riseOnHover: false, 7440 7441 // @option riseOffset: Number = 250 7442 // The z-index offset used for the `riseOnHover` feature. 7443 riseOffset: 250, 7444 7445 // @option pane: String = 'markerPane' 7446 // `Map pane` where the markers icon will be added. 7447 pane: 'markerPane', 7448 7449 // @option pane: String = 'shadowPane' 7450 // `Map pane` where the markers shadow will be added. 7451 shadowPane: 'shadowPane', 7452 7453 // @option bubblingMouseEvents: Boolean = false 7454 // When `true`, a mouse event on this marker will trigger the same event on the map 7455 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7456 bubblingMouseEvents: false, 7457 7458 // @section Draggable marker options 7459 // @option draggable: Boolean = false 7460 // Whether the marker is draggable with mouse/touch or not. 7461 draggable: false, 7462 7463 // @option autoPan: Boolean = false 7464 // Whether to pan the map when dragging this marker near its edge or not. 7465 autoPan: false, 7466 7467 // @option autoPanPadding: Point = Point(50, 50) 7468 // Distance (in pixels to the left/right and to the top/bottom) of the 7469 // map edge to start panning the map. 7470 autoPanPadding: [50, 50], 7471 7472 // @option autoPanSpeed: Number = 10 7473 // Number of pixels the map should pan by. 7474 autoPanSpeed: 10 7475 }, 7476 7477 /* @section 7478 * 7479 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 7480 */ 7481 7482 initialize: function (latlng, options) { 7483 setOptions(this, options); 7484 this._latlng = toLatLng(latlng); 7485 }, 7486 7487 onAdd: function (map) { 7488 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 7489 7490 if (this._zoomAnimated) { 7491 map.on('zoomanim', this._animateZoom, this); 7492 } 7493 7494 this._initIcon(); 7495 this.update(); 7496 }, 7497 7498 onRemove: function (map) { 7499 if (this.dragging && this.dragging.enabled()) { 7500 this.options.draggable = true; 7501 this.dragging.removeHooks(); 7502 } 7503 delete this.dragging; 7504 7505 if (this._zoomAnimated) { 7506 map.off('zoomanim', this._animateZoom, this); 7507 } 7508 7509 this._removeIcon(); 7510 this._removeShadow(); 7511 }, 7512 7513 getEvents: function () { 7514 return { 7515 zoom: this.update, 7516 viewreset: this.update 7517 }; 7518 }, 7519 7520 // @method getLatLng: LatLng 7521 // Returns the current geographical position of the marker. 7522 getLatLng: function () { 7523 return this._latlng; 7524 }, 7525 7526 // @method setLatLng(latlng: LatLng): this 7527 // Changes the marker position to the given point. 7528 setLatLng: function (latlng) { 7529 var oldLatLng = this._latlng; 7530 this._latlng = toLatLng(latlng); 7531 this.update(); 7532 7533 // @event move: Event 7534 // 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`. 7535 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7536 }, 7537 7538 // @method setZIndexOffset(offset: Number): this 7539 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 7540 setZIndexOffset: function (offset) { 7541 this.options.zIndexOffset = offset; 7542 return this.update(); 7543 }, 7544 7545 // @method getIcon: Icon 7546 // Returns the current icon used by the marker 7547 getIcon: function () { 7548 return this.options.icon; 7549 }, 7550 7551 // @method setIcon(icon: Icon): this 7552 // Changes the marker icon. 7553 setIcon: function (icon) { 7554 7555 this.options.icon = icon; 7556 7557 if (this._map) { 7558 this._initIcon(); 7559 this.update(); 7560 } 7561 7562 if (this._popup) { 7563 this.bindPopup(this._popup, this._popup.options); 7564 } 7565 7566 return this; 7567 }, 7568 7569 getElement: function () { 7570 return this._icon; 7571 }, 7572 7573 update: function () { 7574 7575 if (this._icon && this._map) { 7576 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 7577 this._setPos(pos); 7578 } 7579 7580 return this; 7581 }, 7582 7583 _initIcon: function () { 7584 var options = this.options, 7585 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7586 7587 var icon = options.icon.createIcon(this._icon), 7588 addIcon = false; 7589 7590 // if we're not reusing the icon, remove the old one and init new one 7591 if (icon !== this._icon) { 7592 if (this._icon) { 7593 this._removeIcon(); 7594 } 7595 addIcon = true; 7596 7597 if (options.title) { 7598 icon.title = options.title; 7599 } 7600 7601 if (icon.tagName === 'IMG') { 7602 icon.alt = options.alt || ''; 7603 } 7604 } 7605 7606 addClass(icon, classToAdd); 7607 7608 if (options.keyboard) { 7609 icon.tabIndex = '0'; 7610 } 7611 7612 this._icon = icon; 7613 7614 if (options.riseOnHover) { 7615 this.on({ 7616 mouseover: this._bringToFront, 7617 mouseout: this._resetZIndex 7618 }); 7619 } 7620 7621 var newShadow = options.icon.createShadow(this._shadow), 7622 addShadow = false; 7623 7624 if (newShadow !== this._shadow) { 7625 this._removeShadow(); 7626 addShadow = true; 7627 } 7628 7629 if (newShadow) { 7630 addClass(newShadow, classToAdd); 7631 newShadow.alt = ''; 7632 } 7633 this._shadow = newShadow; 7634 7635 7636 if (options.opacity < 1) { 7637 this._updateOpacity(); 7638 } 7639 7640 7641 if (addIcon) { 7642 this.getPane().appendChild(this._icon); 7643 } 7644 this._initInteraction(); 7645 if (newShadow && addShadow) { 7646 this.getPane(options.shadowPane).appendChild(this._shadow); 7647 } 7648 }, 7649 7650 _removeIcon: function () { 7651 if (this.options.riseOnHover) { 7652 this.off({ 7653 mouseover: this._bringToFront, 7654 mouseout: this._resetZIndex 7655 }); 7656 } 7657 7658 remove(this._icon); 7659 this.removeInteractiveTarget(this._icon); 7660 7661 this._icon = null; 7662 }, 7663 7664 _removeShadow: function () { 7665 if (this._shadow) { 7666 remove(this._shadow); 7667 } 7668 this._shadow = null; 7669 }, 7670 7671 _setPos: function (pos) { 7672 7673 if (this._icon) { 7674 setPosition(this._icon, pos); 7675 } 7676 7677 if (this._shadow) { 7678 setPosition(this._shadow, pos); 7679 } 7680 7681 this._zIndex = pos.y + this.options.zIndexOffset; 7682 7683 this._resetZIndex(); 7684 }, 7685 7686 _updateZIndex: function (offset) { 7687 if (this._icon) { 7688 this._icon.style.zIndex = this._zIndex + offset; 7689 } 7690 }, 7691 7692 _animateZoom: function (opt) { 7693 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 7694 7695 this._setPos(pos); 7696 }, 7697 7698 _initInteraction: function () { 7699 7700 if (!this.options.interactive) { return; } 7701 7702 addClass(this._icon, 'leaflet-interactive'); 7703 7704 this.addInteractiveTarget(this._icon); 7705 7706 if (MarkerDrag) { 7707 var draggable = this.options.draggable; 7708 if (this.dragging) { 7709 draggable = this.dragging.enabled(); 7710 this.dragging.disable(); 7711 } 7712 7713 this.dragging = new MarkerDrag(this); 7714 7715 if (draggable) { 7716 this.dragging.enable(); 7717 } 7718 } 7719 }, 7720 7721 // @method setOpacity(opacity: Number): this 7722 // Changes the opacity of the marker. 7723 setOpacity: function (opacity) { 7724 this.options.opacity = opacity; 7725 if (this._map) { 7726 this._updateOpacity(); 7727 } 7728 7729 return this; 7730 }, 7731 7732 _updateOpacity: function () { 7733 var opacity = this.options.opacity; 7734 7735 if (this._icon) { 7736 setOpacity(this._icon, opacity); 7737 } 7738 7739 if (this._shadow) { 7740 setOpacity(this._shadow, opacity); 7741 } 7742 }, 7743 7744 _bringToFront: function () { 7745 this._updateZIndex(this.options.riseOffset); 7746 }, 7747 7748 _resetZIndex: function () { 7749 this._updateZIndex(0); 7750 }, 7751 7752 _getPopupAnchor: function () { 7753 return this.options.icon.options.popupAnchor; 7754 }, 7755 7756 _getTooltipAnchor: function () { 7757 return this.options.icon.options.tooltipAnchor; 7758 } 7759 }); 7760 7761 7762 // factory L.marker(latlng: LatLng, options? : Marker options) 7763 7764 // @factory L.marker(latlng: LatLng, options? : Marker options) 7765 // Instantiates a Marker object given a geographical point and optionally an options object. 7766 function marker(latlng, options) { 7767 return new Marker(latlng, options); 7768 } 7769 7770 /* 7771 * @class Path 7772 * @aka L.Path 7773 * @inherits Interactive layer 7774 * 7775 * An abstract class that contains options and constants shared between vector 7776 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 7777 */ 7778 7779 var Path = Layer.extend({ 7780 7781 // @section 7782 // @aka Path options 7783 options: { 7784 // @option stroke: Boolean = true 7785 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 7786 stroke: true, 7787 7788 // @option color: String = '#3388ff' 7789 // Stroke color 7790 color: '#3388ff', 7791 7792 // @option weight: Number = 3 7793 // Stroke width in pixels 7794 weight: 3, 7795 7796 // @option opacity: Number = 1.0 7797 // Stroke opacity 7798 opacity: 1, 7799 7800 // @option lineCap: String= 'round' 7801 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 7802 lineCap: 'round', 7803 7804 // @option lineJoin: String = 'round' 7805 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 7806 lineJoin: 'round', 7807 7808 // @option dashArray: String = null 7809 // 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). 7810 dashArray: null, 7811 7812 // @option dashOffset: String = null 7813 // 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). 7814 dashOffset: null, 7815 7816 // @option fill: Boolean = depends 7817 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 7818 fill: false, 7819 7820 // @option fillColor: String = * 7821 // Fill color. Defaults to the value of the [`color`](#path-color) option 7822 fillColor: null, 7823 7824 // @option fillOpacity: Number = 0.2 7825 // Fill opacity. 7826 fillOpacity: 0.2, 7827 7828 // @option fillRule: String = 'evenodd' 7829 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 7830 fillRule: 'evenodd', 7831 7832 // className: '', 7833 7834 // Option inherited from "Interactive layer" abstract class 7835 interactive: true, 7836 7837 // @option bubblingMouseEvents: Boolean = true 7838 // When `true`, a mouse event on this path will trigger the same event on the map 7839 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7840 bubblingMouseEvents: true 7841 }, 7842 7843 beforeAdd: function (map) { 7844 // Renderer is set here because we need to call renderer.getEvents 7845 // before this.getEvents. 7846 this._renderer = map.getRenderer(this); 7847 }, 7848 7849 onAdd: function () { 7850 this._renderer._initPath(this); 7851 this._reset(); 7852 this._renderer._addPath(this); 7853 }, 7854 7855 onRemove: function () { 7856 this._renderer._removePath(this); 7857 }, 7858 7859 // @method redraw(): this 7860 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 7861 redraw: function () { 7862 if (this._map) { 7863 this._renderer._updatePath(this); 7864 } 7865 return this; 7866 }, 7867 7868 // @method setStyle(style: Path options): this 7869 // Changes the appearance of a Path based on the options in the `Path options` object. 7870 setStyle: function (style) { 7871 setOptions(this, style); 7872 if (this._renderer) { 7873 this._renderer._updateStyle(this); 7874 if (this.options.stroke && style && style.hasOwnProperty('weight')) { 7875 this._updateBounds(); 7876 } 7877 } 7878 return this; 7879 }, 7880 7881 // @method bringToFront(): this 7882 // Brings the layer to the top of all path layers. 7883 bringToFront: function () { 7884 if (this._renderer) { 7885 this._renderer._bringToFront(this); 7886 } 7887 return this; 7888 }, 7889 7890 // @method bringToBack(): this 7891 // Brings the layer to the bottom of all path layers. 7892 bringToBack: function () { 7893 if (this._renderer) { 7894 this._renderer._bringToBack(this); 7895 } 7896 return this; 7897 }, 7898 7899 getElement: function () { 7900 return this._path; 7901 }, 7902 7903 _reset: function () { 7904 // defined in child classes 7905 this._project(); 7906 this._update(); 7907 }, 7908 7909 _clickTolerance: function () { 7910 // used when doing hit detection for Canvas layers 7911 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance; 7912 } 7913 }); 7914 7915 /* 7916 * @class CircleMarker 7917 * @aka L.CircleMarker 7918 * @inherits Path 7919 * 7920 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 7921 */ 7922 7923 var CircleMarker = Path.extend({ 7924 7925 // @section 7926 // @aka CircleMarker options 7927 options: { 7928 fill: true, 7929 7930 // @option radius: Number = 10 7931 // Radius of the circle marker, in pixels 7932 radius: 10 7933 }, 7934 7935 initialize: function (latlng, options) { 7936 setOptions(this, options); 7937 this._latlng = toLatLng(latlng); 7938 this._radius = this.options.radius; 7939 }, 7940 7941 // @method setLatLng(latLng: LatLng): this 7942 // Sets the position of a circle marker to a new location. 7943 setLatLng: function (latlng) { 7944 var oldLatLng = this._latlng; 7945 this._latlng = toLatLng(latlng); 7946 this.redraw(); 7947 7948 // @event move: Event 7949 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 7950 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7951 }, 7952 7953 // @method getLatLng(): LatLng 7954 // Returns the current geographical position of the circle marker 7955 getLatLng: function () { 7956 return this._latlng; 7957 }, 7958 7959 // @method setRadius(radius: Number): this 7960 // Sets the radius of a circle marker. Units are in pixels. 7961 setRadius: function (radius) { 7962 this.options.radius = this._radius = radius; 7963 return this.redraw(); 7964 }, 7965 7966 // @method getRadius(): Number 7967 // Returns the current radius of the circle 7968 getRadius: function () { 7969 return this._radius; 7970 }, 7971 7972 setStyle : function (options) { 7973 var radius = options && options.radius || this._radius; 7974 Path.prototype.setStyle.call(this, options); 7975 this.setRadius(radius); 7976 return this; 7977 }, 7978 7979 _project: function () { 7980 this._point = this._map.latLngToLayerPoint(this._latlng); 7981 this._updateBounds(); 7982 }, 7983 7984 _updateBounds: function () { 7985 var r = this._radius, 7986 r2 = this._radiusY || r, 7987 w = this._clickTolerance(), 7988 p = [r + w, r2 + w]; 7989 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 7990 }, 7991 7992 _update: function () { 7993 if (this._map) { 7994 this._updatePath(); 7995 } 7996 }, 7997 7998 _updatePath: function () { 7999 this._renderer._updateCircle(this); 8000 }, 8001 8002 _empty: function () { 8003 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 8004 }, 8005 8006 // Needed by the `Canvas` renderer for interactivity 8007 _containsPoint: function (p) { 8008 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 8009 } 8010 }); 8011 8012 8013 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 8014 // Instantiates a circle marker object given a geographical point, and an optional options object. 8015 function circleMarker(latlng, options) { 8016 return new CircleMarker(latlng, options); 8017 } 8018 8019 /* 8020 * @class Circle 8021 * @aka L.Circle 8022 * @inherits CircleMarker 8023 * 8024 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 8025 * 8026 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 8027 * 8028 * @example 8029 * 8030 * ```js 8031 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 8032 * ``` 8033 */ 8034 8035 var Circle = CircleMarker.extend({ 8036 8037 initialize: function (latlng, options, legacyOptions) { 8038 if (typeof options === 'number') { 8039 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 8040 options = extend({}, legacyOptions, {radius: options}); 8041 } 8042 setOptions(this, options); 8043 this._latlng = toLatLng(latlng); 8044 8045 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 8046 8047 // @section 8048 // @aka Circle options 8049 // @option radius: Number; Radius of the circle, in meters. 8050 this._mRadius = this.options.radius; 8051 }, 8052 8053 // @method setRadius(radius: Number): this 8054 // Sets the radius of a circle. Units are in meters. 8055 setRadius: function (radius) { 8056 this._mRadius = radius; 8057 return this.redraw(); 8058 }, 8059 8060 // @method getRadius(): Number 8061 // Returns the current radius of a circle. Units are in meters. 8062 getRadius: function () { 8063 return this._mRadius; 8064 }, 8065 8066 // @method getBounds(): LatLngBounds 8067 // Returns the `LatLngBounds` of the path. 8068 getBounds: function () { 8069 var half = [this._radius, this._radiusY || this._radius]; 8070 8071 return new LatLngBounds( 8072 this._map.layerPointToLatLng(this._point.subtract(half)), 8073 this._map.layerPointToLatLng(this._point.add(half))); 8074 }, 8075 8076 setStyle: Path.prototype.setStyle, 8077 8078 _project: function () { 8079 8080 var lng = this._latlng.lng, 8081 lat = this._latlng.lat, 8082 map = this._map, 8083 crs = map.options.crs; 8084 8085 if (crs.distance === Earth.distance) { 8086 var d = Math.PI / 180, 8087 latR = (this._mRadius / Earth.R) / d, 8088 top = map.project([lat + latR, lng]), 8089 bottom = map.project([lat - latR, lng]), 8090 p = top.add(bottom).divideBy(2), 8091 lat2 = map.unproject(p).lat, 8092 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8093 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8094 8095 if (isNaN(lngR) || lngR === 0) { 8096 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8097 } 8098 8099 this._point = p.subtract(map.getPixelOrigin()); 8100 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 8101 this._radiusY = p.y - top.y; 8102 8103 } else { 8104 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8105 8106 this._point = map.latLngToLayerPoint(this._latlng); 8107 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8108 } 8109 8110 this._updateBounds(); 8111 } 8112 }); 8113 8114 // @factory L.circle(latlng: LatLng, options?: Circle options) 8115 // Instantiates a circle object given a geographical point, and an options object 8116 // which contains the circle radius. 8117 // @alternative 8118 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8119 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8120 // Do not use in new applications or plugins. 8121 function circle(latlng, options, legacyOptions) { 8122 return new Circle(latlng, options, legacyOptions); 8123 } 8124 8125 /* 8126 * @class Polyline 8127 * @aka L.Polyline 8128 * @inherits Path 8129 * 8130 * A class for drawing polyline overlays on a map. Extends `Path`. 8131 * 8132 * @example 8133 * 8134 * ```js 8135 * // create a red polyline from an array of LatLng points 8136 * var latlngs = [ 8137 * [45.51, -122.68], 8138 * [37.77, -122.43], 8139 * [34.04, -118.2] 8140 * ]; 8141 * 8142 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8143 * 8144 * // zoom the map to the polyline 8145 * map.fitBounds(polyline.getBounds()); 8146 * ``` 8147 * 8148 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8149 * 8150 * ```js 8151 * // create a red polyline from an array of arrays of LatLng points 8152 * var latlngs = [ 8153 * [[45.51, -122.68], 8154 * [37.77, -122.43], 8155 * [34.04, -118.2]], 8156 * [[40.78, -73.91], 8157 * [41.83, -87.62], 8158 * [32.76, -96.72]] 8159 * ]; 8160 * ``` 8161 */ 8162 8163 8164 var Polyline = Path.extend({ 8165 8166 // @section 8167 // @aka Polyline options 8168 options: { 8169 // @option smoothFactor: Number = 1.0 8170 // How much to simplify the polyline on each zoom level. More means 8171 // better performance and smoother look, and less means more accurate representation. 8172 smoothFactor: 1.0, 8173 8174 // @option noClip: Boolean = false 8175 // Disable polyline clipping. 8176 noClip: false 8177 }, 8178 8179 initialize: function (latlngs, options) { 8180 setOptions(this, options); 8181 this._setLatLngs(latlngs); 8182 }, 8183 8184 // @method getLatLngs(): LatLng[] 8185 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8186 getLatLngs: function () { 8187 return this._latlngs; 8188 }, 8189 8190 // @method setLatLngs(latlngs: LatLng[]): this 8191 // Replaces all the points in the polyline with the given array of geographical points. 8192 setLatLngs: function (latlngs) { 8193 this._setLatLngs(latlngs); 8194 return this.redraw(); 8195 }, 8196 8197 // @method isEmpty(): Boolean 8198 // Returns `true` if the Polyline has no LatLngs. 8199 isEmpty: function () { 8200 return !this._latlngs.length; 8201 }, 8202 8203 // @method closestLayerPoint(p: Point): Point 8204 // Returns the point closest to `p` on the Polyline. 8205 closestLayerPoint: function (p) { 8206 var minDistance = Infinity, 8207 minPoint = null, 8208 closest = _sqClosestPointOnSegment, 8209 p1, p2; 8210 8211 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8212 var points = this._parts[j]; 8213 8214 for (var i = 1, len = points.length; i < len; i++) { 8215 p1 = points[i - 1]; 8216 p2 = points[i]; 8217 8218 var sqDist = closest(p, p1, p2, true); 8219 8220 if (sqDist < minDistance) { 8221 minDistance = sqDist; 8222 minPoint = closest(p, p1, p2); 8223 } 8224 } 8225 } 8226 if (minPoint) { 8227 minPoint.distance = Math.sqrt(minDistance); 8228 } 8229 return minPoint; 8230 }, 8231 8232 // @method getCenter(): LatLng 8233 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline. 8234 getCenter: function () { 8235 // throws error when not yet added to map as this center calculation requires projected coordinates 8236 if (!this._map) { 8237 throw new Error('Must add layer to map before using getCenter()'); 8238 } 8239 8240 var i, halfDist, segDist, dist, p1, p2, ratio, 8241 points = this._rings[0], 8242 len = points.length; 8243 8244 if (!len) { return null; } 8245 8246 // polyline centroid algorithm; only uses the first ring if there are multiple 8247 8248 for (i = 0, halfDist = 0; i < len - 1; i++) { 8249 halfDist += points[i].distanceTo(points[i + 1]) / 2; 8250 } 8251 8252 // The line is so small in the current view that all points are on the same pixel. 8253 if (halfDist === 0) { 8254 return this._map.layerPointToLatLng(points[0]); 8255 } 8256 8257 for (i = 0, dist = 0; i < len - 1; i++) { 8258 p1 = points[i]; 8259 p2 = points[i + 1]; 8260 segDist = p1.distanceTo(p2); 8261 dist += segDist; 8262 8263 if (dist > halfDist) { 8264 ratio = (dist - halfDist) / segDist; 8265 return this._map.layerPointToLatLng([ 8266 p2.x - ratio * (p2.x - p1.x), 8267 p2.y - ratio * (p2.y - p1.y) 8268 ]); 8269 } 8270 } 8271 }, 8272 8273 // @method getBounds(): LatLngBounds 8274 // Returns the `LatLngBounds` of the path. 8275 getBounds: function () { 8276 return this._bounds; 8277 }, 8278 8279 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this 8280 // Adds a given point to the polyline. By default, adds to the first ring of 8281 // the polyline in case of a multi-polyline, but can be overridden by passing 8282 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8283 addLatLng: function (latlng, latlngs) { 8284 latlngs = latlngs || this._defaultShape(); 8285 latlng = toLatLng(latlng); 8286 latlngs.push(latlng); 8287 this._bounds.extend(latlng); 8288 return this.redraw(); 8289 }, 8290 8291 _setLatLngs: function (latlngs) { 8292 this._bounds = new LatLngBounds(); 8293 this._latlngs = this._convertLatLngs(latlngs); 8294 }, 8295 8296 _defaultShape: function () { 8297 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8298 }, 8299 8300 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8301 _convertLatLngs: function (latlngs) { 8302 var result = [], 8303 flat = isFlat(latlngs); 8304 8305 for (var i = 0, len = latlngs.length; i < len; i++) { 8306 if (flat) { 8307 result[i] = toLatLng(latlngs[i]); 8308 this._bounds.extend(result[i]); 8309 } else { 8310 result[i] = this._convertLatLngs(latlngs[i]); 8311 } 8312 } 8313 8314 return result; 8315 }, 8316 8317 _project: function () { 8318 var pxBounds = new Bounds(); 8319 this._rings = []; 8320 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8321 8322 if (this._bounds.isValid() && pxBounds.isValid()) { 8323 this._rawPxBounds = pxBounds; 8324 this._updateBounds(); 8325 } 8326 }, 8327 8328 _updateBounds: function () { 8329 var w = this._clickTolerance(), 8330 p = new Point(w, w); 8331 this._pxBounds = new Bounds([ 8332 this._rawPxBounds.min.subtract(p), 8333 this._rawPxBounds.max.add(p) 8334 ]); 8335 }, 8336 8337 // recursively turns latlngs into a set of rings with projected coordinates 8338 _projectLatlngs: function (latlngs, result, projectedBounds) { 8339 var flat = latlngs[0] instanceof LatLng, 8340 len = latlngs.length, 8341 i, ring; 8342 8343 if (flat) { 8344 ring = []; 8345 for (i = 0; i < len; i++) { 8346 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8347 projectedBounds.extend(ring[i]); 8348 } 8349 result.push(ring); 8350 } else { 8351 for (i = 0; i < len; i++) { 8352 this._projectLatlngs(latlngs[i], result, projectedBounds); 8353 } 8354 } 8355 }, 8356 8357 // clip polyline by renderer bounds so that we have less to render for performance 8358 _clipPoints: function () { 8359 var bounds = this._renderer._bounds; 8360 8361 this._parts = []; 8362 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8363 return; 8364 } 8365 8366 if (this.options.noClip) { 8367 this._parts = this._rings; 8368 return; 8369 } 8370 8371 var parts = this._parts, 8372 i, j, k, len, len2, segment, points; 8373 8374 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8375 points = this._rings[i]; 8376 8377 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8378 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8379 8380 if (!segment) { continue; } 8381 8382 parts[k] = parts[k] || []; 8383 parts[k].push(segment[0]); 8384 8385 // if segment goes out of screen, or it's the last one, it's the end of the line part 8386 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8387 parts[k].push(segment[1]); 8388 k++; 8389 } 8390 } 8391 } 8392 }, 8393 8394 // simplify each clipped part of the polyline for performance 8395 _simplifyPoints: function () { 8396 var parts = this._parts, 8397 tolerance = this.options.smoothFactor; 8398 8399 for (var i = 0, len = parts.length; i < len; i++) { 8400 parts[i] = simplify(parts[i], tolerance); 8401 } 8402 }, 8403 8404 _update: function () { 8405 if (!this._map) { return; } 8406 8407 this._clipPoints(); 8408 this._simplifyPoints(); 8409 this._updatePath(); 8410 }, 8411 8412 _updatePath: function () { 8413 this._renderer._updatePoly(this); 8414 }, 8415 8416 // Needed by the `Canvas` renderer for interactivity 8417 _containsPoint: function (p, closed) { 8418 var i, j, k, len, len2, part, 8419 w = this._clickTolerance(); 8420 8421 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8422 8423 // hit detection for polylines 8424 for (i = 0, len = this._parts.length; i < len; i++) { 8425 part = this._parts[i]; 8426 8427 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8428 if (!closed && (j === 0)) { continue; } 8429 8430 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8431 return true; 8432 } 8433 } 8434 } 8435 return false; 8436 } 8437 }); 8438 8439 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8440 // Instantiates a polyline object given an array of geographical points and 8441 // optionally an options object. You can create a `Polyline` object with 8442 // multiple separate lines (`MultiPolyline`) by passing an array of arrays 8443 // of geographic points. 8444 function polyline(latlngs, options) { 8445 return new Polyline(latlngs, options); 8446 } 8447 8448 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8449 Polyline._flat = _flat; 8450 8451 /* 8452 * @class Polygon 8453 * @aka L.Polygon 8454 * @inherits Polyline 8455 * 8456 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8457 * 8458 * 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. 8459 * 8460 * 8461 * @example 8462 * 8463 * ```js 8464 * // create a red polygon from an array of LatLng points 8465 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 8466 * 8467 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8468 * 8469 * // zoom the map to the polygon 8470 * map.fitBounds(polygon.getBounds()); 8471 * ``` 8472 * 8473 * 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: 8474 * 8475 * ```js 8476 * var latlngs = [ 8477 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8478 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8479 * ]; 8480 * ``` 8481 * 8482 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8483 * 8484 * ```js 8485 * var latlngs = [ 8486 * [ // first polygon 8487 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8488 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8489 * ], 8490 * [ // second polygon 8491 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 8492 * ] 8493 * ]; 8494 * ``` 8495 */ 8496 8497 var Polygon = Polyline.extend({ 8498 8499 options: { 8500 fill: true 8501 }, 8502 8503 isEmpty: function () { 8504 return !this._latlngs.length || !this._latlngs[0].length; 8505 }, 8506 8507 getCenter: function () { 8508 // throws error when not yet added to map as this center calculation requires projected coordinates 8509 if (!this._map) { 8510 throw new Error('Must add layer to map before using getCenter()'); 8511 } 8512 8513 var i, j, p1, p2, f, area, x, y, center, 8514 points = this._rings[0], 8515 len = points.length; 8516 8517 if (!len) { return null; } 8518 8519 // polygon centroid algorithm; only uses the first ring if there are multiple 8520 8521 area = x = y = 0; 8522 8523 for (i = 0, j = len - 1; i < len; j = i++) { 8524 p1 = points[i]; 8525 p2 = points[j]; 8526 8527 f = p1.y * p2.x - p2.y * p1.x; 8528 x += (p1.x + p2.x) * f; 8529 y += (p1.y + p2.y) * f; 8530 area += f * 3; 8531 } 8532 8533 if (area === 0) { 8534 // Polygon is so small that all points are on same pixel. 8535 center = points[0]; 8536 } else { 8537 center = [x / area, y / area]; 8538 } 8539 return this._map.layerPointToLatLng(center); 8540 }, 8541 8542 _convertLatLngs: function (latlngs) { 8543 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 8544 len = result.length; 8545 8546 // remove last point if it equals first one 8547 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 8548 result.pop(); 8549 } 8550 return result; 8551 }, 8552 8553 _setLatLngs: function (latlngs) { 8554 Polyline.prototype._setLatLngs.call(this, latlngs); 8555 if (isFlat(this._latlngs)) { 8556 this._latlngs = [this._latlngs]; 8557 } 8558 }, 8559 8560 _defaultShape: function () { 8561 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8562 }, 8563 8564 _clipPoints: function () { 8565 // polygons need a different clipping algorithm so we redefine that 8566 8567 var bounds = this._renderer._bounds, 8568 w = this.options.weight, 8569 p = new Point(w, w); 8570 8571 // increase clip padding by stroke width to avoid stroke on clip edges 8572 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8573 8574 this._parts = []; 8575 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8576 return; 8577 } 8578 8579 if (this.options.noClip) { 8580 this._parts = this._rings; 8581 return; 8582 } 8583 8584 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8585 clipped = clipPolygon(this._rings[i], bounds, true); 8586 if (clipped.length) { 8587 this._parts.push(clipped); 8588 } 8589 } 8590 }, 8591 8592 _updatePath: function () { 8593 this._renderer._updatePoly(this, true); 8594 }, 8595 8596 // Needed by the `Canvas` renderer for interactivity 8597 _containsPoint: function (p) { 8598 var inside = false, 8599 part, p1, p2, i, j, k, len, len2; 8600 8601 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8602 8603 // ray casting algorithm for detecting if point is in polygon 8604 for (i = 0, len = this._parts.length; i < len; i++) { 8605 part = this._parts[i]; 8606 8607 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8608 p1 = part[j]; 8609 p2 = part[k]; 8610 8611 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)) { 8612 inside = !inside; 8613 } 8614 } 8615 } 8616 8617 // also check if it's on polygon stroke 8618 return inside || Polyline.prototype._containsPoint.call(this, p, true); 8619 } 8620 8621 }); 8622 8623 8624 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8625 function polygon(latlngs, options) { 8626 return new Polygon(latlngs, options); 8627 } 8628 8629 /* 8630 * @class GeoJSON 8631 * @aka L.GeoJSON 8632 * @inherits FeatureGroup 8633 * 8634 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 8635 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 8636 * 8637 * @example 8638 * 8639 * ```js 8640 * L.geoJSON(data, { 8641 * style: function (feature) { 8642 * return {color: feature.properties.color}; 8643 * } 8644 * }).bindPopup(function (layer) { 8645 * return layer.feature.properties.description; 8646 * }).addTo(map); 8647 * ``` 8648 */ 8649 8650 var GeoJSON = FeatureGroup.extend({ 8651 8652 /* @section 8653 * @aka GeoJSON options 8654 * 8655 * @option pointToLayer: Function = * 8656 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 8657 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 8658 * The default is to spawn a default `Marker`: 8659 * ```js 8660 * function(geoJsonPoint, latlng) { 8661 * return L.marker(latlng); 8662 * } 8663 * ``` 8664 * 8665 * @option style: Function = * 8666 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, 8667 * called internally when data is added. 8668 * The default value is to not override any defaults: 8669 * ```js 8670 * function (geoJsonFeature) { 8671 * return {} 8672 * } 8673 * ``` 8674 * 8675 * @option onEachFeature: Function = * 8676 * A `Function` that will be called once for each created `Feature`, after it has 8677 * been created and styled. Useful for attaching events and popups to features. 8678 * The default is to do nothing with the newly created layers: 8679 * ```js 8680 * function (feature, layer) {} 8681 * ``` 8682 * 8683 * @option filter: Function = * 8684 * A `Function` that will be used to decide whether to include a feature or not. 8685 * The default is to include all features: 8686 * ```js 8687 * function (geoJsonFeature) { 8688 * return true; 8689 * } 8690 * ``` 8691 * Note: dynamically changing the `filter` option will have effect only on newly 8692 * added data. It will _not_ re-evaluate already included features. 8693 * 8694 * @option coordsToLatLng: Function = * 8695 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 8696 * The default is the `coordsToLatLng` static method. 8697 * 8698 * @option markersInheritOptions: Boolean = false 8699 * Whether default Markers for "Point" type Features inherit from group options. 8700 */ 8701 8702 initialize: function (geojson, options) { 8703 setOptions(this, options); 8704 8705 this._layers = {}; 8706 8707 if (geojson) { 8708 this.addData(geojson); 8709 } 8710 }, 8711 8712 // @method addData( <GeoJSON> data ): this 8713 // Adds a GeoJSON object to the layer. 8714 addData: function (geojson) { 8715 var features = isArray(geojson) ? geojson : geojson.features, 8716 i, len, feature; 8717 8718 if (features) { 8719 for (i = 0, len = features.length; i < len; i++) { 8720 // only add this if geometry or geometries are set and not null 8721 feature = features[i]; 8722 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 8723 this.addData(feature); 8724 } 8725 } 8726 return this; 8727 } 8728 8729 var options = this.options; 8730 8731 if (options.filter && !options.filter(geojson)) { return this; } 8732 8733 var layer = geometryToLayer(geojson, options); 8734 if (!layer) { 8735 return this; 8736 } 8737 layer.feature = asFeature(geojson); 8738 8739 layer.defaultOptions = layer.options; 8740 this.resetStyle(layer); 8741 8742 if (options.onEachFeature) { 8743 options.onEachFeature(geojson, layer); 8744 } 8745 8746 return this.addLayer(layer); 8747 }, 8748 8749 // @method resetStyle( <Path> layer? ): this 8750 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 8751 // If `layer` is omitted, the style of all features in the current layer is reset. 8752 resetStyle: function (layer) { 8753 if (layer === undefined) { 8754 return this.eachLayer(this.resetStyle, this); 8755 } 8756 // reset any custom styles 8757 layer.options = extend({}, layer.defaultOptions); 8758 this._setLayerStyle(layer, this.options.style); 8759 return this; 8760 }, 8761 8762 // @method setStyle( <Function> style ): this 8763 // Changes styles of GeoJSON vector layers with the given style function. 8764 setStyle: function (style) { 8765 return this.eachLayer(function (layer) { 8766 this._setLayerStyle(layer, style); 8767 }, this); 8768 }, 8769 8770 _setLayerStyle: function (layer, style) { 8771 if (layer.setStyle) { 8772 if (typeof style === 'function') { 8773 style = style(layer.feature); 8774 } 8775 layer.setStyle(style); 8776 } 8777 } 8778 }); 8779 8780 // @section 8781 // There are several static functions which can be called without instantiating L.GeoJSON: 8782 8783 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 8784 // Creates a `Layer` from a given GeoJSON feature. Can use a custom 8785 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 8786 // functions if provided as options. 8787 function geometryToLayer(geojson, options) { 8788 8789 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 8790 coords = geometry ? geometry.coordinates : null, 8791 layers = [], 8792 pointToLayer = options && options.pointToLayer, 8793 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 8794 latlng, latlngs, i, len; 8795 8796 if (!coords && !geometry) { 8797 return null; 8798 } 8799 8800 switch (geometry.type) { 8801 case 'Point': 8802 latlng = _coordsToLatLng(coords); 8803 return _pointToLayer(pointToLayer, geojson, latlng, options); 8804 8805 case 'MultiPoint': 8806 for (i = 0, len = coords.length; i < len; i++) { 8807 latlng = _coordsToLatLng(coords[i]); 8808 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 8809 } 8810 return new FeatureGroup(layers); 8811 8812 case 'LineString': 8813 case 'MultiLineString': 8814 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 8815 return new Polyline(latlngs, options); 8816 8817 case 'Polygon': 8818 case 'MultiPolygon': 8819 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 8820 return new Polygon(latlngs, options); 8821 8822 case 'GeometryCollection': 8823 for (i = 0, len = geometry.geometries.length; i < len; i++) { 8824 var layer = geometryToLayer({ 8825 geometry: geometry.geometries[i], 8826 type: 'Feature', 8827 properties: geojson.properties 8828 }, options); 8829 8830 if (layer) { 8831 layers.push(layer); 8832 } 8833 } 8834 return new FeatureGroup(layers); 8835 8836 default: 8837 throw new Error('Invalid GeoJSON object.'); 8838 } 8839 } 8840 8841 function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 8842 return pointToLayerFn ? 8843 pointToLayerFn(geojson, latlng) : 8844 new Marker(latlng, options && options.markersInheritOptions && options); 8845 } 8846 8847 // @function coordsToLatLng(coords: Array): LatLng 8848 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 8849 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 8850 function coordsToLatLng(coords) { 8851 return new LatLng(coords[1], coords[0], coords[2]); 8852 } 8853 8854 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 8855 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 8856 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 8857 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 8858 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 8859 var latlngs = []; 8860 8861 for (var i = 0, len = coords.length, latlng; i < len; i++) { 8862 latlng = levelsDeep ? 8863 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 8864 (_coordsToLatLng || coordsToLatLng)(coords[i]); 8865 8866 latlngs.push(latlng); 8867 } 8868 8869 return latlngs; 8870 } 8871 8872 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array 8873 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 8874 function latLngToCoords(latlng, precision) { 8875 precision = typeof precision === 'number' ? precision : 6; 8876 return latlng.alt !== undefined ? 8877 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 8878 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 8879 } 8880 8881 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array 8882 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 8883 // `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. 8884 function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 8885 var coords = []; 8886 8887 for (var i = 0, len = latlngs.length; i < len; i++) { 8888 coords.push(levelsDeep ? 8889 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : 8890 latLngToCoords(latlngs[i], precision)); 8891 } 8892 8893 if (!levelsDeep && closed) { 8894 coords.push(coords[0]); 8895 } 8896 8897 return coords; 8898 } 8899 8900 function getFeature(layer, newGeometry) { 8901 return layer.feature ? 8902 extend({}, layer.feature, {geometry: newGeometry}) : 8903 asFeature(newGeometry); 8904 } 8905 8906 // @function asFeature(geojson: Object): Object 8907 // Normalize GeoJSON geometries/features into GeoJSON features. 8908 function asFeature(geojson) { 8909 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 8910 return geojson; 8911 } 8912 8913 return { 8914 type: 'Feature', 8915 properties: {}, 8916 geometry: geojson 8917 }; 8918 } 8919 8920 var PointToGeoJSON = { 8921 toGeoJSON: function (precision) { 8922 return getFeature(this, { 8923 type: 'Point', 8924 coordinates: latLngToCoords(this.getLatLng(), precision) 8925 }); 8926 } 8927 }; 8928 8929 // @namespace Marker 8930 // @section Other methods 8931 // @method toGeoJSON(precision?: Number): Object 8932 // `precision` is the number of decimal places for coordinates. 8933 // The default value is 6 places. 8934 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 8935 Marker.include(PointToGeoJSON); 8936 8937 // @namespace CircleMarker 8938 // @method toGeoJSON(precision?: Number): Object 8939 // `precision` is the number of decimal places for coordinates. 8940 // The default value is 6 places. 8941 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 8942 Circle.include(PointToGeoJSON); 8943 CircleMarker.include(PointToGeoJSON); 8944 8945 8946 // @namespace Polyline 8947 // @method toGeoJSON(precision?: Number): Object 8948 // `precision` is the number of decimal places for coordinates. 8949 // The default value is 6 places. 8950 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 8951 Polyline.include({ 8952 toGeoJSON: function (precision) { 8953 var multi = !isFlat(this._latlngs); 8954 8955 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 8956 8957 return getFeature(this, { 8958 type: (multi ? 'Multi' : '') + 'LineString', 8959 coordinates: coords 8960 }); 8961 } 8962 }); 8963 8964 // @namespace Polygon 8965 // @method toGeoJSON(precision?: Number): Object 8966 // `precision` is the number of decimal places for coordinates. 8967 // The default value is 6 places. 8968 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 8969 Polygon.include({ 8970 toGeoJSON: function (precision) { 8971 var holes = !isFlat(this._latlngs), 8972 multi = holes && !isFlat(this._latlngs[0]); 8973 8974 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 8975 8976 if (!holes) { 8977 coords = [coords]; 8978 } 8979 8980 return getFeature(this, { 8981 type: (multi ? 'Multi' : '') + 'Polygon', 8982 coordinates: coords 8983 }); 8984 } 8985 }); 8986 8987 8988 // @namespace LayerGroup 8989 LayerGroup.include({ 8990 toMultiPoint: function (precision) { 8991 var coords = []; 8992 8993 this.eachLayer(function (layer) { 8994 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 8995 }); 8996 8997 return getFeature(this, { 8998 type: 'MultiPoint', 8999 coordinates: coords 9000 }); 9001 }, 9002 9003 // @method toGeoJSON(precision?: Number): Object 9004 // `precision` is the number of decimal places for coordinates. 9005 // The default value is 6 places. 9006 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 9007 toGeoJSON: function (precision) { 9008 9009 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 9010 9011 if (type === 'MultiPoint') { 9012 return this.toMultiPoint(precision); 9013 } 9014 9015 var isGeometryCollection = type === 'GeometryCollection', 9016 jsons = []; 9017 9018 this.eachLayer(function (layer) { 9019 if (layer.toGeoJSON) { 9020 var json = layer.toGeoJSON(precision); 9021 if (isGeometryCollection) { 9022 jsons.push(json.geometry); 9023 } else { 9024 var feature = asFeature(json); 9025 // Squash nested feature collections 9026 if (feature.type === 'FeatureCollection') { 9027 jsons.push.apply(jsons, feature.features); 9028 } else { 9029 jsons.push(feature); 9030 } 9031 } 9032 } 9033 }); 9034 9035 if (isGeometryCollection) { 9036 return getFeature(this, { 9037 geometries: jsons, 9038 type: 'GeometryCollection' 9039 }); 9040 } 9041 9042 return { 9043 type: 'FeatureCollection', 9044 features: jsons 9045 }; 9046 } 9047 }); 9048 9049 // @namespace GeoJSON 9050 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9051 // Creates a GeoJSON layer. Optionally accepts an object in 9052 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 9053 // (you can alternatively add it later with `addData` method) and an `options` object. 9054 function geoJSON(geojson, options) { 9055 return new GeoJSON(geojson, options); 9056 } 9057 9058 // Backward compatibility. 9059 var geoJson = geoJSON; 9060 9061 /* 9062 * @class ImageOverlay 9063 * @aka L.ImageOverlay 9064 * @inherits Interactive layer 9065 * 9066 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 9067 * 9068 * @example 9069 * 9070 * ```js 9071 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 9072 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 9073 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 9074 * ``` 9075 */ 9076 9077 var ImageOverlay = Layer.extend({ 9078 9079 // @section 9080 // @aka ImageOverlay options 9081 options: { 9082 // @option opacity: Number = 1.0 9083 // The opacity of the image overlay. 9084 opacity: 1, 9085 9086 // @option alt: String = '' 9087 // Text for the `alt` attribute of the image (useful for accessibility). 9088 alt: '', 9089 9090 // @option interactive: Boolean = false 9091 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 9092 interactive: false, 9093 9094 // @option crossOrigin: Boolean|String = false 9095 // Whether the crossOrigin attribute will be added to the image. 9096 // 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. 9097 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 9098 crossOrigin: false, 9099 9100 // @option errorOverlayUrl: String = '' 9101 // URL to the overlay image to show in place of the overlay that failed to load. 9102 errorOverlayUrl: '', 9103 9104 // @option zIndex: Number = 1 9105 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 9106 zIndex: 1, 9107 9108 // @option className: String = '' 9109 // A custom class name to assign to the image. Empty by default. 9110 className: '' 9111 }, 9112 9113 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 9114 this._url = url; 9115 this._bounds = toLatLngBounds(bounds); 9116 9117 setOptions(this, options); 9118 }, 9119 9120 onAdd: function () { 9121 if (!this._image) { 9122 this._initImage(); 9123 9124 if (this.options.opacity < 1) { 9125 this._updateOpacity(); 9126 } 9127 } 9128 9129 if (this.options.interactive) { 9130 addClass(this._image, 'leaflet-interactive'); 9131 this.addInteractiveTarget(this._image); 9132 } 9133 9134 this.getPane().appendChild(this._image); 9135 this._reset(); 9136 }, 9137 9138 onRemove: function () { 9139 remove(this._image); 9140 if (this.options.interactive) { 9141 this.removeInteractiveTarget(this._image); 9142 } 9143 }, 9144 9145 // @method setOpacity(opacity: Number): this 9146 // Sets the opacity of the overlay. 9147 setOpacity: function (opacity) { 9148 this.options.opacity = opacity; 9149 9150 if (this._image) { 9151 this._updateOpacity(); 9152 } 9153 return this; 9154 }, 9155 9156 setStyle: function (styleOpts) { 9157 if (styleOpts.opacity) { 9158 this.setOpacity(styleOpts.opacity); 9159 } 9160 return this; 9161 }, 9162 9163 // @method bringToFront(): this 9164 // Brings the layer to the top of all overlays. 9165 bringToFront: function () { 9166 if (this._map) { 9167 toFront(this._image); 9168 } 9169 return this; 9170 }, 9171 9172 // @method bringToBack(): this 9173 // Brings the layer to the bottom of all overlays. 9174 bringToBack: function () { 9175 if (this._map) { 9176 toBack(this._image); 9177 } 9178 return this; 9179 }, 9180 9181 // @method setUrl(url: String): this 9182 // Changes the URL of the image. 9183 setUrl: function (url) { 9184 this._url = url; 9185 9186 if (this._image) { 9187 this._image.src = url; 9188 } 9189 return this; 9190 }, 9191 9192 // @method setBounds(bounds: LatLngBounds): this 9193 // Update the bounds that this ImageOverlay covers 9194 setBounds: function (bounds) { 9195 this._bounds = toLatLngBounds(bounds); 9196 9197 if (this._map) { 9198 this._reset(); 9199 } 9200 return this; 9201 }, 9202 9203 getEvents: function () { 9204 var events = { 9205 zoom: this._reset, 9206 viewreset: this._reset 9207 }; 9208 9209 if (this._zoomAnimated) { 9210 events.zoomanim = this._animateZoom; 9211 } 9212 9213 return events; 9214 }, 9215 9216 // @method setZIndex(value: Number): this 9217 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9218 setZIndex: function (value) { 9219 this.options.zIndex = value; 9220 this._updateZIndex(); 9221 return this; 9222 }, 9223 9224 // @method getBounds(): LatLngBounds 9225 // Get the bounds that this ImageOverlay covers 9226 getBounds: function () { 9227 return this._bounds; 9228 }, 9229 9230 // @method getElement(): HTMLElement 9231 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9232 // used by this overlay. 9233 getElement: function () { 9234 return this._image; 9235 }, 9236 9237 _initImage: function () { 9238 var wasElementSupplied = this._url.tagName === 'IMG'; 9239 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9240 9241 addClass(img, 'leaflet-image-layer'); 9242 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9243 if (this.options.className) { addClass(img, this.options.className); } 9244 9245 img.onselectstart = falseFn; 9246 img.onmousemove = falseFn; 9247 9248 // @event load: Event 9249 // Fired when the ImageOverlay layer has loaded its image 9250 img.onload = bind(this.fire, this, 'load'); 9251 img.onerror = bind(this._overlayOnError, this, 'error'); 9252 9253 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9254 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9255 } 9256 9257 if (this.options.zIndex) { 9258 this._updateZIndex(); 9259 } 9260 9261 if (wasElementSupplied) { 9262 this._url = img.src; 9263 return; 9264 } 9265 9266 img.src = this._url; 9267 img.alt = this.options.alt; 9268 }, 9269 9270 _animateZoom: function (e) { 9271 var scale = this._map.getZoomScale(e.zoom), 9272 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9273 9274 setTransform(this._image, offset, scale); 9275 }, 9276 9277 _reset: function () { 9278 var image = this._image, 9279 bounds = new Bounds( 9280 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9281 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9282 size = bounds.getSize(); 9283 9284 setPosition(image, bounds.min); 9285 9286 image.style.width = size.x + 'px'; 9287 image.style.height = size.y + 'px'; 9288 }, 9289 9290 _updateOpacity: function () { 9291 setOpacity(this._image, this.options.opacity); 9292 }, 9293 9294 _updateZIndex: function () { 9295 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9296 this._image.style.zIndex = this.options.zIndex; 9297 } 9298 }, 9299 9300 _overlayOnError: function () { 9301 // @event error: Event 9302 // Fired when the ImageOverlay layer fails to load its image 9303 this.fire('error'); 9304 9305 var errorUrl = this.options.errorOverlayUrl; 9306 if (errorUrl && this._url !== errorUrl) { 9307 this._url = errorUrl; 9308 this._image.src = errorUrl; 9309 } 9310 } 9311 }); 9312 9313 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9314 // Instantiates an image overlay object given the URL of the image and the 9315 // geographical bounds it is tied to. 9316 var imageOverlay = function (url, bounds, options) { 9317 return new ImageOverlay(url, bounds, options); 9318 }; 9319 9320 /* 9321 * @class VideoOverlay 9322 * @aka L.VideoOverlay 9323 * @inherits ImageOverlay 9324 * 9325 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9326 * 9327 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9328 * HTML5 element. 9329 * 9330 * @example 9331 * 9332 * ```js 9333 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9334 * videoBounds = [[ 32, -130], [ 13, -100]]; 9335 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9336 * ``` 9337 */ 9338 9339 var VideoOverlay = ImageOverlay.extend({ 9340 9341 // @section 9342 // @aka VideoOverlay options 9343 options: { 9344 // @option autoplay: Boolean = true 9345 // Whether the video starts playing automatically when loaded. 9346 autoplay: true, 9347 9348 // @option loop: Boolean = true 9349 // Whether the video will loop back to the beginning when played. 9350 loop: true, 9351 9352 // @option keepAspectRatio: Boolean = true 9353 // Whether the video will save aspect ratio after the projection. 9354 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit 9355 keepAspectRatio: true 9356 }, 9357 9358 _initImage: function () { 9359 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9360 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9361 9362 addClass(vid, 'leaflet-image-layer'); 9363 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9364 if (this.options.className) { addClass(vid, this.options.className); } 9365 9366 vid.onselectstart = falseFn; 9367 vid.onmousemove = falseFn; 9368 9369 // @event load: Event 9370 // Fired when the video has finished loading the first frame 9371 vid.onloadeddata = bind(this.fire, this, 'load'); 9372 9373 if (wasElementSupplied) { 9374 var sourceElements = vid.getElementsByTagName('source'); 9375 var sources = []; 9376 for (var j = 0; j < sourceElements.length; j++) { 9377 sources.push(sourceElements[j].src); 9378 } 9379 9380 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9381 return; 9382 } 9383 9384 if (!isArray(this._url)) { this._url = [this._url]; } 9385 9386 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; } 9387 vid.autoplay = !!this.options.autoplay; 9388 vid.loop = !!this.options.loop; 9389 for (var i = 0; i < this._url.length; i++) { 9390 var source = create$1('source'); 9391 source.src = this._url[i]; 9392 vid.appendChild(source); 9393 } 9394 } 9395 9396 // @method getElement(): HTMLVideoElement 9397 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9398 // used by this overlay. 9399 }); 9400 9401 9402 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9403 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9404 // geographical bounds it is tied to. 9405 9406 function videoOverlay(video, bounds, options) { 9407 return new VideoOverlay(video, bounds, options); 9408 } 9409 9410 /* 9411 * @class SVGOverlay 9412 * @aka L.SVGOverlay 9413 * @inherits ImageOverlay 9414 * 9415 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`. 9416 * 9417 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. 9418 * 9419 * @example