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