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