2  Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin
 
   3  Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.
 
   6 (function (window, undefined) {
 
  10 if (typeof exports !== undefined + '') {
 
  16         L.noConflict = function () {
 
  28  * L.Util is a namespace for various utility functions.
 
  32         extend: function (dest) { // (Object[, Object, ...]) ->
 
  33                 var sources = Array.prototype.slice.call(arguments, 1),
 
  36                 for (j = 0, len = sources.length; j < len; j++) {
 
  37                         src = sources[j] || {};
 
  39                                 if (src.hasOwnProperty(i)) {
 
  47         bind: function (fn, obj) { // (Function, Object) -> Function
 
  48                 var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
 
  50                         return fn.apply(obj, args || arguments);
 
  55                 var lastId = 0, key = '_leaflet_id';
 
  56                 return function (/*Object*/ obj) {
 
  57                         obj[key] = obj[key] || ++lastId;
 
  62         limitExecByInterval: function (fn, time, context) {
 
  63                 var lock, execOnUnlock;
 
  65                 return function wrapperFn() {
 
  75                         setTimeout(function () {
 
  79                                         wrapperFn.apply(context, args);
 
  84                         fn.apply(context, args);
 
  88         falseFn: function () {
 
  92         formatNum: function (num, digits) {
 
  93                 var pow = Math.pow(10, digits || 5);
 
  94                 return Math.round(num * pow) / pow;
 
  97         splitWords: function (str) {
 
  98                 return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
 
 101         setOptions: function (obj, options) {
 
 102                 obj.options = L.extend({}, obj.options, options);
 
 106         getParamString: function (obj) {
 
 109                         if (obj.hasOwnProperty(i)) {
 
 110                                 params.push(i + '=' + obj[i]);
 
 113                 return '?' + params.join('&');
 
 116         template: function (str, data) {
 
 117                 return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
 
 118                         var value = data[key];
 
 119                         if (!data.hasOwnProperty(key)) {
 
 120                                 throw new Error('No value provided for variable ' + str);
 
 126         emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
 
 131         // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
 
 133         function getPrefixed(name) {
 
 135                     prefixes = ['webkit', 'moz', 'o', 'ms'];
 
 137                 for (i = 0; i < prefixes.length && !fn; i++) {
 
 138                         fn = window[prefixes[i] + name];
 
 146         function timeoutDefer(fn) {
 
 147                 var time = +new Date(),
 
 148                     timeToCall = Math.max(0, 16 - (time - lastTime));
 
 150                 lastTime = time + timeToCall;
 
 151                 return window.setTimeout(fn, timeToCall);
 
 154         var requestFn = window.requestAnimationFrame ||
 
 155                 getPrefixed('RequestAnimationFrame') || timeoutDefer;
 
 157         var cancelFn = window.cancelAnimationFrame ||
 
 158                 getPrefixed('CancelAnimationFrame') ||
 
 159                 getPrefixed('CancelRequestAnimationFrame') ||
 
 160                 function (id) { window.clearTimeout(id); };
 
 163         L.Util.requestAnimFrame = function (fn, context, immediate, element) {
 
 164                 fn = L.bind(fn, context);
 
 166                 if (immediate && requestFn === timeoutDefer) {
 
 169                         return requestFn.call(window, fn, element);
 
 173         L.Util.cancelAnimFrame = function (id) {
 
 175                         cancelFn.call(window, id);
 
 181 // shortcuts for most used utility functions
 
 182 L.extend = L.Util.extend;
 
 183 L.bind = L.Util.bind;
 
 184 L.stamp = L.Util.stamp;
 
 185 L.setOptions = L.Util.setOptions;
 
 189  * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
 
 192 L.Class = function () {};
 
 194 L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
 
 196         // extended class with the new prototype
 
 197         var NewClass = function () {
 
 198                 if (this.initialize) {
 
 199                         this.initialize.apply(this, arguments);
 
 203         // instantiate class without calling constructor
 
 204         var F = function () {};
 
 205         F.prototype = this.prototype;
 
 208         proto.constructor = NewClass;
 
 210         NewClass.prototype = proto;
 
 212         //inherit parent's statics
 
 213         for (var i in this) {
 
 214                 if (this.hasOwnProperty(i) && i !== 'prototype') {
 
 215                         NewClass[i] = this[i];
 
 219         // mix static properties into the class
 
 221                 L.extend(NewClass, props.statics);
 
 222                 delete props.statics;
 
 225         // mix includes into the prototype
 
 226         if (props.includes) {
 
 227                 L.Util.extend.apply(null, [proto].concat(props.includes));
 
 228                 delete props.includes;
 
 232         if (props.options && proto.options) {
 
 233                 props.options = L.extend({}, proto.options, props.options);
 
 236         // mix given properties into the prototype
 
 237         L.extend(proto, props);
 
 243 // method for adding properties to prototype
 
 244 L.Class.include = function (props) {
 
 245         L.extend(this.prototype, props);
 
 248 L.Class.mergeOptions = function (options) {
 
 249         L.extend(this.prototype.options, options);
 
 254  * L.Mixin.Events adds custom events functionality to Leaflet classes
 
 257 var key = '_leaflet_events';
 
 263         addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
 
 264                 var events = this[key] = this[key] || {},
 
 267                 // Types can be a map of types/handlers
 
 268                 if (typeof types === 'object') {
 
 269                         for (type in types) {
 
 270                                 if (types.hasOwnProperty(type)) {
 
 271                                         this.addEventListener(type, types[type], fn);
 
 278                 types = L.Util.splitWords(types);
 
 280                 for (i = 0, len = types.length; i < len; i++) {
 
 281                         events[types[i]] = events[types[i]] || [];
 
 282                         events[types[i]].push({
 
 284                                 context: context || this
 
 291         hasEventListeners: function (type) { // (String) -> Boolean
 
 292                 return (key in this) && (type in this[key]) && (this[key][type].length > 0);
 
 295         removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
 
 296                 var events = this[key],
 
 297                         type, i, len, listeners, j;
 
 299                 if (typeof types === 'object') {
 
 300                         for (type in types) {
 
 301                                 if (types.hasOwnProperty(type)) {
 
 302                                         this.removeEventListener(type, types[type], fn);
 
 309                 types = L.Util.splitWords(types);
 
 311                 for (i = 0, len = types.length; i < len; i++) {
 
 313                         if (this.hasEventListeners(types[i])) {
 
 314                                 listeners = events[types[i]];
 
 316                                 for (j = listeners.length - 1; j >= 0; j--) {
 
 318                                                 (!fn || listeners[j].action === fn) &&
 
 319                                                 (!context || (listeners[j].context === context))
 
 321                                                 listeners.splice(j, 1);
 
 330         fireEvent: function (type, data) { // (String[, Object])
 
 331                 if (!this.hasEventListeners(type)) {
 
 335                 var event = L.extend({
 
 340                 var listeners = this[key][type].slice();
 
 342                 for (var i = 0, len = listeners.length; i < len; i++) {
 
 343                         listeners[i].action.call(listeners[i].context || this, event);
 
 350 L.Mixin.Events.on = L.Mixin.Events.addEventListener;
 
 351 L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
 
 352 L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
 
 357         var ie = !!window.ActiveXObject,
 
 358             // http://tanalin.com/en/articles/ie-version-js/
 
 359             ie6 = ie && !window.XMLHttpRequest,
 
 360             ie7 = ie && !document.querySelector,
 
 362             // terrible browser detection to work around Safari / iOS / Android browser bugs
 
 363             // see TileLayer._addTile and debug/hacks/jitter.html
 
 365             ua = navigator.userAgent.toLowerCase(),
 
 366             webkit = ua.indexOf("webkit") !== -1,
 
 367             chrome = ua.indexOf("chrome") !== -1,
 
 368             android = ua.indexOf("android") !== -1,
 
 369             android23 = ua.search("android [23]") !== -1,
 
 371             mobile = typeof orientation !== undefined + '',
 
 372             msTouch = (window.navigator && window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints),
 
 373             retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
 
 374                       ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches)),
 
 376             doc = document.documentElement,
 
 377             ie3d = ie && ('transition' in doc.style),
 
 378             webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
 
 379             gecko3d = 'MozPerspective' in doc.style,
 
 380             opera3d = 'OTransition' in doc.style,
 
 381             any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d);
 
 384         var touch = !window.L_NO_TOUCH && (function () {
 
 386                 var startName = 'ontouchstart';
 
 388                 // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
 
 389                 if (msTouch || (startName in doc)) {
 
 394                 var div = document.createElement('div'),
 
 397                 if (!div.setAttribute) {
 
 400                 div.setAttribute(startName, 'return;');
 
 402                 if (typeof div[startName] === 'function') {
 
 406                 div.removeAttribute(startName);
 
 419                 android23: android23,
 
 430                 mobileWebkit: mobile && webkit,
 
 431                 mobileWebkit3d: mobile && webkit3d,
 
 432                 mobileOpera: mobile && window.opera,
 
 444  * L.Point represents a point with x and y coordinates.
 
 447 L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
 
 448         this.x = (round ? Math.round(x) : x);
 
 449         this.y = (round ? Math.round(y) : y);
 
 452 L.Point.prototype = {
 
 455                 return new L.Point(this.x, this.y);
 
 458         // non-destructive, returns a new point
 
 459         add: function (point) {
 
 460                 return this.clone()._add(L.point(point));
 
 463         // destructive, used directly for performance in situations where it's safe to modify existing point
 
 464         _add: function (point) {
 
 470         subtract: function (point) {
 
 471                 return this.clone()._subtract(L.point(point));
 
 474         _subtract: function (point) {
 
 480         divideBy: function (num) {
 
 481                 return this.clone()._divideBy(num);
 
 484         _divideBy: function (num) {
 
 490         multiplyBy: function (num) {
 
 491                 return this.clone()._multiplyBy(num);
 
 494         _multiplyBy: function (num) {
 
 501                 return this.clone()._round();
 
 504         _round: function () {
 
 505                 this.x = Math.round(this.x);
 
 506                 this.y = Math.round(this.y);
 
 511                 return this.clone()._floor();
 
 514         _floor: function () {
 
 515                 this.x = Math.floor(this.x);
 
 516                 this.y = Math.floor(this.y);
 
 520         distanceTo: function (point) {
 
 521                 point = L.point(point);
 
 523                 var x = point.x - this.x,
 
 524                     y = point.y - this.y;
 
 526                 return Math.sqrt(x * x + y * y);
 
 529         toString: function () {
 
 531                         L.Util.formatNum(this.x) + ', ' +
 
 532                         L.Util.formatNum(this.y) + ')';
 
 536 L.point = function (x, y, round) {
 
 537         if (x instanceof L.Point) {
 
 540         if (x instanceof Array) {
 
 541                 return new L.Point(x[0], x[1]);
 
 546         return new L.Point(x, y, round);
 
 551  * L.Bounds represents a rectangular area on the screen in pixel coordinates.
 
 554 L.Bounds = L.Class.extend({
 
 556         initialize: function (a, b) {   //(Point, Point) or Point[]
 
 559                 var points = b ? [a, b] : a;
 
 561                 for (var i = 0, len = points.length; i < len; i++) {
 
 562                         this.extend(points[i]);
 
 566         // extend the bounds to contain the given point
 
 567         extend: function (point) { // (Point)
 
 568                 point = L.point(point);
 
 570                 if (!this.min && !this.max) {
 
 571                         this.min = point.clone();
 
 572                         this.max = point.clone();
 
 574                         this.min.x = Math.min(point.x, this.min.x);
 
 575                         this.max.x = Math.max(point.x, this.max.x);
 
 576                         this.min.y = Math.min(point.y, this.min.y);
 
 577                         this.max.y = Math.max(point.y, this.max.y);
 
 582         getCenter: function (round) { // (Boolean) -> Point
 
 584                         (this.min.x + this.max.x) / 2,
 
 585                         (this.min.y + this.max.y) / 2, round);
 
 588         getBottomLeft: function () { // -> Point
 
 589                 return new L.Point(this.min.x, this.max.y);
 
 592         getTopRight: function () { // -> Point
 
 593                 return new L.Point(this.max.x, this.min.y);
 
 596         contains: function (obj) { // (Bounds) or (Point) -> Boolean
 
 599                 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
 
 605                 if (obj instanceof L.Bounds) {
 
 612                 return (min.x >= this.min.x) &&
 
 613                        (max.x <= this.max.x) &&
 
 614                        (min.y >= this.min.y) &&
 
 615                        (max.y <= this.max.y);
 
 618         intersects: function (bounds) { // (Bounds) -> Boolean
 
 619                 bounds = L.bounds(bounds);
 
 625                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
 
 626                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
 
 628                 return xIntersects && yIntersects;
 
 631         isValid: function () {
 
 632                 return !!(this.min && this.max);
 
 636 L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
 
 637         if (!a || a instanceof L.Bounds) {
 
 640         return new L.Bounds(a, b);
 
 645  * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
 
 648 L.Transformation = L.Class.extend({
 
 649         initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {
 
 656         transform: function (point, scale) {
 
 657                 return this._transform(point.clone(), scale);
 
 660         // destructive transform (faster)
 
 661         _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
 
 663                 point.x = scale * (this._a * point.x + this._b);
 
 664                 point.y = scale * (this._c * point.y + this._d);
 
 668         untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
 
 671                         (point.x / scale - this._b) / this._a,
 
 672                         (point.y / scale - this._d) / this._c);
 
 678  * L.DomUtil contains various utility functions for working with DOM.
 
 683                 return (typeof id === 'string' ? document.getElementById(id) : id);
 
 686         getStyle: function (el, style) {
 
 688                 var value = el.style[style];
 
 690                 if (!value && el.currentStyle) {
 
 691                         value = el.currentStyle[style];
 
 694                 if ((!value || value === 'auto') && document.defaultView) {
 
 695                         var css = document.defaultView.getComputedStyle(el, null);
 
 696                         value = css ? css[style] : null;
 
 699                 return value === 'auto' ? null : value;
 
 702         getViewportOffset: function (element) {
 
 707                     docBody = document.body,
 
 712                         top  += el.offsetTop  || 0;
 
 713                         left += el.offsetLeft || 0;
 
 714                         pos = L.DomUtil.getStyle(el, 'position');
 
 716                         if (el.offsetParent === docBody && pos === 'absolute') { break; }
 
 718                         if (pos === 'fixed') {
 
 719                                 top  += docBody.scrollTop  || 0;
 
 720                                 left += docBody.scrollLeft || 0;
 
 723                         el = el.offsetParent;
 
 730                         if (el === docBody) { break; }
 
 732                         top  -= el.scrollTop  || 0;
 
 733                         left -= el.scrollLeft || 0;
 
 735                         // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
 
 736                         // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
 
 737                         if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
 
 738                                 left += el.scrollWidth - el.clientWidth;
 
 740                                 // ie7 shows the scrollbar by default and provides clientWidth counting it, so we
 
 741                                 // need to add it back in if it is visible; scrollbar is on the left as we are RTL
 
 742                                 if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
 
 743                                            L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
 
 751                 return new L.Point(left, top);
 
 754         documentIsLtr: function () {
 
 755                 if (!L.DomUtil._docIsLtrCached) {
 
 756                         L.DomUtil._docIsLtrCached = true;
 
 757                         L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === "ltr";
 
 759                 return L.DomUtil._docIsLtr;
 
 762         create: function (tagName, className, container) {
 
 764                 var el = document.createElement(tagName);
 
 765                 el.className = className;
 
 768                         container.appendChild(el);
 
 774         disableTextSelection: function () {
 
 775                 if (document.selection && document.selection.empty) {
 
 776                         document.selection.empty();
 
 778                 if (!this._onselectstart) {
 
 779                         this._onselectstart = document.onselectstart;
 
 780                         document.onselectstart = L.Util.falseFn;
 
 784         enableTextSelection: function () {
 
 785                 if (document.onselectstart === L.Util.falseFn) {
 
 786                         document.onselectstart = this._onselectstart;
 
 787                         this._onselectstart = null;
 
 791         hasClass: function (el, name) {
 
 792                 return (el.className.length > 0) &&
 
 793                         new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
 
 796         addClass: function (el, name) {
 
 797                 if (!L.DomUtil.hasClass(el, name)) {
 
 798                         el.className += (el.className ? ' ' : '') + name;
 
 802         removeClass: function (el, name) {
 
 804                 function replaceFn(w, match) {
 
 805                         if (match === name) { return ''; }
 
 809                 el.className = el.className
 
 810                         .replace(/(\S+)\s*/g, replaceFn)
 
 811                         .replace(/(^\s+|\s+$)/, '');
 
 814         setOpacity: function (el, value) {
 
 816                 if ('opacity' in el.style) {
 
 817                         el.style.opacity = value;
 
 819                 } else if ('filter' in el.style) {
 
 822                             filterName = 'DXImageTransform.Microsoft.Alpha';
 
 824                         // filters collection throws an error if we try to retrieve a filter that doesn't exist
 
 825                         try { filter = el.filters.item(filterName); } catch (e) {}
 
 827                         value = Math.round(value * 100);
 
 830                                 filter.Enabled = (value !== 100);
 
 831                                 filter.Opacity = value;
 
 833                                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
 
 838         testProp: function (props) {
 
 840                 var style = document.documentElement.style;
 
 842                 for (var i = 0; i < props.length; i++) {
 
 843                         if (props[i] in style) {
 
 850         getTranslateString: function (point) {
 
 851                 // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
 
 852                 // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
 
 853                 // (same speed either way), Opera 12 doesn't support translate3d
 
 855                 var is3d = L.Browser.webkit3d,
 
 856                     open = 'translate' + (is3d ? '3d' : '') + '(',
 
 857                     close = (is3d ? ',0' : '') + ')';
 
 859                 return open + point.x + 'px,' + point.y + 'px' + close;
 
 862         getScaleString: function (scale, origin) {
 
 864                 var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
 
 865                     scaleStr = ' scale(' + scale + ') ';
 
 867                 return preTranslateStr + scaleStr;
 
 870         setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
 
 872                 el._leaflet_pos = point;
 
 874                 if (!disable3D && L.Browser.any3d) {
 
 875                         el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);
 
 877                         // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
 
 878                         if (L.Browser.mobileWebkit3d) {
 
 879                                 el.style.WebkitBackfaceVisibility = 'hidden';
 
 882                         el.style.left = point.x + 'px';
 
 883                         el.style.top = point.y + 'px';
 
 887         getPosition: function (el) {
 
 888                 // this method is only used for elements previously positioned using setPosition,
 
 889                 // so it's safe to cache the position for performance
 
 890                 return el._leaflet_pos;
 
 895 // prefix style property names
 
 897 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
 
 898         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
 
 900 L.DomUtil.TRANSITION = L.DomUtil.testProp(
 
 901         ['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']);
 
 903 L.DomUtil.TRANSITION_END =
 
 904         L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
 
 905         L.DomUtil.TRANSITION + 'End' : 'transitionend';
 
 909         CM.LatLng represents a geographical point with latitude and longtitude coordinates.
 
 912 L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
 
 913         var lat = parseFloat(rawLat),
 
 914             lng = parseFloat(rawLng);
 
 916         if (isNaN(lat) || isNaN(lng)) {
 
 917                 throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
 
 920         if (noWrap !== true) {
 
 921                 lat = Math.max(Math.min(lat, 90), -90);                                 // clamp latitude into -90..90
 
 922                 lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180);   // wrap longtitude into -180..180
 
 930         DEG_TO_RAD: Math.PI / 180,
 
 931         RAD_TO_DEG: 180 / Math.PI,
 
 932         MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
 
 935 L.LatLng.prototype = {
 
 936         equals: function (obj) { // (LatLng) -> Boolean
 
 937                 if (!obj) { return false; }
 
 941                 var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
 
 942                 return margin <= L.LatLng.MAX_MARGIN;
 
 945         toString: function (precision) { // -> String
 
 947                         L.Util.formatNum(this.lat, precision) + ', ' +
 
 948                         L.Util.formatNum(this.lng, precision) + ')';
 
 951         // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
 
 952         distanceTo: function (other) { // (LatLng) -> Number
 
 953                 other = L.latLng(other);
 
 955                 var R = 6378137, // earth radius in meters
 
 956                     d2r = L.LatLng.DEG_TO_RAD,
 
 957                     dLat = (other.lat - this.lat) * d2r,
 
 958                     dLon = (other.lng - this.lng) * d2r,
 
 959                     lat1 = this.lat * d2r,
 
 960                     lat2 = other.lat * d2r,
 
 961                     sin1 = Math.sin(dLat / 2),
 
 962                     sin2 = Math.sin(dLon / 2);
 
 964                 var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
 
 966                 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
 
 970 L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
 
 971         if (a instanceof L.LatLng) {
 
 974         if (a instanceof Array) {
 
 975                 return new L.LatLng(a[0], a[1]);
 
 980         return new L.LatLng(a, b, c);
 
 986  * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
 
 989 L.LatLngBounds = L.Class.extend({
 
 990         initialize: function (southWest, northEast) {   // (LatLng, LatLng) or (LatLng[])
 
 991                 if (!southWest) { return; }
 
 993                 var latlngs = northEast ? [southWest, northEast] : southWest;
 
 995                 for (var i = 0, len = latlngs.length; i < len; i++) {
 
 996                         this.extend(latlngs[i]);
 
1000         // extend the bounds to contain the given point or bounds
 
1001         extend: function (obj) { // (LatLng) or (LatLngBounds)
 
1002                 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
 
1003                         obj = L.latLng(obj);
 
1005                         obj = L.latLngBounds(obj);
 
1008                 if (obj instanceof L.LatLng) {
 
1009                         if (!this._southWest && !this._northEast) {
 
1010                                 this._southWest = new L.LatLng(obj.lat, obj.lng, true);
 
1011                                 this._northEast = new L.LatLng(obj.lat, obj.lng, true);
 
1013                                 this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
 
1014                                 this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
 
1016                                 this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
 
1017                                 this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
 
1019                 } else if (obj instanceof L.LatLngBounds) {
 
1020                         this.extend(obj._southWest);
 
1021                         this.extend(obj._northEast);
 
1026         // extend the bounds by a percentage
 
1027         pad: function (bufferRatio) { // (Number) -> LatLngBounds
 
1028                 var sw = this._southWest,
 
1029                     ne = this._northEast,
 
1030                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
 
1031                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
 
1033                 return new L.LatLngBounds(
 
1034                         new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
 
1035                         new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
 
1038         getCenter: function () { // -> LatLng
 
1039                 return new L.LatLng(
 
1040                         (this._southWest.lat + this._northEast.lat) / 2,
 
1041                         (this._southWest.lng + this._northEast.lng) / 2);
 
1044         getSouthWest: function () {
 
1045                 return this._southWest;
 
1048         getNorthEast: function () {
 
1049                 return this._northEast;
 
1052         getNorthWest: function () {
 
1053                 return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
 
1056         getSouthEast: function () {
 
1057                 return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
 
1060         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
 
1061                 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
 
1062                         obj = L.latLng(obj);
 
1064                         obj = L.latLngBounds(obj);
 
1067                 var sw = this._southWest,
 
1068                     ne = this._northEast,
 
1071                 if (obj instanceof L.LatLngBounds) {
 
1072                         sw2 = obj.getSouthWest();
 
1073                         ne2 = obj.getNorthEast();
 
1078                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
 
1079                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
 
1082         intersects: function (bounds) { // (LatLngBounds)
 
1083                 bounds = L.latLngBounds(bounds);
 
1085                 var sw = this._southWest,
 
1086                     ne = this._northEast,
 
1087                     sw2 = bounds.getSouthWest(),
 
1088                     ne2 = bounds.getNorthEast(),
 
1090                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
 
1091                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
 
1093                 return latIntersects && lngIntersects;
 
1096         toBBoxString: function () {
 
1097                 var sw = this._southWest,
 
1098                     ne = this._northEast;
 
1100                 return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
 
1103         equals: function (bounds) { // (LatLngBounds)
 
1104                 if (!bounds) { return false; }
 
1106                 bounds = L.latLngBounds(bounds);
 
1108                 return this._southWest.equals(bounds.getSouthWest()) &&
 
1109                        this._northEast.equals(bounds.getNorthEast());
 
1112         isValid: function () {
 
1113                 return !!(this._southWest && this._northEast);
 
1117 //TODO International date line?
 
1119 L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
 
1120         if (!a || a instanceof L.LatLngBounds) {
 
1123         return new L.LatLngBounds(a, b);
 
1128  * L.Projection contains various geographical projections used by CRS classes.
 
1135 L.Projection.SphericalMercator = {
 
1136         MAX_LATITUDE: 85.0511287798,
 
1138         project: function (latlng) { // (LatLng) -> Point
 
1139                 var d = L.LatLng.DEG_TO_RAD,
 
1140                     max = this.MAX_LATITUDE,
 
1141                     lat = Math.max(Math.min(max, latlng.lat), -max),
 
1145                 y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
 
1147                 return new L.Point(x, y);
 
1150         unproject: function (point) { // (Point, Boolean) -> LatLng
 
1151                 var d = L.LatLng.RAD_TO_DEG,
 
1153                     lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
 
1155                 // TODO refactor LatLng wrapping
 
1156                 return new L.LatLng(lat, lng, true);
 
1162 L.Projection.LonLat = {
 
1163         project: function (latlng) {
 
1164                 return new L.Point(latlng.lng, latlng.lat);
 
1167         unproject: function (point) {
 
1168                 return new L.LatLng(point.y, point.x, true);
 
1175         latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
 
1176                 var projectedPoint = this.projection.project(latlng),
 
1177                     scale = this.scale(zoom);
 
1179                 return this.transformation._transform(projectedPoint, scale);
 
1182         pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
 
1183                 var scale = this.scale(zoom),
 
1184                     untransformedPoint = this.transformation.untransform(point, scale);
 
1186                 return this.projection.unproject(untransformedPoint);
 
1189         project: function (latlng) {
 
1190                 return this.projection.project(latlng);
 
1193         scale: function (zoom) {
 
1194                 return 256 * Math.pow(2, zoom);
 
1200 L.CRS.Simple = L.extend({}, L.CRS, {
 
1201         projection: L.Projection.LonLat,
 
1202         transformation: new L.Transformation(1, 0, 1, 0)
 
1207 L.CRS.EPSG3857 = L.extend({}, L.CRS, {
 
1210         projection: L.Projection.SphericalMercator,
 
1211         transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
 
1213         project: function (latlng) { // (LatLng) -> Point
 
1214                 var projectedPoint = this.projection.project(latlng),
 
1215                     earthRadius = 6378137;
 
1216                 return projectedPoint.multiplyBy(earthRadius);
 
1220 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
 
1226 L.CRS.EPSG4326 = L.extend({}, L.CRS, {
 
1229         projection: L.Projection.LonLat,
 
1230         transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
 
1235  * L.Map is the central class of the API - it is used to create a map.
 
1238 L.Map = L.Class.extend({
 
1240         includes: L.Mixin.Events,
 
1243                 crs: L.CRS.EPSG3857,
 
1251                 fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
 
1253                 markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
 
1256         initialize: function (id, options) { // (HTMLElement or String, Object)
 
1257                 options = L.setOptions(this, options);
 
1259                 this._initContainer(id);
 
1264                 if (options.maxBounds) {
 
1265                         this.setMaxBounds(options.maxBounds);
 
1268                 if (options.center && options.zoom !== undefined) {
 
1269                         this.setView(L.latLng(options.center), options.zoom, true);
 
1272                 this._initLayers(options.layers);
 
1276         // public methods that modify map state
 
1278         // replaced by animation-powered implementation in Map.PanAnimation.js
 
1279         setView: function (center, zoom) {
 
1280                 this._resetView(L.latLng(center), this._limitZoom(zoom));
 
1284         setZoom: function (zoom) { // (Number)
 
1285                 return this.setView(this.getCenter(), zoom);
 
1288         zoomIn: function (delta) {
 
1289                 return this.setZoom(this._zoom + (delta || 1));
 
1292         zoomOut: function (delta) {
 
1293                 return this.setZoom(this._zoom - (delta || 1));
 
1296         fitBounds: function (bounds) { // (LatLngBounds)
 
1297                 var zoom = this.getBoundsZoom(bounds);
 
1298                 return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
 
1301         fitWorld: function () {
 
1302                 var sw = new L.LatLng(-60, -170),
 
1303                     ne = new L.LatLng(85, 179);
 
1305                 return this.fitBounds(new L.LatLngBounds(sw, ne));
 
1308         panTo: function (center) { // (LatLng)
 
1309                 return this.setView(center, this._zoom);
 
1312         panBy: function (offset) { // (Point)
 
1313                 // replaced with animated panBy in Map.Animation.js
 
1314                 this.fire('movestart');
 
1316                 this._rawPanBy(L.point(offset));
 
1319                 return this.fire('moveend');
 
1322         setMaxBounds: function (bounds) {
 
1323                 bounds = L.latLngBounds(bounds);
 
1325                 this.options.maxBounds = bounds;
 
1328                         this._boundsMinZoom = null;
 
1332                 var minZoom = this.getBoundsZoom(bounds, true);
 
1334                 this._boundsMinZoom = minZoom;
 
1337                         if (this._zoom < minZoom) {
 
1338                                 this.setView(bounds.getCenter(), minZoom);
 
1340                                 this.panInsideBounds(bounds);
 
1347         panInsideBounds: function (bounds) {
 
1348                 bounds = L.latLngBounds(bounds);
 
1350                 var viewBounds = this.getBounds(),
 
1351                     viewSw = this.project(viewBounds.getSouthWest()),
 
1352                     viewNe = this.project(viewBounds.getNorthEast()),
 
1353                     sw = this.project(bounds.getSouthWest()),
 
1354                     ne = this.project(bounds.getNorthEast()),
 
1358                 if (viewNe.y < ne.y) { // north
 
1359                         dy = ne.y - viewNe.y;
 
1361                 if (viewNe.x > ne.x) { // east
 
1362                         dx = ne.x - viewNe.x;
 
1364                 if (viewSw.y > sw.y) { // south
 
1365                         dy = sw.y - viewSw.y;
 
1367                 if (viewSw.x < sw.x) { // west
 
1368                         dx = sw.x - viewSw.x;
 
1371                 return this.panBy(new L.Point(dx, dy, true));
 
1374         addLayer: function (layer) {
 
1375                 // TODO method is too big, refactor
 
1377                 var id = L.stamp(layer);
 
1379                 if (this._layers[id]) { return this; }
 
1381                 this._layers[id] = layer;
 
1383                 // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
 
1384                 if (layer.options && !isNaN(layer.options.maxZoom)) {
 
1385                         this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
 
1387                 if (layer.options && !isNaN(layer.options.minZoom)) {
 
1388                         this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
 
1391                 // TODO looks ugly, refactor!!!
 
1392                 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
 
1393                         this._tileLayersNum++;
 
1394             this._tileLayersToLoad++;
 
1395             layer.on('load', this._onTileLayerLoad, this);
 
1398                 this.whenReady(function () {
 
1400                         this.fire('layeradd', {layer: layer});
 
1406         removeLayer: function (layer) {
 
1407                 var id = L.stamp(layer);
 
1409                 if (!this._layers[id]) { return; }
 
1411                 layer.onRemove(this);
 
1413                 delete this._layers[id];
 
1415                 // TODO looks ugly, refactor
 
1416                 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
 
1417                         this._tileLayersNum--;
 
1418             this._tileLayersToLoad--;
 
1419             layer.off('load', this._onTileLayerLoad, this);
 
1422                 return this.fire('layerremove', {layer: layer});
 
1425         hasLayer: function (layer) {
 
1426                 var id = L.stamp(layer);
 
1427                 return this._layers.hasOwnProperty(id);
 
1430         invalidateSize: function (animate) {
 
1431                 var oldSize = this.getSize();
 
1433                 this._sizeChanged = true;
 
1435                 if (this.options.maxBounds) {
 
1436                         this.setMaxBounds(this.options.maxBounds);
 
1439                 if (!this._loaded) { return this; }
 
1441                 var offset = oldSize._subtract(this.getSize())._divideBy(2)._round();
 
1443                 if (animate === true) {
 
1446                         this._rawPanBy(offset);
 
1450                         clearTimeout(this._sizeTimer);
 
1451                         this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
 
1456         // TODO handler.addTo
 
1457         addHandler: function (name, HandlerClass) {
 
1458                 if (!HandlerClass) { return; }
 
1460                 this[name] = new HandlerClass(this);
 
1462                 if (this.options[name]) {
 
1463                         this[name].enable();
 
1470         // public methods for getting map state
 
1472         getCenter: function () { // (Boolean) -> LatLng
 
1473                 return this.layerPointToLatLng(this._getCenterLayerPoint());
 
1476         getZoom: function () {
 
1480         getBounds: function () {
 
1481                 var bounds = this.getPixelBounds(),
 
1482                     sw = this.unproject(bounds.getBottomLeft()),
 
1483                     ne = this.unproject(bounds.getTopRight());
 
1485                 return new L.LatLngBounds(sw, ne);
 
1488         getMinZoom: function () {
 
1489                 var z1 = this.options.minZoom || 0,
 
1490                     z2 = this._layersMinZoom || 0,
 
1491                     z3 = this._boundsMinZoom || 0;
 
1493                 return Math.max(z1, z2, z3);
 
1496         getMaxZoom: function () {
 
1497                 var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
 
1498                     z2 = this._layersMaxZoom  === undefined ? Infinity : this._layersMaxZoom;
 
1500                 return Math.min(z1, z2);
 
1503         getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
 
1504                 bounds = L.latLngBounds(bounds);
 
1506                 var size = this.getSize(),
 
1507                     zoom = this.options.minZoom || 0,
 
1508                     maxZoom = this.getMaxZoom(),
 
1509                     ne = bounds.getNorthEast(),
 
1510                     sw = bounds.getSouthWest(),
 
1514                     zoomNotFound = true;
 
1522                         nePoint = this.project(ne, zoom);
 
1523                         swPoint = this.project(sw, zoom);
 
1525                         boundsSize = new L.Point(
 
1526                                 Math.abs(nePoint.x - swPoint.x),
 
1527                                 Math.abs(swPoint.y - nePoint.y));
 
1530                                 zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
 
1532                                 zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
 
1534                 } while (zoomNotFound && zoom <= maxZoom);
 
1536                 if (zoomNotFound && inside) {
 
1540                 return inside ? zoom : zoom - 1;
 
1543         getSize: function () {
 
1544                 if (!this._size || this._sizeChanged) {
 
1545                         this._size = new L.Point(
 
1546                                 this._container.clientWidth,
 
1547                                 this._container.clientHeight);
 
1549                         this._sizeChanged = false;
 
1551                 return this._size.clone();
 
1554         getPixelBounds: function () {
 
1555                 var topLeftPoint = this._getTopLeftPoint();
 
1556                 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
 
1559         getPixelOrigin: function () {
 
1560                 return this._initialTopLeftPoint;
 
1563         getPanes: function () {
 
1567         getContainer: function () {
 
1568                 return this._container;
 
1572         // TODO replace with universal implementation after refactoring projections
 
1574         getZoomScale: function (toZoom) {
 
1575                 var crs = this.options.crs;
 
1576                 return crs.scale(toZoom) / crs.scale(this._zoom);
 
1579         getScaleZoom: function (scale) {
 
1580                 return this._zoom + (Math.log(scale) / Math.LN2);
 
1584         // conversion methods
 
1586         project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
 
1587                 zoom = zoom === undefined ? this._zoom : zoom;
 
1588                 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
 
1591         unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
 
1592                 zoom = zoom === undefined ? this._zoom : zoom;
 
1593                 return this.options.crs.pointToLatLng(L.point(point), zoom);
 
1596         layerPointToLatLng: function (point) { // (Point)
 
1597                 var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
 
1598                 return this.unproject(projectedPoint);
 
1601         latLngToLayerPoint: function (latlng) { // (LatLng)
 
1602                 var projectedPoint = this.project(L.latLng(latlng))._round();
 
1603                 return projectedPoint._subtract(this._initialTopLeftPoint);
 
1606         containerPointToLayerPoint: function (point) { // (Point)
 
1607                 return L.point(point).subtract(this._getMapPanePos());
 
1610         layerPointToContainerPoint: function (point) { // (Point)
 
1611                 return L.point(point).add(this._getMapPanePos());
 
1614         containerPointToLatLng: function (point) {
 
1615                 var layerPoint = this.containerPointToLayerPoint(L.point(point));
 
1616                 return this.layerPointToLatLng(layerPoint);
 
1619         latLngToContainerPoint: function (latlng) {
 
1620                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
 
1623         mouseEventToContainerPoint: function (e) { // (MouseEvent)
 
1624                 return L.DomEvent.getMousePosition(e, this._container);
 
1627         mouseEventToLayerPoint: function (e) { // (MouseEvent)
 
1628                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
 
1631         mouseEventToLatLng: function (e) { // (MouseEvent)
 
1632                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
 
1636         // map initialization methods
 
1638         _initContainer: function (id) {
 
1639                 var container = this._container = L.DomUtil.get(id);
 
1641                 if (container._leaflet) {
 
1642                         throw new Error("Map container is already initialized.");
 
1645                 container._leaflet = true;
 
1648         _initLayout: function () {
 
1649                 var container = this._container;
 
1651                 container.innerHTML = '';
 
1652                 L.DomUtil.addClass(container, 'leaflet-container');
 
1654                 if (L.Browser.touch) {
 
1655                         L.DomUtil.addClass(container, 'leaflet-touch');
 
1658                 if (this.options.fadeAnimation) {
 
1659                         L.DomUtil.addClass(container, 'leaflet-fade-anim');
 
1662                 var position = L.DomUtil.getStyle(container, 'position');
 
1664                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
 
1665                         container.style.position = 'relative';
 
1670                 if (this._initControlPos) {
 
1671                         this._initControlPos();
 
1675         _initPanes: function () {
 
1676                 var panes = this._panes = {};
 
1678                 this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
 
1680                 this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
 
1681                 panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
 
1682                 panes.shadowPane = this._createPane('leaflet-shadow-pane');
 
1683                 panes.overlayPane = this._createPane('leaflet-overlay-pane');
 
1684                 panes.markerPane = this._createPane('leaflet-marker-pane');
 
1685                 panes.popupPane = this._createPane('leaflet-popup-pane');
 
1687                 var zoomHide = ' leaflet-zoom-hide';
 
1689                 if (!this.options.markerZoomAnimation) {
 
1690                         L.DomUtil.addClass(panes.markerPane, zoomHide);
 
1691                         L.DomUtil.addClass(panes.shadowPane, zoomHide);
 
1692                         L.DomUtil.addClass(panes.popupPane, zoomHide);
 
1696         _createPane: function (className, container) {
 
1697                 return L.DomUtil.create('div', className, container || this._panes.objectsPane);
 
1702         _initHooks: function () {
 
1704                 for (i = 0, len = this._initializers.length; i < len; i++) {
 
1705                         this._initializers[i].call(this);
 
1709         _initLayers: function (layers) {
 
1710                 layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
 
1713                 this._tileLayersNum = 0;
 
1717                 for (i = 0, len = layers.length; i < len; i++) {
 
1718                         this.addLayer(layers[i]);
 
1723         // private methods that modify map state
 
1725         _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
 
1727                 var zoomChanged = (this._zoom !== zoom);
 
1729                 if (!afterZoomAnim) {
 
1730                         this.fire('movestart');
 
1733                                 this.fire('zoomstart');
 
1739                 this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
 
1741                 if (!preserveMapOffset) {
 
1742                         L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
 
1744                         this._initialTopLeftPoint._add(this._getMapPanePos());
 
1747                 this._tileLayersToLoad = this._tileLayersNum;
 
1749                 var loading = !this._loaded;
 
1750                 this._loaded = true;
 
1752                 this.fire('viewreset', {hard: !preserveMapOffset});
 
1756                 if (zoomChanged || afterZoomAnim) {
 
1757                         this.fire('zoomend');
 
1760                 this.fire('moveend', {hard: !preserveMapOffset});
 
1767         _rawPanBy: function (offset) {
 
1768                 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
 
1774         _initEvents: function () {
 
1775                 if (!L.DomEvent) { return; }
 
1777                 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
 
1779                 var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
 
1780                               'mouseleave', 'mousemove', 'contextmenu'],
 
1783                 for (i = 0, len = events.length; i < len; i++) {
 
1784                         L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
 
1787                 if (this.options.trackResize) {
 
1788                         L.DomEvent.on(window, 'resize', this._onResize, this);
 
1792         _onResize: function () {
 
1793                 L.Util.cancelAnimFrame(this._resizeRequest);
 
1794                 this._resizeRequest = L.Util.requestAnimFrame(
 
1795                         this.invalidateSize, this, false, this._container);
 
1798         _onMouseClick: function (e) {
 
1799                 if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
 
1801                 this.fire('preclick');
 
1802                 this._fireMouseEvent(e);
 
1805         _fireMouseEvent: function (e) {
 
1806                 if (!this._loaded) { return; }
 
1810                 type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
 
1812                 if (!this.hasEventListeners(type)) { return; }
 
1814                 if (type === 'contextmenu') {
 
1815                         L.DomEvent.preventDefault(e);
 
1818                 var containerPoint = this.mouseEventToContainerPoint(e),
 
1819                     layerPoint = this.containerPointToLayerPoint(containerPoint),
 
1820                     latlng = this.layerPointToLatLng(layerPoint);
 
1824                         layerPoint: layerPoint,
 
1825                         containerPoint: containerPoint,
 
1830         _onTileLayerLoad: function () {
 
1831                 // TODO super-ugly, refactor!!!
 
1832                 // clear scaled tiles after all new tiles are loaded (for performance)
 
1833                 this._tileLayersToLoad--;
 
1834                 if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
 
1835                         clearTimeout(this._clearTileBgTimer);
 
1836                         this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500);
 
1840         whenReady: function (callback, context) {
 
1842                         callback.call(context || this, this);
 
1844                         this.on('load', callback, context);
 
1850         // private methods for getting map state
 
1852         _getMapPanePos: function () {
 
1853                 return L.DomUtil.getPosition(this._mapPane);
 
1856         _getTopLeftPoint: function () {
 
1857                 if (!this._loaded) {
 
1858                         throw new Error('Set map center and zoom first.');
 
1861                 return this._initialTopLeftPoint.subtract(this._getMapPanePos());
 
1864         _getNewTopLeftPoint: function (center, zoom) {
 
1865                 var viewHalf = this.getSize()._divideBy(2);
 
1866                 // TODO round on display, not calculation to increase precision?
 
1867                 return this.project(center, zoom)._subtract(viewHalf)._round();
 
1870         _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
 
1871                 var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
 
1872                 return this.project(latlng, newZoom)._subtract(topLeft);
 
1875         _getCenterLayerPoint: function () {
 
1876                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
 
1879         _getCenterOffset: function (center) {
 
1880                 return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
 
1883         _limitZoom: function (zoom) {
 
1884                 var min = this.getMinZoom(),
 
1885                     max = this.getMaxZoom();
 
1887                 return Math.max(min, Math.min(max, zoom));
 
1891 L.Map.addInitHook = function (fn) {
 
1892         var args = Array.prototype.slice.call(arguments, 1);
 
1894         var init = typeof fn === 'function' ? fn : function () {
 
1895                 this[fn].apply(this, args);
 
1898         this.prototype._initializers.push(init);
 
1901 L.map = function (id, options) {
 
1902         return new L.Map(id, options);
 
1907 L.Projection.Mercator = {
 
1908         MAX_LATITUDE: 85.0840591556,
 
1910         R_MINOR: 6356752.3142,
 
1913         project: function (latlng) { // (LatLng) -> Point
 
1914                 var d = L.LatLng.DEG_TO_RAD,
 
1915                     max = this.MAX_LATITUDE,
 
1916                     lat = Math.max(Math.min(max, latlng.lat), -max),
 
1919                     x = latlng.lng * d * r,
 
1922                     eccent = Math.sqrt(1.0 - tmp * tmp),
 
1923                     con = eccent * Math.sin(y);
 
1925                 con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
 
1927                 var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
 
1928                 y = -r2 * Math.log(ts);
 
1930                 return new L.Point(x, y);
 
1933         unproject: function (point) { // (Point, Boolean) -> LatLng
 
1934                 var d = L.LatLng.RAD_TO_DEG,
 
1937                     lng = point.x * d / r,
 
1939                     eccent = Math.sqrt(1 - (tmp * tmp)),
 
1940                     ts = Math.exp(- point.y / r2),
 
1941                     phi = (Math.PI / 2) - 2 * Math.atan(ts),
 
1948                 while ((Math.abs(dphi) > tol) && (--i > 0)) {
 
1949                         con = eccent * Math.sin(phi);
 
1950                         dphi = (Math.PI / 2) - 2 * Math.atan(ts *
 
1951                                     Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
 
1955                 return new L.LatLng(phi * d, lng, true);
 
1961 L.CRS.EPSG3395 = L.extend({}, L.CRS, {
 
1964         projection: L.Projection.Mercator,
 
1966         transformation: (function () {
 
1967                 var m = L.Projection.Mercator,
 
1971                 return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
 
1977  * L.TileLayer is used for standard xyz-numbered tile layers.
 
1980 L.TileLayer = L.Class.extend({
 
1981         includes: L.Mixin.Events,
 
1992                 /* (undefined works too)
 
1995                 continuousWorld: false,
 
1998                 detectRetina: false,
 
2001                 unloadInvisibleTiles: L.Browser.mobile,
 
2002                 updateWhenIdle: L.Browser.mobile
 
2005         initialize: function (url, options) {
 
2006                 options = L.setOptions(this, options);
 
2008                 // detecting retina displays, adjusting tileSize and zoom levels
 
2009                 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
 
2011                         options.tileSize = Math.floor(options.tileSize / 2);
 
2012                         options.zoomOffset++;
 
2014                         if (options.minZoom > 0) {
 
2017                         this.options.maxZoom--;
 
2022                 var subdomains = this.options.subdomains;
 
2024                 if (typeof subdomains === 'string') {
 
2025                         this.options.subdomains = subdomains.split('');
 
2029         onAdd: function (map) {
 
2032                 // create a container div for tiles
 
2033                 this._initContainer();
 
2035                 // create an image to clone for tiles
 
2036                 this._createTileProto();
 
2040                         'viewreset': this._resetCallback,
 
2041                         'moveend': this._update
 
2044                 if (!this.options.updateWhenIdle) {
 
2045                         this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
 
2046                         map.on('move', this._limitedUpdate, this);
 
2053         addTo: function (map) {
 
2058         onRemove: function (map) {
 
2059                 map._panes.tilePane.removeChild(this._container);
 
2062                         'viewreset': this._resetCallback,
 
2063                         'moveend': this._update
 
2066                 if (!this.options.updateWhenIdle) {
 
2067                         map.off('move', this._limitedUpdate, this);
 
2070                 this._container = null;
 
2074         bringToFront: function () {
 
2075                 var pane = this._map._panes.tilePane;
 
2077                 if (this._container) {
 
2078                         pane.appendChild(this._container);
 
2079                         this._setAutoZIndex(pane, Math.max);
 
2085         bringToBack: function () {
 
2086                 var pane = this._map._panes.tilePane;
 
2088                 if (this._container) {
 
2089                         pane.insertBefore(this._container, pane.firstChild);
 
2090                         this._setAutoZIndex(pane, Math.min);
 
2096         getAttribution: function () {
 
2097                 return this.options.attribution;
 
2100         setOpacity: function (opacity) {
 
2101                 this.options.opacity = opacity;
 
2104                         this._updateOpacity();
 
2110         setZIndex: function (zIndex) {
 
2111                 this.options.zIndex = zIndex;
 
2112                 this._updateZIndex();
 
2117         setUrl: function (url, noRedraw) {
 
2127         redraw: function () {
 
2129                         this._map._panes.tilePane.empty = false;
 
2136         _updateZIndex: function () {
 
2137                 if (this._container && this.options.zIndex !== undefined) {
 
2138                         this._container.style.zIndex = this.options.zIndex;
 
2142         _setAutoZIndex: function (pane, compare) {
 
2144                 var layers = pane.getElementsByClassName('leaflet-layer'),
 
2145                     edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
 
2148                 for (i = 0, len = layers.length; i < len; i++) {
 
2150                         if (layers[i] !== this._container) {
 
2151                                 zIndex = parseInt(layers[i].style.zIndex, 10);
 
2153                                 if (!isNaN(zIndex)) {
 
2154                                         edgeZIndex = compare(edgeZIndex, zIndex);
 
2159                 this.options.zIndex = this._container.style.zIndex =
 
2160                         (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
 
2163         _updateOpacity: function () {
 
2164                 L.DomUtil.setOpacity(this._container, this.options.opacity);
 
2166                 // stupid webkit hack to force redrawing of tiles
 
2168                     tiles = this._tiles;
 
2170                 if (L.Browser.webkit) {
 
2172                                 if (tiles.hasOwnProperty(i)) {
 
2173                                         tiles[i].style.webkitTransform += ' translate(0,0)';
 
2179         _initContainer: function () {
 
2180                 var tilePane = this._map._panes.tilePane;
 
2182                 if (!this._container || tilePane.empty) {
 
2183                         this._container = L.DomUtil.create('div', 'leaflet-layer');
 
2185                         this._updateZIndex();
 
2187                         tilePane.appendChild(this._container);
 
2189                         if (this.options.opacity < 1) {
 
2190                                 this._updateOpacity();
 
2195         _resetCallback: function (e) {
 
2196                 this._reset(e.hard);
 
2199         _reset: function (clearOldContainer) {
 
2200                 var tiles = this._tiles;
 
2202                 for (var key in tiles) {
 
2203                         if (tiles.hasOwnProperty(key)) {
 
2204                                 this.fire('tileunload', {tile: tiles[key]});
 
2209                 this._tilesToLoad = 0;
 
2211                 if (this.options.reuseTiles) {
 
2212                         this._unusedTiles = [];
 
2215                 if (clearOldContainer && this._container) {
 
2216                         this._container.innerHTML = "";
 
2219                 this._initContainer();
 
2222         _update: function (e) {
 
2224                 if (!this._map) { return; }
 
2226                 var bounds = this._map.getPixelBounds(),
 
2227                     zoom = this._map.getZoom(),
 
2228                     tileSize = this.options.tileSize;
 
2230                 if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
 
2234                 var nwTilePoint = new L.Point(
 
2235                         Math.floor(bounds.min.x / tileSize),
 
2236                         Math.floor(bounds.min.y / tileSize)),
 
2238                     seTilePoint = new L.Point(
 
2239                         Math.floor(bounds.max.x / tileSize),
 
2240                         Math.floor(bounds.max.y / tileSize)),
 
2242                     tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
 
2244                 this._addTilesFromCenterOut(tileBounds);
 
2246                 if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
 
2247                         this._removeOtherTiles(tileBounds);
 
2251         _addTilesFromCenterOut: function (bounds) {
 
2253                     center = bounds.getCenter();
 
2257                 for (j = bounds.min.y; j <= bounds.max.y; j++) {
 
2258                         for (i = bounds.min.x; i <= bounds.max.x; i++) {
 
2259                                 point = new L.Point(i, j);
 
2261                                 if (this._tileShouldBeLoaded(point)) {
 
2267                 var tilesToLoad = queue.length;
 
2269                 if (tilesToLoad === 0) { return; }
 
2271                 // load tiles in order of their distance to center
 
2272                 queue.sort(function (a, b) {
 
2273                         return a.distanceTo(center) - b.distanceTo(center);
 
2276                 var fragment = document.createDocumentFragment();
 
2278                 // if its the first batch of tiles to load
 
2279                 if (!this._tilesToLoad) {
 
2280                         this.fire('loading');
 
2283                 this._tilesToLoad += tilesToLoad;
 
2285                 for (i = 0; i < tilesToLoad; i++) {
 
2286                         this._addTile(queue[i], fragment);
 
2289                 this._container.appendChild(fragment);
 
2292         _tileShouldBeLoaded: function (tilePoint) {
 
2293                 if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
 
2294                         return false; // already loaded
 
2297                 if (!this.options.continuousWorld) {
 
2298                         var limit = this._getWrapTileNum();
 
2300                         if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
 
2301                                                         tilePoint.y < 0 || tilePoint.y >= limit) {
 
2302                                 return false; // exceeds world bounds
 
2309         _removeOtherTiles: function (bounds) {
 
2310                 var kArr, x, y, key;
 
2312                 for (key in this._tiles) {
 
2313                         if (this._tiles.hasOwnProperty(key)) {
 
2314                                 kArr = key.split(':');
 
2315                                 x = parseInt(kArr[0], 10);
 
2316                                 y = parseInt(kArr[1], 10);
 
2318                                 // remove tile if it's out of bounds
 
2319                                 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
 
2320                                         this._removeTile(key);
 
2326         _removeTile: function (key) {
 
2327                 var tile = this._tiles[key];
 
2329                 this.fire("tileunload", {tile: tile, url: tile.src});
 
2331                 if (this.options.reuseTiles) {
 
2332                         L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
 
2333                         this._unusedTiles.push(tile);
 
2335                 } else if (tile.parentNode === this._container) {
 
2336                         this._container.removeChild(tile);
 
2339                 // for https://github.com/CloudMade/Leaflet/issues/137
 
2340                 if (!L.Browser.android) {
 
2341                         tile.src = L.Util.emptyImageUrl;
 
2344                 delete this._tiles[key];
 
2347         _addTile: function (tilePoint, container) {
 
2348                 var tilePos = this._getTilePos(tilePoint);
 
2350                 // get unused tile - or create a new tile
 
2351                 var tile = this._getTile();
 
2354                 Chrome 20 layouts much faster with top/left (verify with timeline, frames)
 
2355                 Android 4 browser has display issues with top/left and requires transform instead
 
2356                 Android 3 browser not tested
 
2357                 Android 2 browser requires top/left or tiles disappear on load or first drag
 
2358                 (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
 
2359                 (other browsers don't currently care) - see debug/hacks/jitter.html for an example
 
2361                 L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
 
2363                 this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
 
2365                 this._loadTile(tile, tilePoint);
 
2367                 if (tile.parentNode !== this._container) {
 
2368                         container.appendChild(tile);
 
2372         _getZoomForUrl: function () {
 
2374                 var options = this.options,
 
2375                     zoom = this._map.getZoom();
 
2377                 if (options.zoomReverse) {
 
2378                         zoom = options.maxZoom - zoom;
 
2381                 return zoom + options.zoomOffset;
 
2384         _getTilePos: function (tilePoint) {
 
2385                 var origin = this._map.getPixelOrigin(),
 
2386                     tileSize = this.options.tileSize;
 
2388                 return tilePoint.multiplyBy(tileSize).subtract(origin);
 
2391         // image-specific code (override to implement e.g. Canvas or SVG tile layer)
 
2393         getTileUrl: function (tilePoint) {
 
2394                 this._adjustTilePoint(tilePoint);
 
2396                 return L.Util.template(this._url, L.extend({
 
2397                         s: this._getSubdomain(tilePoint),
 
2398                         z: this._getZoomForUrl(),
 
2404         _getWrapTileNum: function () {
 
2405                 // TODO refactor, limit is not valid for non-standard projections
 
2406                 return Math.pow(2, this._getZoomForUrl());
 
2409         _adjustTilePoint: function (tilePoint) {
 
2411                 var limit = this._getWrapTileNum();
 
2413                 // wrap tile coordinates
 
2414                 if (!this.options.continuousWorld && !this.options.noWrap) {
 
2415                         tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
 
2418                 if (this.options.tms) {
 
2419                         tilePoint.y = limit - tilePoint.y - 1;
 
2423         _getSubdomain: function (tilePoint) {
 
2424                 var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
 
2425                 return this.options.subdomains[index];
 
2428         _createTileProto: function () {
 
2429                 var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
 
2430                 img.style.width = img.style.height = this.options.tileSize + 'px';
 
2431                 img.galleryimg = 'no';
 
2434         _getTile: function () {
 
2435                 if (this.options.reuseTiles && this._unusedTiles.length > 0) {
 
2436                         var tile = this._unusedTiles.pop();
 
2437                         this._resetTile(tile);
 
2440                 return this._createTile();
 
2443         _resetTile: function (tile) {
 
2444                 // Override if data stored on a tile needs to be cleaned up before reuse
 
2447         _createTile: function () {
 
2448                 var tile = this._tileImg.cloneNode(false);
 
2449                 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
 
2453         _loadTile: function (tile, tilePoint) {
 
2455                 tile.onload  = this._tileOnLoad;
 
2456                 tile.onerror = this._tileOnError;
 
2458                 tile.src     = this.getTileUrl(tilePoint);
 
2461     _tileLoaded: function () {
 
2462         this._tilesToLoad--;
 
2463         if (!this._tilesToLoad) {
 
2468         _tileOnLoad: function (e) {
 
2469                 var layer = this._layer;
 
2471                 //Only if we are loading an actual image
 
2472                 if (this.src !== L.Util.emptyImageUrl) {
 
2473                         L.DomUtil.addClass(this, 'leaflet-tile-loaded');
 
2475                         layer.fire('tileload', {
 
2481                 layer._tileLoaded();
 
2484         _tileOnError: function (e) {
 
2485                 var layer = this._layer;
 
2487                 layer.fire('tileerror', {
 
2492                 var newUrl = layer.options.errorTileUrl;
 
2497         layer._tileLoaded();
 
2501 L.tileLayer = function (url, options) {
 
2502         return new L.TileLayer(url, options);
 
2506 L.TileLayer.WMS = L.TileLayer.extend({
 
2514                 format: 'image/jpeg',
 
2518         initialize: function (url, options) { // (String, Object)
 
2522                 var wmsParams = L.extend({}, this.defaultWmsParams);
 
2524                 if (options.detectRetina && L.Browser.retina) {
 
2525                         wmsParams.width = wmsParams.height = this.options.tileSize * 2;
 
2527                         wmsParams.width = wmsParams.height = this.options.tileSize;
 
2530                 for (var i in options) {
 
2531                         // all keys that are not TileLayer options go to WMS params
 
2532                         if (!this.options.hasOwnProperty(i)) {
 
2533                                 wmsParams[i] = options[i];
 
2537                 this.wmsParams = wmsParams;
 
2539                 L.setOptions(this, options);
 
2542         onAdd: function (map) {
 
2544                 var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
 
2545                 this.wmsParams[projectionKey] = map.options.crs.code;
 
2547                 L.TileLayer.prototype.onAdd.call(this, map);
 
2550         getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
 
2552                 var map = this._map,
 
2553                     crs = map.options.crs,
 
2554                     tileSize = this.options.tileSize,
 
2556                     nwPoint = tilePoint.multiplyBy(tileSize),
 
2557                     sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
 
2559                     nw = crs.project(map.unproject(nwPoint, zoom)),
 
2560                     se = crs.project(map.unproject(sePoint, zoom)),
 
2562                     bbox = [nw.x, se.y, se.x, nw.y].join(','),
 
2564                     url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
 
2566                 return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
 
2569         setParams: function (params, noRedraw) {
 
2571                 L.extend(this.wmsParams, params);
 
2581 L.tileLayer.wms = function (url, options) {
 
2582         return new L.TileLayer.WMS(url, options);
 
2586 L.TileLayer.Canvas = L.TileLayer.extend({
 
2591         initialize: function (options) {
 
2592                 L.setOptions(this, options);
 
2595         redraw: function () {
 
2596                 var tiles = this._tiles;
 
2598                 for (var i in tiles) {
 
2599                         if (tiles.hasOwnProperty(i)) {
 
2600                                 this._redrawTile(tiles[i]);
 
2605         _redrawTile: function (tile) {
 
2606                 this.drawTile(tile, tile._tilePoint, this._map._zoom);
 
2609         _createTileProto: function () {
 
2610                 var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
 
2611                 proto.width = proto.height = this.options.tileSize;
 
2614         _createTile: function () {
 
2615                 var tile = this._canvasProto.cloneNode(false);
 
2616                 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
 
2620         _loadTile: function (tile, tilePoint) {
 
2622                 tile._tilePoint = tilePoint;
 
2624                 this._redrawTile(tile);
 
2626                 if (!this.options.async) {
 
2627                         this.tileDrawn(tile);
 
2631         drawTile: function (tile, tilePoint) {
 
2632                 // override with rendering code
 
2635         tileDrawn: function (tile) {
 
2636                 this._tileOnLoad.call(tile);
 
2641 L.tileLayer.canvas = function (options) {
 
2642         return new L.TileLayer.Canvas(options);
 
2646 L.ImageOverlay = L.Class.extend({
 
2647         includes: L.Mixin.Events,
 
2653         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
 
2655                 this._bounds = L.latLngBounds(bounds);
 
2657                 L.setOptions(this, options);
 
2660         onAdd: function (map) {
 
2667                 map._panes.overlayPane.appendChild(this._image);
 
2669                 map.on('viewreset', this._reset, this);
 
2671                 if (map.options.zoomAnimation && L.Browser.any3d) {
 
2672                         map.on('zoomanim', this._animateZoom, this);
 
2678         onRemove: function (map) {
 
2679                 map.getPanes().overlayPane.removeChild(this._image);
 
2681                 map.off('viewreset', this._reset, this);
 
2683                 if (map.options.zoomAnimation) {
 
2684                         map.off('zoomanim', this._animateZoom, this);
 
2688         addTo: function (map) {
 
2693         setOpacity: function (opacity) {
 
2694                 this.options.opacity = opacity;
 
2695                 this._updateOpacity();
 
2699         // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
 
2700         bringToFront: function () {
 
2702                         this._map._panes.overlayPane.appendChild(this._image);
 
2707         bringToBack: function () {
 
2708                 var pane = this._map._panes.overlayPane;
 
2710                         pane.insertBefore(this._image, pane.firstChild);
 
2715         _initImage: function () {
 
2716                 this._image = L.DomUtil.create('img', 'leaflet-image-layer');
 
2718                 if (this._map.options.zoomAnimation && L.Browser.any3d) {
 
2719                         L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
 
2721                         L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
 
2724                 this._updateOpacity();
 
2726                 //TODO createImage util method to remove duplication
 
2727                 L.extend(this._image, {
 
2729                         onselectstart: L.Util.falseFn,
 
2730                         onmousemove: L.Util.falseFn,
 
2731                         onload: L.bind(this._onImageLoad, this),
 
2736         _animateZoom: function (e) {
 
2737                 var map = this._map,
 
2738                     image = this._image,
 
2739                     scale = map.getZoomScale(e.zoom),
 
2740                     nw = this._bounds.getNorthWest(),
 
2741                     se = this._bounds.getSouthEast(),
 
2743                     topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
 
2744                     size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
 
2745                     currentSize = map.latLngToLayerPoint(se)._subtract(map.latLngToLayerPoint(nw)),
 
2746                     origin = topLeft._add(size._subtract(currentSize)._divideBy(2));
 
2748                 image.style[L.DomUtil.TRANSFORM] =
 
2749                         L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
 
2752         _reset: function () {
 
2753                 var image   = this._image,
 
2754                     topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
 
2755                     size    = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
 
2757                 L.DomUtil.setPosition(image, topLeft);
 
2759                 image.style.width  = size.x + 'px';
 
2760                 image.style.height = size.y + 'px';
 
2763         _onImageLoad: function () {
 
2767         _updateOpacity: function () {
 
2768                 L.DomUtil.setOpacity(this._image, this.options.opacity);
 
2772 L.imageOverlay = function (url, bounds, options) {
 
2773         return new L.ImageOverlay(url, bounds, options);
 
2777 L.Icon = L.Class.extend({
 
2780                 iconUrl: (String) (required)
 
2781                 iconSize: (Point) (can be set through CSS)
 
2782                 iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
 
2783                 popupAnchor: (Point) (if not specified, popup opens in the anchor point)
 
2784                 shadowUrl: (Point) (no shadow by default)
 
2786                 shadowAnchor: (Point)
 
2791         initialize: function (options) {
 
2792                 L.setOptions(this, options);
 
2795         createIcon: function () {
 
2796                 return this._createIcon('icon');
 
2799         createShadow: function () {
 
2800                 return this._createIcon('shadow');
 
2803         _createIcon: function (name) {
 
2804                 var src = this._getIconUrl(name);
 
2807                         if (name === 'icon') {
 
2808                                 throw new Error("iconUrl not set in Icon options (see the docs).");
 
2813                 var img = this._createImg(src);
 
2814                 this._setIconStyles(img, name);
 
2819         _setIconStyles: function (img, name) {
 
2820                 var options = this.options,
 
2821                     size = L.point(options[name + 'Size']),
 
2824                 if (name === 'shadow') {
 
2825                         anchor = L.point(options.shadowAnchor || options.iconAnchor);
 
2827                         anchor = L.point(options.iconAnchor);
 
2830                 if (!anchor && size) {
 
2831                         anchor = size.divideBy(2, true);
 
2834                 img.className = 'leaflet-marker-' + name + ' ' + options.className;
 
2837                         img.style.marginLeft = (-anchor.x) + 'px';
 
2838                         img.style.marginTop  = (-anchor.y) + 'px';
 
2842                         img.style.width  = size.x + 'px';
 
2843                         img.style.height = size.y + 'px';
 
2847         _createImg: function (src) {
 
2850                 if (!L.Browser.ie6) {
 
2851                         el = document.createElement('img');
 
2854                         el = document.createElement('div');
 
2856                                 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
 
2861         _getIconUrl: function (name) {
 
2862                 return this.options[name + 'Url'];
 
2866 L.icon = function (options) {
 
2867         return new L.Icon(options);
 
2872 L.Icon.Default = L.Icon.extend({
 
2875                 iconSize: new L.Point(25, 41),
 
2876                 iconAnchor: new L.Point(12, 41),
 
2877                 popupAnchor: new L.Point(1, -34),
 
2879                 shadowSize: new L.Point(41, 41)
 
2882         _getIconUrl: function (name) {
 
2883                 var key = name + 'Url';
 
2885                 if (this.options[key]) {
 
2886                         return this.options[key];
 
2889                 var path = L.Icon.Default.imagePath;
 
2892                         throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
 
2895                 return path + '/marker-' + name + '.png';
 
2899 L.Icon.Default.imagePath = (function () {
 
2900         var scripts = document.getElementsByTagName('script'),
 
2901             leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
 
2903         var i, len, src, matches;
 
2905         for (i = 0, len = scripts.length; i < len; i++) {
 
2906                 src = scripts[i].src;
 
2907                 matches = src.match(leafletRe);
 
2910                         return src.split(leafletRe)[0] + '/images';
 
2917  * L.Marker is used to display clickable/draggable icons on the map.
 
2920 L.Marker = L.Class.extend({
 
2922         includes: L.Mixin.Events,
 
2925                 icon: new L.Icon.Default(),
 
2935         initialize: function (latlng, options) {
 
2936                 L.setOptions(this, options);
 
2937                 this._latlng = L.latLng(latlng);
 
2940         onAdd: function (map) {
 
2943                 map.on('viewreset', this.update, this);
 
2948                 if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
 
2949                         map.on('zoomanim', this._animateZoom, this);
 
2953         addTo: function (map) {
 
2958         onRemove: function (map) {
 
2961                 this.fire('remove');
 
2964                         'viewreset': this.update,
 
2965                         'zoomanim': this._animateZoom
 
2971         getLatLng: function () {
 
2972                 return this._latlng;
 
2975         setLatLng: function (latlng) {
 
2976                 this._latlng = L.latLng(latlng);
 
2980                 this.fire('move', { latlng: this._latlng });
 
2983         setZIndexOffset: function (offset) {
 
2984                 this.options.zIndexOffset = offset;
 
2988         setIcon: function (icon) {
 
2993                 this.options.icon = icon;
 
3001         update: function () {
 
3002                 if (!this._icon) { return; }
 
3004                 var pos = this._map.latLngToLayerPoint(this._latlng).round();
 
3008         _initIcon: function () {
 
3009                 var options = this.options,
 
3011                     animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
 
3012                     classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
 
3013                     needOpacityUpdate = false;
 
3016                         this._icon = options.icon.createIcon();
 
3018                         if (options.title) {
 
3019                                 this._icon.title = options.title;
 
3022                         this._initInteraction();
 
3023                         needOpacityUpdate = (this.options.opacity < 1);
 
3025                         L.DomUtil.addClass(this._icon, classToAdd);
 
3027                         if (options.riseOnHover) {
 
3029                                         .on(this._icon, 'mouseover', this._bringToFront, this)
 
3030                                         .on(this._icon, 'mouseout', this._resetZIndex, this);
 
3034                 if (!this._shadow) {
 
3035                         this._shadow = options.icon.createShadow();
 
3038                                 L.DomUtil.addClass(this._shadow, classToAdd);
 
3039                                 needOpacityUpdate = (this.options.opacity < 1);
 
3043                 if (needOpacityUpdate) {
 
3044                         this._updateOpacity();
 
3047                 var panes = this._map._panes;
 
3049                 panes.markerPane.appendChild(this._icon);
 
3052                         panes.shadowPane.appendChild(this._shadow);
 
3056         _removeIcon: function () {
 
3057                 var panes = this._map._panes;
 
3059                 if (this.options.riseOnHover) {
 
3061                             .off(this._icon, 'mouseover', this._bringToFront)
 
3062                             .off(this._icon, 'mouseout', this._resetZIndex);
 
3065                 panes.markerPane.removeChild(this._icon);
 
3068                         panes.shadowPane.removeChild(this._shadow);
 
3071                 this._icon = this._shadow = null;
 
3074         _setPos: function (pos) {
 
3075                 L.DomUtil.setPosition(this._icon, pos);
 
3078                         L.DomUtil.setPosition(this._shadow, pos);
 
3081                 this._zIndex = pos.y + this.options.zIndexOffset;
 
3083                 this._resetZIndex();
 
3086         _updateZIndex: function (offset) {
 
3087                 this._icon.style.zIndex = this._zIndex + offset;
 
3090         _animateZoom: function (opt) {
 
3091                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
 
3096         _initInteraction: function () {
 
3097                 if (!this.options.clickable) {
 
3101                 var icon = this._icon,
 
3102                     events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
 
3104                 L.DomUtil.addClass(icon, 'leaflet-clickable');
 
3105                 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
 
3107                 for (var i = 0; i < events.length; i++) {
 
3108                         L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
 
3111                 if (L.Handler.MarkerDrag) {
 
3112                         this.dragging = new L.Handler.MarkerDrag(this);
 
3114                         if (this.options.draggable) {
 
3115                                 this.dragging.enable();
 
3120         _onMouseClick: function (e) {
 
3121                 var wasDragged = this.dragging && this.dragging.moved();
 
3122                 if (this.hasEventListeners(e.type) || wasDragged) {
 
3123                         L.DomEvent.stopPropagation(e);
 
3125                 if (wasDragged) { return; }
 
3126                 if (this._map.dragging && this._map.dragging.moved()) { return; }
 
3132         _fireMouseEvent: function (e) {
 
3136                 if (e.type !== 'mousedown') {
 
3137                         L.DomEvent.stopPropagation(e);
 
3141         setOpacity: function (opacity) {
 
3142                 this.options.opacity = opacity;
 
3144                         this._updateOpacity();
 
3148         _updateOpacity: function () {
 
3149                 L.DomUtil.setOpacity(this._icon, this.options.opacity);
 
3151                         L.DomUtil.setOpacity(this._shadow, this.options.opacity);
 
3155         _bringToFront: function () {
 
3156                 this._updateZIndex(this.options.riseOffset);
 
3159         _resetZIndex: function () {
 
3160                 this._updateZIndex(0);
 
3164 L.marker = function (latlng, options) {
 
3165         return new L.Marker(latlng, options);
 
3169 L.DivIcon = L.Icon.extend({
 
3171                 iconSize: new L.Point(12, 12), // also can be set through CSS
 
3174                 popupAnchor: (Point)
 
3178                 className: 'leaflet-div-icon'
 
3181         createIcon: function () {
 
3182                 var div = document.createElement('div'),
 
3183                     options = this.options;
 
3186                         div.innerHTML = options.html;
 
3189                 if (options.bgPos) {
 
3190                         div.style.backgroundPosition =
 
3191                                 (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
 
3194                 this._setIconStyles(div, 'icon');
 
3198         createShadow: function () {
 
3203 L.divIcon = function (options) {
 
3204         return new L.DivIcon(options);
 
3209 L.Map.mergeOptions({
 
3210         closePopupOnClick: true
 
3213 L.Popup = L.Class.extend({
 
3214         includes: L.Mixin.Events,
 
3222                 offset: new L.Point(0, 6),
 
3223                 autoPanPadding: new L.Point(5, 5),
 
3227         initialize: function (options, source) {
 
3228                 L.setOptions(this, options);
 
3230                 this._source = source;
 
3233         onAdd: function (map) {
 
3236                 if (!this._container) {
 
3239                 this._updateContent();
 
3241                 var animFade = map.options.fadeAnimation;
 
3244                         L.DomUtil.setOpacity(this._container, 0);
 
3246                 map._panes.popupPane.appendChild(this._container);
 
3248                 map.on('viewreset', this._updatePosition, this);
 
3250                 if (L.Browser.any3d) {
 
3251                         map.on('zoomanim', this._zoomAnimation, this);
 
3254                 if (map.options.closePopupOnClick) {
 
3255                         map.on('preclick', this._close, this);
 
3261                         L.DomUtil.setOpacity(this._container, 1);
 
3265         addTo: function (map) {
 
3270         openOn: function (map) {
 
3271                 map.openPopup(this);
 
3275         onRemove: function (map) {
 
3276                 map._panes.popupPane.removeChild(this._container);
 
3278                 L.Util.falseFn(this._container.offsetWidth); // force reflow
 
3281                         viewreset: this._updatePosition,
 
3282                         preclick: this._close,
 
3283                         zoomanim: this._zoomAnimation
 
3286                 if (map.options.fadeAnimation) {
 
3287                         L.DomUtil.setOpacity(this._container, 0);
 
3293         setLatLng: function (latlng) {
 
3294                 this._latlng = L.latLng(latlng);
 
3299         setContent: function (content) {
 
3300                 this._content = content;
 
3305         _close: function () {
 
3306                 var map = this._map;
 
3313                             .fire('popupclose', {popup: this});
 
3317         _initLayout: function () {
 
3318                 var prefix = 'leaflet-popup',
 
3319                         containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-animated',
 
3320                         container = this._container = L.DomUtil.create('div', containerClass),
 
3323                 if (this.options.closeButton) {
 
3324                         closeButton = this._closeButton =
 
3325                                 L.DomUtil.create('a', prefix + '-close-button', container);
 
3326                         closeButton.href = '#close';
 
3327                         closeButton.innerHTML = '×';
 
3329                         L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
 
3332                 var wrapper = this._wrapper =
 
3333                         L.DomUtil.create('div', prefix + '-content-wrapper', container);
 
3334                 L.DomEvent.disableClickPropagation(wrapper);
 
3336                 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
 
3337                 L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
 
3339                 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
 
3340                 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
 
3343         _update: function () {
 
3344                 if (!this._map) { return; }
 
3346                 this._container.style.visibility = 'hidden';
 
3348                 this._updateContent();
 
3349                 this._updateLayout();
 
3350                 this._updatePosition();
 
3352                 this._container.style.visibility = '';
 
3357         _updateContent: function () {
 
3358                 if (!this._content) { return; }
 
3360                 if (typeof this._content === 'string') {
 
3361                         this._contentNode.innerHTML = this._content;
 
3363                         while (this._contentNode.hasChildNodes()) {
 
3364                                 this._contentNode.removeChild(this._contentNode.firstChild);
 
3366                         this._contentNode.appendChild(this._content);
 
3368                 this.fire('contentupdate');
 
3371         _updateLayout: function () {
 
3372                 var container = this._contentNode,
 
3373                     style = container.style;
 
3376                 style.whiteSpace = 'nowrap';
 
3378                 var width = container.offsetWidth;
 
3379                 width = Math.min(width, this.options.maxWidth);
 
3380                 width = Math.max(width, this.options.minWidth);
 
3382                 style.width = (width + 1) + 'px';
 
3383                 style.whiteSpace = '';
 
3387                 var height = container.offsetHeight,
 
3388                     maxHeight = this.options.maxHeight,
 
3389                     scrolledClass = 'leaflet-popup-scrolled';
 
3391                 if (maxHeight && height > maxHeight) {
 
3392                         style.height = maxHeight + 'px';
 
3393                         L.DomUtil.addClass(container, scrolledClass);
 
3395                         L.DomUtil.removeClass(container, scrolledClass);
 
3398                 this._containerWidth = this._container.offsetWidth;
 
3401         _updatePosition: function () {
 
3402                 if (!this._map) { return; }
 
3404                 var pos = this._map.latLngToLayerPoint(this._latlng),
 
3405                     is3d = L.Browser.any3d,
 
3406                     offset = this.options.offset;
 
3409                         L.DomUtil.setPosition(this._container, pos);
 
3412                 this._containerBottom = -offset.y - (is3d ? 0 : pos.y);
 
3413                 this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x);
 
3415                 //Bottom position the popup in case the height of the popup changes (images loading etc)
 
3416                 this._container.style.bottom = this._containerBottom + 'px';
 
3417                 this._container.style.left = this._containerLeft + 'px';
 
3420         _zoomAnimation: function (opt) {
 
3421                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
 
3423                 L.DomUtil.setPosition(this._container, pos);
 
3426         _adjustPan: function () {
 
3427                 if (!this.options.autoPan) { return; }
 
3429                 var map = this._map,
 
3430                     containerHeight = this._container.offsetHeight,
 
3431                     containerWidth = this._containerWidth,
 
3433                     layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
 
3435                 if (L.Browser.any3d) {
 
3436                         layerPos._add(L.DomUtil.getPosition(this._container));
 
3439                 var containerPos = map.layerPointToContainerPoint(layerPos),
 
3440                     padding = this.options.autoPanPadding,
 
3441                     size = map.getSize(),
 
3445                 if (containerPos.x < 0) {
 
3446                         dx = containerPos.x - padding.x;
 
3448                 if (containerPos.x + containerWidth > size.x) {
 
3449                         dx = containerPos.x + containerWidth - size.x + padding.x;
 
3451                 if (containerPos.y < 0) {
 
3452                         dy = containerPos.y - padding.y;
 
3454                 if (containerPos.y + containerHeight > size.y) {
 
3455                         dy = containerPos.y + containerHeight - size.y + padding.y;
 
3459                         map.panBy(new L.Point(dx, dy));
 
3463         _onCloseButtonClick: function (e) {
 
3469 L.popup = function (options, source) {
 
3470         return new L.Popup(options, source);
 
3475  * Popup extension to L.Marker, adding openPopup & bindPopup methods.
 
3479         openPopup: function () {
 
3480                 if (this._popup && this._map) {
 
3481                         this._popup.setLatLng(this._latlng);
 
3482                         this._map.openPopup(this._popup);
 
3488         closePopup: function () {
 
3490                         this._popup._close();
 
3495         bindPopup: function (content, options) {
 
3496                 var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
 
3498                 anchor = anchor.add(L.Popup.prototype.options.offset);
 
3500                 if (options && options.offset) {
 
3501                         anchor = anchor.add(options.offset);
 
3504                 options = L.extend({offset: anchor}, options);
 
3508                             .on('click', this.openPopup, this)
 
3509                             .on('remove', this.closePopup, this)
 
3510                             .on('move', this._movePopup, this);
 
3513                 this._popup = new L.Popup(options, this)
 
3514                         .setContent(content);
 
3519         unbindPopup: function () {
 
3523                             .off('click', this.openPopup)
 
3524                             .off('remove', this.closePopup)
 
3525                             .off('move', this._movePopup);
 
3530         _movePopup: function (e) {
 
3531                 this._popup.setLatLng(e.latlng);
 
3538         openPopup: function (popup) {
 
3541                 this._popup = popup;
 
3545                     .fire('popupopen', {popup: this._popup});
 
3548         closePopup: function () {
 
3550                         this._popup._close();
 
3558  * L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer.
 
3561 L.LayerGroup = L.Class.extend({
 
3562         initialize: function (layers) {
 
3568                         for (i = 0, len = layers.length; i < len; i++) {
 
3569                                 this.addLayer(layers[i]);
 
3574         addLayer: function (layer) {
 
3575                 var id = L.stamp(layer);
 
3577                 this._layers[id] = layer;
 
3580                         this._map.addLayer(layer);
 
3586         removeLayer: function (layer) {
 
3587                 var id = L.stamp(layer);
 
3589                 delete this._layers[id];
 
3592                         this._map.removeLayer(layer);
 
3598         clearLayers: function () {
 
3599                 this.eachLayer(this.removeLayer, this);
 
3603         invoke: function (methodName) {
 
3604                 var args = Array.prototype.slice.call(arguments, 1),
 
3607                 for (i in this._layers) {
 
3608                         if (this._layers.hasOwnProperty(i)) {
 
3609                                 layer = this._layers[i];
 
3611                                 if (layer[methodName]) {
 
3612                                         layer[methodName].apply(layer, args);
 
3620         onAdd: function (map) {
 
3622                 this.eachLayer(map.addLayer, map);
 
3625         onRemove: function (map) {
 
3626                 this.eachLayer(map.removeLayer, map);
 
3630         addTo: function (map) {
 
3635         eachLayer: function (method, context) {
 
3636                 for (var i in this._layers) {
 
3637                         if (this._layers.hasOwnProperty(i)) {
 
3638                                 method.call(context, this._layers[i]);
 
3644 L.layerGroup = function (layers) {
 
3645         return new L.LayerGroup(layers);
 
3650  * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
 
3653 L.FeatureGroup = L.LayerGroup.extend({
 
3654         includes: L.Mixin.Events,
 
3657                 EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu'
 
3660         addLayer: function (layer) {
 
3661                 if (this._layers[L.stamp(layer)]) {
 
3665                 layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
 
3667                 L.LayerGroup.prototype.addLayer.call(this, layer);
 
3669                 if (this._popupContent && layer.bindPopup) {
 
3670                         layer.bindPopup(this._popupContent);
 
3673                 return this.fire('layeradd', {layer: layer});
 
3676         removeLayer: function (layer) {
 
3677                 layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
 
3679                 L.LayerGroup.prototype.removeLayer.call(this, layer);
 
3682                 if (this._popupContent) {
 
3683                         this.invoke('unbindPopup');
 
3686                 return this.fire('layerremove', {layer: layer});
 
3689         bindPopup: function (content) {
 
3690                 this._popupContent = content;
 
3691                 return this.invoke('bindPopup', content);
 
3694         setStyle: function (style) {
 
3695                 return this.invoke('setStyle', style);
 
3698         bringToFront: function () {
 
3699                 return this.invoke('bringToFront');
 
3702         bringToBack: function () {
 
3703                 return this.invoke('bringToBack');
 
3706         getBounds: function () {
 
3707                 var bounds = new L.LatLngBounds();
 
3709                 this.eachLayer(function (layer) {
 
3710                         bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
 
3716         _propagateEvent: function (e) {
 
3720                 this.fire(e.type, e);
 
3724 L.featureGroup = function (layers) {
 
3725         return new L.FeatureGroup(layers);
 
3730  * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
 
3733 L.Path = L.Class.extend({
 
3734         includes: [L.Mixin.Events],
 
3737                 // how much to extend the clip area around the map view
 
3738                 // (relative to its size, e.g. 0.5 is half the screen in each direction)
 
3739                 // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
 
3740                 CLIP_PADDING: L.Browser.mobile ?
 
3741                         Math.max(0, Math.min(0.5,
 
3742                                 (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5
 
3753                 fillColor: null, //same as color by default
 
3759         initialize: function (options) {
 
3760                 L.setOptions(this, options);
 
3763         onAdd: function (map) {
 
3766                 if (!this._container) {
 
3767                         this._initElements();
 
3771                 this.projectLatlngs();
 
3774                 if (this._container) {
 
3775                         this._map._pathRoot.appendChild(this._container);
 
3779                         'viewreset': this.projectLatlngs,
 
3780                         'moveend': this._updatePath
 
3784         addTo: function (map) {
 
3789         onRemove: function (map) {
 
3790                 map._pathRoot.removeChild(this._container);
 
3794                 if (L.Browser.vml) {
 
3795                         this._container = null;
 
3796                         this._stroke = null;
 
3800                 this.fire('remove');
 
3803                         'viewreset': this.projectLatlngs,
 
3804                         'moveend': this._updatePath
 
3808         projectLatlngs: function () {
 
3809                 // do all projection stuff here
 
3812         setStyle: function (style) {
 
3813                 L.setOptions(this, style);
 
3815                 if (this._container) {
 
3816                         this._updateStyle();
 
3822         redraw: function () {
 
3824                         this.projectLatlngs();
 
3832         _updatePathViewport: function () {
 
3833                 var p = L.Path.CLIP_PADDING,
 
3834                     size = this.getSize(),
 
3835                     panePos = L.DomUtil.getPosition(this._mapPane),
 
3836                     min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
 
3837                     max = min.add(size.multiplyBy(1 + p * 2)._round());
 
3839                 this._pathViewport = new L.Bounds(min, max);
 
3844 L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
 
3846 L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
 
3848 L.Path = L.Path.extend({
 
3853         bringToFront: function () {
 
3854                 var root = this._map._pathRoot,
 
3855                     path = this._container;
 
3857                 if (path && root.lastChild !== path) {
 
3858                         root.appendChild(path);
 
3863         bringToBack: function () {
 
3864                 var root = this._map._pathRoot,
 
3865                     path = this._container,
 
3866                     first = root.firstChild;
 
3868                 if (path && first !== path) {
 
3869                         root.insertBefore(path, first);
 
3874         getPathString: function () {
 
3875                 // form path string here
 
3878         _createElement: function (name) {
 
3879                 return document.createElementNS(L.Path.SVG_NS, name);
 
3882         _initElements: function () {
 
3883                 this._map._initPathRoot();
 
3888         _initPath: function () {
 
3889                 this._container = this._createElement('g');
 
3891                 this._path = this._createElement('path');
 
3892                 this._container.appendChild(this._path);
 
3895         _initStyle: function () {
 
3896                 if (this.options.stroke) {
 
3897                         this._path.setAttribute('stroke-linejoin', 'round');
 
3898                         this._path.setAttribute('stroke-linecap', 'round');
 
3900                 if (this.options.fill) {
 
3901                         this._path.setAttribute('fill-rule', 'evenodd');
 
3903                 this._updateStyle();
 
3906         _updateStyle: function () {
 
3907                 if (this.options.stroke) {
 
3908                         this._path.setAttribute('stroke', this.options.color);
 
3909                         this._path.setAttribute('stroke-opacity', this.options.opacity);
 
3910                         this._path.setAttribute('stroke-width', this.options.weight);
 
3911                         if (this.options.dashArray) {
 
3912                                 this._path.setAttribute('stroke-dasharray', this.options.dashArray);
 
3914                                 this._path.removeAttribute('stroke-dasharray');
 
3917                         this._path.setAttribute('stroke', 'none');
 
3919                 if (this.options.fill) {
 
3920                         this._path.setAttribute('fill', this.options.fillColor || this.options.color);
 
3921                         this._path.setAttribute('fill-opacity', this.options.fillOpacity);
 
3923                         this._path.setAttribute('fill', 'none');
 
3927         _updatePath: function () {
 
3928                 var str = this.getPathString();
 
3930                         // fix webkit empty string parsing bug
 
3933                 this._path.setAttribute('d', str);
 
3936         // TODO remove duplication with L.Map
 
3937         _initEvents: function () {
 
3938                 if (this.options.clickable) {
 
3939                         if (L.Browser.svg || !L.Browser.vml) {
 
3940                                 this._path.setAttribute('class', 'leaflet-clickable');
 
3943                         L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
 
3945                         var events = ['dblclick', 'mousedown', 'mouseover',
 
3946                                       'mouseout', 'mousemove', 'contextmenu'];
 
3947                         for (var i = 0; i < events.length; i++) {
 
3948                                 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
 
3953         _onMouseClick: function (e) {
 
3954                 if (this._map.dragging && this._map.dragging.moved()) { return; }
 
3956                 this._fireMouseEvent(e);
 
3959         _fireMouseEvent: function (e) {
 
3960                 if (!this.hasEventListeners(e.type)) { return; }
 
3962                 var map = this._map,
 
3963                     containerPoint = map.mouseEventToContainerPoint(e),
 
3964                     layerPoint = map.containerPointToLayerPoint(containerPoint),
 
3965                     latlng = map.layerPointToLatLng(layerPoint);
 
3969                         layerPoint: layerPoint,
 
3970                         containerPoint: containerPoint,
 
3974                 if (e.type === 'contextmenu') {
 
3975                         L.DomEvent.preventDefault(e);
 
3977                 if (e.type !== 'mousemove') {
 
3978                         L.DomEvent.stopPropagation(e);
 
3984         _initPathRoot: function () {
 
3985                 if (!this._pathRoot) {
 
3986                         this._pathRoot = L.Path.prototype._createElement('svg');
 
3987                         this._panes.overlayPane.appendChild(this._pathRoot);
 
3989                         if (this.options.zoomAnimation && L.Browser.any3d) {
 
3990                                 this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
 
3993                                         'zoomanim': this._animatePathZoom,
 
3994                                         'zoomend': this._endPathZoom
 
3997                                 this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
 
4000                         this.on('moveend', this._updateSvgViewport);
 
4001                         this._updateSvgViewport();
 
4005         _animatePathZoom: function (opt) {
 
4006                 var scale = this.getZoomScale(opt.zoom),
 
4007                     offset = this._getCenterOffset(opt.center),
 
4008                     translate = offset.multiplyBy(-scale)._add(this._pathViewport.min);
 
4010                 this._pathRoot.style[L.DomUtil.TRANSFORM] =
 
4011                         L.DomUtil.getTranslateString(translate) + ' scale(' + scale + ') ';
 
4013                 this._pathZooming = true;
 
4016         _endPathZoom: function () {
 
4017                 this._pathZooming = false;
 
4020         _updateSvgViewport: function () {
 
4022                 if (this._pathZooming) {
 
4023                         // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
 
4024                         // When the zoom animation ends we will be updated again anyway
 
4025                         // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
 
4029                 this._updatePathViewport();
 
4031                 var vp = this._pathViewport,
 
4034                     width = max.x - min.x,
 
4035                     height = max.y - min.y,
 
4036                     root = this._pathRoot,
 
4037                     pane = this._panes.overlayPane;
 
4039                 // Hack to make flicker on drag end on mobile webkit less irritating
 
4040                 if (L.Browser.mobileWebkit) {
 
4041                         pane.removeChild(root);
 
4044                 L.DomUtil.setPosition(root, min);
 
4045                 root.setAttribute('width', width);
 
4046                 root.setAttribute('height', height);
 
4047                 root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
 
4049                 if (L.Browser.mobileWebkit) {
 
4050                         pane.appendChild(root);
 
4057  * Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method.
 
4062         bindPopup: function (content, options) {
 
4064                 if (!this._popup || options) {
 
4065                         this._popup = new L.Popup(options, this);
 
4068                 this._popup.setContent(content);
 
4070                 if (!this._popupHandlersAdded) {
 
4072                             .on('click', this._openPopup, this)
 
4073                             .on('remove', this.closePopup, this);
 
4075                         this._popupHandlersAdded = true;
 
4081         unbindPopup: function () {
 
4085                             .off('click', this.openPopup)
 
4086                             .off('remove', this.closePopup);
 
4088                         this._popupHandlersAdded = false;
 
4093         openPopup: function (latlng) {
 
4096                         // open the popup from one of the path's points if not specified
 
4097                         latlng = latlng || this._latlng ||
 
4098                                  this._latlngs[Math.floor(this._latlngs.length / 2)];
 
4100                         this._openPopup({latlng: latlng});
 
4106         closePopup: function () {
 
4108                         this._popup._close();
 
4113         _openPopup: function (e) {
 
4114                 this._popup.setLatLng(e.latlng);
 
4115                 this._map.openPopup(this._popup);
 
4121  * Vector rendering for IE6-8 through VML.
 
4122  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
 
4125 L.Browser.vml = !L.Browser.svg && (function () {
 
4127                 var div = document.createElement('div');
 
4128                 div.innerHTML = '<v:shape adj="1"/>';
 
4130                 var shape = div.firstChild;
 
4131                 shape.style.behavior = 'url(#default#VML)';
 
4133                 return shape && (typeof shape.adj === 'object');
 
4140 L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
 
4146         _createElement: (function () {
 
4148                         document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
 
4149                         return function (name) {
 
4150                                 return document.createElement('<lvml:' + name + ' class="lvml">');
 
4153                         return function (name) {
 
4154                                 return document.createElement(
 
4155                                         '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
 
4160         _initPath: function () {
 
4161                 var container = this._container = this._createElement('shape');
 
4162                 L.DomUtil.addClass(container, 'leaflet-vml-shape');
 
4163                 if (this.options.clickable) {
 
4164                         L.DomUtil.addClass(container, 'leaflet-clickable');
 
4166                 container.coordsize = '1 1';
 
4168                 this._path = this._createElement('path');
 
4169                 container.appendChild(this._path);
 
4171                 this._map._pathRoot.appendChild(container);
 
4174         _initStyle: function () {
 
4175                 this._updateStyle();
 
4178         _updateStyle: function () {
 
4179                 var stroke = this._stroke,
 
4181                     options = this.options,
 
4182                     container = this._container;
 
4184                 container.stroked = options.stroke;
 
4185                 container.filled = options.fill;
 
4187                 if (options.stroke) {
 
4189                                 stroke = this._stroke = this._createElement('stroke');
 
4190                                 stroke.endcap = 'round';
 
4191                                 container.appendChild(stroke);
 
4193                         stroke.weight = options.weight + 'px';
 
4194                         stroke.color = options.color;
 
4195                         stroke.opacity = options.opacity;
 
4196                         if (options.dashArray) {
 
4197                                 stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
 
4199                                 stroke.dashStyle = '';
 
4201                 } else if (stroke) {
 
4202                         container.removeChild(stroke);
 
4203                         this._stroke = null;
 
4208                                 fill = this._fill = this._createElement('fill');
 
4209                                 container.appendChild(fill);
 
4211                         fill.color = options.fillColor || options.color;
 
4212                         fill.opacity = options.fillOpacity;
 
4214                         container.removeChild(fill);
 
4219         _updatePath: function () {
 
4220                 var style = this._container.style;
 
4222                 style.display = 'none';
 
4223                 this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
 
4228 L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
 
4229         _initPathRoot: function () {
 
4230                 if (this._pathRoot) { return; }
 
4232                 var root = this._pathRoot = document.createElement('div');
 
4233                 root.className = 'leaflet-vml-container';
 
4234                 this._panes.overlayPane.appendChild(root);
 
4236                 this.on('moveend', this._updatePathViewport);
 
4237                 this._updatePathViewport();
 
4243  * Vector rendering for all browsers that support canvas.
 
4246 L.Browser.canvas = (function () {
 
4247         return !!document.createElement('canvas').getContext;
 
4250 L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
 
4252                 //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
 
4257         redraw: function () {
 
4259                         this.projectLatlngs();
 
4260                         this._requestUpdate();
 
4265         setStyle: function (style) {
 
4266                 L.setOptions(this, style);
 
4269                         this._updateStyle();
 
4270                         this._requestUpdate();
 
4275         onRemove: function (map) {
 
4277                     .off('viewreset', this.projectLatlngs, this)
 
4278                     .off('moveend', this._updatePath, this);
 
4280                 this._requestUpdate();
 
4285         _requestUpdate: function () {
 
4286                 if (this._map && !L.Path._updateRequest) {
 
4287                         L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
 
4291         _fireMapMoveEnd: function () {
 
4292                 L.Path._updateRequest = null;
 
4293                 this.fire('moveend');
 
4296         _initElements: function () {
 
4297                 this._map._initPathRoot();
 
4298                 this._ctx = this._map._canvasCtx;
 
4301         _updateStyle: function () {
 
4302                 var options = this.options;
 
4304                 if (options.stroke) {
 
4305                         this._ctx.lineWidth = options.weight;
 
4306                         this._ctx.strokeStyle = options.color;
 
4309                         this._ctx.fillStyle = options.fillColor || options.color;
 
4313         _drawPath: function () {
 
4314                 var i, j, len, len2, point, drawMethod;
 
4316                 this._ctx.beginPath();
 
4318                 for (i = 0, len = this._parts.length; i < len; i++) {
 
4319                         for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
 
4320                                 point = this._parts[i][j];
 
4321                                 drawMethod = (j === 0 ? 'move' : 'line') + 'To';
 
4323                                 this._ctx[drawMethod](point.x, point.y);
 
4325                         // TODO refactor ugly hack
 
4326                         if (this instanceof L.Polygon) {
 
4327                                 this._ctx.closePath();
 
4332         _checkIfEmpty: function () {
 
4333                 return !this._parts.length;
 
4336         _updatePath: function () {
 
4337                 if (this._checkIfEmpty()) { return; }
 
4339                 var ctx = this._ctx,
 
4340                     options = this.options;
 
4344                 this._updateStyle();
 
4347                         if (options.fillOpacity < 1) {
 
4348                                 ctx.globalAlpha = options.fillOpacity;
 
4353                 if (options.stroke) {
 
4354                         if (options.opacity < 1) {
 
4355                                 ctx.globalAlpha = options.opacity;
 
4362                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
 
4365         _initEvents: function () {
 
4366                 if (this.options.clickable) {
 
4368                         // TODO mouseover, mouseout, dblclick
 
4369                         this._map.on('click', this._onClick, this);
 
4373         _onClick: function (e) {
 
4374                 if (this._containsPoint(e.layerPoint)) {
 
4375                         this.fire('click', e);
 
4380 L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
 
4381         _initPathRoot: function () {
 
4382                 var root = this._pathRoot,
 
4386                         root = this._pathRoot = document.createElement("canvas");
 
4387                         root.style.position = 'absolute';
 
4388                         ctx = this._canvasCtx = root.getContext('2d');
 
4390                         ctx.lineCap = "round";
 
4391                         ctx.lineJoin = "round";
 
4393                         this._panes.overlayPane.appendChild(root);
 
4395                         if (this.options.zoomAnimation) {
 
4396                                 this._pathRoot.className = 'leaflet-zoom-animated';
 
4397                                 this.on('zoomanim', this._animatePathZoom);
 
4398                                 this.on('zoomend', this._endPathZoom);
 
4400                         this.on('moveend', this._updateCanvasViewport);
 
4401                         this._updateCanvasViewport();
 
4405         _updateCanvasViewport: function () {
 
4406                 // don't redraw while zooming. See _updateSvgViewport for more details
 
4407                 if (this._pathZooming) { return; }
 
4408                 this._updatePathViewport();
 
4410                 var vp = this._pathViewport,
 
4412                     size = vp.max.subtract(min),
 
4413                     root = this._pathRoot;
 
4415                 //TODO check if this works properly on mobile webkit
 
4416                 L.DomUtil.setPosition(root, min);
 
4417                 root.width = size.x;
 
4418                 root.height = size.y;
 
4419                 root.getContext('2d').translate(-min.x, -min.y);
 
4425  * L.LineUtil contains different utility functions for line segments
 
4426  * and polylines (clipping, simplification, distances, etc.)
 
4431         // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
 
4432         // Improves rendering performance dramatically by lessening the number of points to draw.
 
4434         simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
 
4435                 if (!tolerance || !points.length) {
 
4436                         return points.slice();
 
4439                 var sqTolerance = tolerance * tolerance;
 
4441                 // stage 1: vertex reduction
 
4442                 points = this._reducePoints(points, sqTolerance);
 
4444                 // stage 2: Douglas-Peucker simplification
 
4445                 points = this._simplifyDP(points, sqTolerance);
 
4450         // distance from a point to a segment between two points
 
4451         pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
 
4452                 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
 
4455         closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
 
4456                 return this._sqClosestPointOnSegment(p, p1, p2);
 
4459         // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
 
4460         _simplifyDP: function (points, sqTolerance) {
 
4462                 var len = points.length,
 
4463                     ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
 
4464                     markers = new ArrayConstructor(len);
 
4466                 markers[0] = markers[len - 1] = 1;
 
4468                 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
 
4473                 for (i = 0; i < len; i++) {
 
4475                                 newPoints.push(points[i]);
 
4482         _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
 
4487                 for (i = first + 1; i <= last - 1; i++) {
 
4488                         sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
 
4490                         if (sqDist > maxSqDist) {
 
4496                 if (maxSqDist > sqTolerance) {
 
4499                         this._simplifyDPStep(points, markers, sqTolerance, first, index);
 
4500                         this._simplifyDPStep(points, markers, sqTolerance, index, last);
 
4504         // reduce points that are too close to each other to a single point
 
4505         _reducePoints: function (points, sqTolerance) {
 
4506                 var reducedPoints = [points[0]];
 
4508                 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
 
4509                         if (this._sqDist(points[i], points[prev]) > sqTolerance) {
 
4510                                 reducedPoints.push(points[i]);
 
4514                 if (prev < len - 1) {
 
4515                         reducedPoints.push(points[len - 1]);
 
4517                 return reducedPoints;
 
4520         /*jshint bitwise:false */ // temporarily allow bitwise oprations
 
4522         // Cohen-Sutherland line clipping algorithm.
 
4523         // Used to avoid rendering parts of a polyline that are not currently visible.
 
4525         clipSegment: function (a, b, bounds, useLastCode) {
 
4526                 var min = bounds.min,
 
4529                     codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
 
4530                     codeB = this._getBitCode(b, bounds),
 
4532                     codeOut, p, newCode;
 
4534                 // save 2nd code to avoid calculating it on the next segment
 
4535                 this._lastCode = codeB;
 
4538                         // if a,b is inside the clip window (trivial accept)
 
4539                         if (!(codeA | codeB)) {
 
4541                         // if a,b is outside the clip window (trivial reject)
 
4542                         } else if (codeA & codeB) {
 
4546                                 codeOut = codeA || codeB,
 
4547                                 p = this._getEdgeIntersection(a, b, codeOut, bounds),
 
4548                                 newCode = this._getBitCode(p, bounds);
 
4550                                 if (codeOut === codeA) {
 
4561         _getEdgeIntersection: function (a, b, code, bounds) {
 
4567                 if (code & 8) { // top
 
4568                         return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
 
4569                 } else if (code & 4) { // bottom
 
4570                         return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
 
4571                 } else if (code & 2) { // right
 
4572                         return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
 
4573                 } else if (code & 1) { // left
 
4574                         return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
 
4578         _getBitCode: function (/*Point*/ p, bounds) {
 
4581                 if (p.x < bounds.min.x) { // left
 
4583                 } else if (p.x > bounds.max.x) { // right
 
4586                 if (p.y < bounds.min.y) { // bottom
 
4588                 } else if (p.y > bounds.max.y) { // top
 
4595         /*jshint bitwise:true */
 
4597         // square distance (to avoid unnecessary Math.sqrt calls)
 
4598         _sqDist: function (p1, p2) {
 
4599                 var dx = p2.x - p1.x,
 
4601                 return dx * dx + dy * dy;
 
4604         // return closest point on segment or distance to that point
 
4605         _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
 
4610                     dot = dx * dx + dy * dy,
 
4614                         t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
 
4628                 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
 
4633 L.Polyline = L.Path.extend({
 
4634         initialize: function (latlngs, options) {
 
4635                 L.Path.prototype.initialize.call(this, options);
 
4637                 this._latlngs = this._convertLatLngs(latlngs);
 
4639                 // TODO refactor: move to Polyline.Edit.js
 
4640                 if (L.Handler.PolyEdit) {
 
4641                         this.editing = new L.Handler.PolyEdit(this);
 
4643                         if (this.options.editable) {
 
4644                                 this.editing.enable();
 
4650                 // how much to simplify the polyline on each zoom level
 
4651                 // more = better performance and smoother look, less = more accurate
 
4656         projectLatlngs: function () {
 
4657                 this._originalPoints = [];
 
4659                 for (var i = 0, len = this._latlngs.length; i < len; i++) {
 
4660                         this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
 
4664         getPathString: function () {
 
4665                 for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
 
4666                         str += this._getPathPartStr(this._parts[i]);
 
4671         getLatLngs: function () {
 
4672                 return this._latlngs;
 
4675         setLatLngs: function (latlngs) {
 
4676                 this._latlngs = this._convertLatLngs(latlngs);
 
4677                 return this.redraw();
 
4680         addLatLng: function (latlng) {
 
4681                 this._latlngs.push(L.latLng(latlng));
 
4682                 return this.redraw();
 
4685         spliceLatLngs: function (index, howMany) {
 
4686                 var removed = [].splice.apply(this._latlngs, arguments);
 
4687                 this._convertLatLngs(this._latlngs);
 
4692         closestLayerPoint: function (p) {
 
4693                 var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
 
4695                 for (var j = 0, jLen = parts.length; j < jLen; j++) {
 
4696                         var points = parts[j];
 
4697                         for (var i = 1, len = points.length; i < len; i++) {
 
4700                                 var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
 
4701                                 if (sqDist < minDistance) {
 
4702                                         minDistance = sqDist;
 
4703                                         minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
 
4708                         minPoint.distance = Math.sqrt(minDistance);
 
4713         getBounds: function () {
 
4714                 var bounds = new L.LatLngBounds(),
 
4715                     latLngs = this.getLatLngs(),
 
4718                 for (i = 0, len = latLngs.length; i < len; i++) {
 
4719                         bounds.extend(latLngs[i]);
 
4725         // TODO refactor: move to Polyline.Edit.js
 
4726         onAdd: function (map) {
 
4727                 L.Path.prototype.onAdd.call(this, map);
 
4729                 if (this.editing && this.editing.enabled()) {
 
4730                         this.editing.addHooks();
 
4734         onRemove: function (map) {
 
4735                 if (this.editing && this.editing.enabled()) {
 
4736                         this.editing.removeHooks();
 
4739                 L.Path.prototype.onRemove.call(this, map);
 
4742         _convertLatLngs: function (latlngs) {
 
4744                 for (i = 0, len = latlngs.length; i < len; i++) {
 
4745                         if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
 
4748                         latlngs[i] = L.latLng(latlngs[i]);
 
4753         _initEvents: function () {
 
4754                 L.Path.prototype._initEvents.call(this);
 
4757         _getPathPartStr: function (points) {
 
4758                 var round = L.Path.VML;
 
4760                 for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
 
4765                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
 
4770         _clipPoints: function () {
 
4771                 var points = this._originalPoints,
 
4772                     len = points.length,
 
4775                 if (this.options.noClip) {
 
4776                         this._parts = [points];
 
4782                 var parts = this._parts,
 
4783                     vp = this._map._pathViewport,
 
4786                 for (i = 0, k = 0; i < len - 1; i++) {
 
4787                         segment = lu.clipSegment(points[i], points[i + 1], vp, i);
 
4792                         parts[k] = parts[k] || [];
 
4793                         parts[k].push(segment[0]);
 
4795                         // if segment goes out of screen, or it's the last one, it's the end of the line part
 
4796                         if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
 
4797                                 parts[k].push(segment[1]);
 
4803         // simplify each clipped part of the polyline
 
4804         _simplifyPoints: function () {
 
4805                 var parts = this._parts,
 
4808                 for (var i = 0, len = parts.length; i < len; i++) {
 
4809                         parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
 
4813         _updatePath: function () {
 
4814                 if (!this._map) { return; }
 
4817                 this._simplifyPoints();
 
4819                 L.Path.prototype._updatePath.call(this);
 
4823 L.polyline = function (latlngs, options) {
 
4824         return new L.Polyline(latlngs, options);
 
4829  * L.PolyUtil contains utilify functions for polygons (clipping, etc.).
 
4832 /*jshint bitwise:false */ // allow bitwise oprations here
 
4837  * Sutherland-Hodgeman polygon clipping algorithm.
 
4838  * Used to avoid rendering parts of a polygon that are not currently visible.
 
4840 L.PolyUtil.clipPolygon = function (points, bounds) {
 
4841         var min = bounds.min,
 
4844             edges = [1, 4, 2, 8],
 
4850         for (i = 0, len = points.length; i < len; i++) {
 
4851                 points[i]._code = lu._getBitCode(points[i], bounds);
 
4854         // for each edge (left, bottom, right, top)
 
4855         for (k = 0; k < 4; k++) {
 
4859                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
 
4863                         // if a is inside the clip window
 
4864                         if (!(a._code & edge)) {
 
4865                                 // if b is outside the clip window (a->b goes out of screen)
 
4866                                 if (b._code & edge) {
 
4867                                         p = lu._getEdgeIntersection(b, a, edge, bounds);
 
4868                                         p._code = lu._getBitCode(p, bounds);
 
4869                                         clippedPoints.push(p);
 
4871                                 clippedPoints.push(a);
 
4873                         // else if b is inside the clip window (a->b enters the screen)
 
4874                         } else if (!(b._code & edge)) {
 
4875                                 p = lu._getEdgeIntersection(b, a, edge, bounds);
 
4876                                 p._code = lu._getBitCode(p, bounds);
 
4877                                 clippedPoints.push(p);
 
4880                 points = clippedPoints;
 
4886 /*jshint bitwise:true */
 
4890  * L.Polygon is used to display polygons on a map.
 
4893 L.Polygon = L.Polyline.extend({
 
4898         initialize: function (latlngs, options) {
 
4899                 L.Polyline.prototype.initialize.call(this, latlngs, options);
 
4901                 if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
 
4902                         this._latlngs = this._convertLatLngs(latlngs[0]);
 
4903                         this._holes = latlngs.slice(1);
 
4907         projectLatlngs: function () {
 
4908                 L.Polyline.prototype.projectLatlngs.call(this);
 
4910                 // project polygon holes points
 
4911                 // TODO move this logic to Polyline to get rid of duplication
 
4912                 this._holePoints = [];
 
4914                 if (!this._holes) { return; }
 
4916                 var i, j, len, len2, hole;
 
4918                 for (i = 0, len = this._holes.length; i < len; i++) {
 
4919                         this._holePoints[i] = [];
 
4921                         for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
 
4922                                 this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
 
4927         _clipPoints: function () {
 
4928                 var points = this._originalPoints,
 
4931                 this._parts = [points].concat(this._holePoints);
 
4933                 if (this.options.noClip) { return; }
 
4935                 for (var i = 0, len = this._parts.length; i < len; i++) {
 
4936                         var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
 
4937                         if (clipped.length) {
 
4938                                 newParts.push(clipped);
 
4942                 this._parts = newParts;
 
4945         _getPathPartStr: function (points) {
 
4946                 var str = L.Polyline.prototype._getPathPartStr.call(this, points);
 
4947                 return str + (L.Browser.svg ? 'z' : 'x');
 
4951 L.polygon = function (latlngs, options) {
 
4952         return new L.Polygon(latlngs, options);
 
4957  * Contains L.MultiPolyline and L.MultiPolygon layers.
 
4961         function createMulti(Klass) {
 
4963                 return L.FeatureGroup.extend({
 
4965                         initialize: function (latlngs, options) {
 
4967                                 this._options = options;
 
4968                                 this.setLatLngs(latlngs);
 
4971                         setLatLngs: function (latlngs) {
 
4973                                     len = latlngs.length;
 
4975                                 this.eachLayer(function (layer) {
 
4977                                                 layer.setLatLngs(latlngs[i++]);
 
4979                                                 this.removeLayer(layer);
 
4984                                         this.addLayer(new Klass(latlngs[i++], this._options));
 
4992         L.MultiPolyline = createMulti(L.Polyline);
 
4993         L.MultiPolygon = createMulti(L.Polygon);
 
4995         L.multiPolyline = function (latlngs, options) {
 
4996                 return new L.MultiPolyline(latlngs, options);
 
4999         L.multiPolygon = function (latlngs, options) {
 
5000                 return new L.MultiPolygon(latlngs, options);
 
5006  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds
 
5009 L.Rectangle = L.Polygon.extend({
 
5010         initialize: function (latLngBounds, options) {
 
5011                 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
 
5014         setBounds: function (latLngBounds) {
 
5015                 this.setLatLngs(this._boundsToLatLngs(latLngBounds));
 
5018         _boundsToLatLngs: function (latLngBounds) {
 
5019                 latLngBounds = L.latLngBounds(latLngBounds);
 
5021                         latLngBounds.getSouthWest(),
 
5022                         latLngBounds.getNorthWest(),
 
5023                         latLngBounds.getNorthEast(),
 
5024                         latLngBounds.getSouthEast(),
 
5025                         latLngBounds.getSouthWest()
 
5030 L.rectangle = function (latLngBounds, options) {
 
5031         return new L.Rectangle(latLngBounds, options);
 
5036  * L.Circle is a circle overlay (with a certain radius in meters).
 
5039 L.Circle = L.Path.extend({
 
5040         initialize: function (latlng, radius, options) {
 
5041                 L.Path.prototype.initialize.call(this, options);
 
5043                 this._latlng = L.latLng(latlng);
 
5044                 this._mRadius = radius;
 
5051         setLatLng: function (latlng) {
 
5052                 this._latlng = L.latLng(latlng);
 
5053                 return this.redraw();
 
5056         setRadius: function (radius) {
 
5057                 this._mRadius = radius;
 
5058                 return this.redraw();
 
5061         projectLatlngs: function () {
 
5062                 var lngRadius = this._getLngRadius(),
 
5063                     latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
 
5064                     point2 = this._map.latLngToLayerPoint(latlng2);
 
5066                 this._point = this._map.latLngToLayerPoint(this._latlng);
 
5067                 this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
 
5070         getBounds: function () {
 
5071                 var lngRadius = this._getLngRadius(),
 
5072                     latRadius = (this._mRadius / 40075017) * 360,
 
5073                     latlng = this._latlng,
 
5074                     sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
 
5075                     ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
 
5077                 return new L.LatLngBounds(sw, ne);
 
5080         getLatLng: function () {
 
5081                 return this._latlng;
 
5084         getPathString: function () {
 
5085                 var p = this._point,
 
5088                 if (this._checkIfEmpty()) {
 
5092                 if (L.Browser.svg) {
 
5093                         return "M" + p.x + "," + (p.y - r) +
 
5094                                "A" + r + "," + r + ",0,1,1," +
 
5095                                (p.x - 0.1) + "," + (p.y - r) + " z";
 
5099                         return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
 
5103         getRadius: function () {
 
5104                 return this._mRadius;
 
5107         // TODO Earth hardcoded, move into projection code!
 
5109         _getLatRadius: function () {
 
5110                 return (this._mRadius / 40075017) * 360;
 
5113         _getLngRadius: function () {
 
5114                 return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
 
5117         _checkIfEmpty: function () {
 
5121                 var vp = this._map._pathViewport,
 
5125                 return p.x - r > vp.max.x || p.y - r > vp.max.y ||
 
5126                        p.x + r < vp.min.x || p.y + r < vp.min.y;
 
5130 L.circle = function (latlng, radius, options) {
 
5131         return new L.Circle(latlng, radius, options);
 
5136  * L.CircleMarker is a circle overlay with a permanent pixel radius.
 
5139 L.CircleMarker = L.Circle.extend({
 
5145         initialize: function (latlng, options) {
 
5146                 L.Circle.prototype.initialize.call(this, latlng, null, options);
 
5147                 this._radius = this.options.radius;
 
5150         projectLatlngs: function () {
 
5151                 this._point = this._map.latLngToLayerPoint(this._latlng);
 
5154         setRadius: function (radius) {
 
5155                 this._radius = radius;
 
5156                 return this.redraw();
 
5160 L.circleMarker = function (latlng, options) {
 
5161         return new L.CircleMarker(latlng, options);
 
5166 L.Polyline.include(!L.Path.CANVAS ? {} : {
 
5167         _containsPoint: function (p, closed) {
 
5168                 var i, j, k, len, len2, dist, part,
 
5169                     w = this.options.weight / 2;
 
5171                 if (L.Browser.touch) {
 
5172                         w += 10; // polyline click tolerance on touch devices
 
5175                 for (i = 0, len = this._parts.length; i < len; i++) {
 
5176                         part = this._parts[i];
 
5177                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
 
5178                                 if (!closed && (j === 0)) {
 
5182                                 dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
 
5195 L.Polygon.include(!L.Path.CANVAS ? {} : {
 
5196         _containsPoint: function (p) {
 
5202                 // TODO optimization: check if within bounds first
 
5204                 if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
 
5205                         // click on polygon border
 
5209                 // ray casting algorithm for detecting if point is in polygon
 
5211                 for (i = 0, len = this._parts.length; i < len; i++) {
 
5212                         part = this._parts[i];
 
5214                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
 
5218                                 if (((p1.y > p.y) !== (p2.y > p.y)) &&
 
5219                                                 (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
 
5231  * Circle canvas specific drawing parts.
 
5234 L.Circle.include(!L.Path.CANVAS ? {} : {
 
5235         _drawPath: function () {
 
5236                 var p = this._point;
 
5237                 this._ctx.beginPath();
 
5238                 this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
 
5241         _containsPoint: function (p) {
 
5242                 var center = this._point,
 
5243                     w2 = this.options.stroke ? this.options.weight / 2 : 0;
 
5245                 return (p.distanceTo(center) <= this._radius + w2);
 
5250 L.GeoJSON = L.FeatureGroup.extend({
 
5251         initialize: function (geojson, options) {
 
5252                 L.setOptions(this, options);
 
5257                         this.addData(geojson);
 
5261         addData: function (geojson) {
 
5262                 var features = geojson instanceof Array ? geojson : geojson.features,
 
5266                         for (i = 0, len = features.length; i < len; i++) {
 
5267                                 this.addData(features[i]);
 
5272                 var options = this.options;
 
5274                 if (options.filter && !options.filter(geojson)) { return; }
 
5276                 var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
 
5277                 layer.feature = geojson;
 
5279                 this.resetStyle(layer);
 
5281                 if (options.onEachFeature) {
 
5282                         options.onEachFeature(geojson, layer);
 
5285                 return this.addLayer(layer);
 
5288         resetStyle: function (layer) {
 
5289                 var style = this.options.style;
 
5291                         this._setLayerStyle(layer, style);
 
5295         setStyle: function (style) {
 
5296                 this.eachLayer(function (layer) {
 
5297                         this._setLayerStyle(layer, style);
 
5301         _setLayerStyle: function (layer, style) {
 
5302                 if (typeof style === 'function') {
 
5303                         style = style(layer.feature);
 
5305                 if (layer.setStyle) {
 
5306                         layer.setStyle(style);
 
5311 L.extend(L.GeoJSON, {
 
5312         geometryToLayer: function (geojson, pointToLayer) {
 
5313                 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
 
5314                     coords = geometry.coordinates,
 
5316                     latlng, latlngs, i, len, layer;
 
5318                 switch (geometry.type) {
 
5320                         latlng = this.coordsToLatLng(coords);
 
5321                         return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
 
5324                         for (i = 0, len = coords.length; i < len; i++) {
 
5325                                 latlng = this.coordsToLatLng(coords[i]);
 
5326                                 layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
 
5329                         return new L.FeatureGroup(layers);
 
5332                         latlngs = this.coordsToLatLngs(coords);
 
5333                         return new L.Polyline(latlngs);
 
5336                         latlngs = this.coordsToLatLngs(coords, 1);
 
5337                         return new L.Polygon(latlngs);
 
5339                 case 'MultiLineString':
 
5340                         latlngs = this.coordsToLatLngs(coords, 1);
 
5341                         return new L.MultiPolyline(latlngs);
 
5343                 case "MultiPolygon":
 
5344                         latlngs = this.coordsToLatLngs(coords, 2);
 
5345                         return new L.MultiPolygon(latlngs);
 
5347                 case "GeometryCollection":
 
5348                         for (i = 0, len = geometry.geometries.length; i < len; i++) {
 
5349                                 layer = this.geometryToLayer(geometry.geometries[i], pointToLayer);
 
5352                         return new L.FeatureGroup(layers);
 
5355                         throw new Error('Invalid GeoJSON object.');
 
5359         coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
 
5360                 var lat = parseFloat(coords[reverse ? 0 : 1]),
 
5361                     lng = parseFloat(coords[reverse ? 1 : 0]);
 
5363                 return new L.LatLng(lat, lng, true);
 
5366         coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
 
5371                 for (i = 0, len = coords.length; i < len; i++) {
 
5372                         latlng = levelsDeep ?
 
5373                                 this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
 
5374                                 this.coordsToLatLng(coords[i], reverse);
 
5376                         latlngs.push(latlng);
 
5383 L.geoJson = function (geojson, options) {
 
5384         return new L.GeoJSON(geojson, options);
 
5389  * L.DomEvent contains functions for working with DOM events.
 
5393         /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
 
5394         addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
 
5396                 var id = L.stamp(fn),
 
5397                     key = '_leaflet_' + type + id,
 
5398                     handler, originalHandler, newType;
 
5400                 if (obj[key]) { return this; }
 
5402                 handler = function (e) {
 
5403                         return fn.call(context || obj, e || L.DomEvent._getEvent());
 
5406                 if (L.Browser.msTouch && type.indexOf('touch') === 0) {
 
5407                         return this.addMsTouchListener(obj, type, handler, id);
 
5408                 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
 
5409                         return this.addDoubleTapListener(obj, handler, id);
 
5411                 } else if ('addEventListener' in obj) {
 
5413                         if (type === 'mousewheel') {
 
5414                                 obj.addEventListener('DOMMouseScroll', handler, false);
 
5415                                 obj.addEventListener(type, handler, false);
 
5417                         } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
 
5419                                 originalHandler = handler;
 
5420                                 newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
 
5422                                 handler = function (e) {
 
5423                                         if (!L.DomEvent._checkMouse(obj, e)) { return; }
 
5424                                         return originalHandler(e);
 
5427                                 obj.addEventListener(newType, handler, false);
 
5430                                 obj.addEventListener(type, handler, false);
 
5433                 } else if ('attachEvent' in obj) {
 
5434                         obj.attachEvent("on" + type, handler);
 
5442         removeListener: function (obj, type, fn) {  // (HTMLElement, String, Function)
 
5444                 var id = L.stamp(fn),
 
5445                     key = '_leaflet_' + type + id,
 
5448                 if (!handler) { return; }
 
5450                 if (L.Browser.msTouch && type.indexOf('touch') === 0) {
 
5451                         this.removeMsTouchListener(obj, type, id);
 
5452                 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
 
5453                         this.removeDoubleTapListener(obj, id);
 
5455                 } else if ('removeEventListener' in obj) {
 
5457                         if (type === 'mousewheel') {
 
5458                                 obj.removeEventListener('DOMMouseScroll', handler, false);
 
5459                                 obj.removeEventListener(type, handler, false);
 
5461                         } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
 
5462                                 obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
 
5464                                 obj.removeEventListener(type, handler, false);
 
5466                 } else if ('detachEvent' in obj) {
 
5467                         obj.detachEvent("on" + type, handler);
 
5475         stopPropagation: function (e) {
 
5477                 if (e.stopPropagation) {
 
5478                         e.stopPropagation();
 
5480                         e.cancelBubble = true;
 
5485         disableClickPropagation: function (el) {
 
5487                 var stop = L.DomEvent.stopPropagation;
 
5490                         .addListener(el, L.Draggable.START, stop)
 
5491                         .addListener(el, 'click', stop)
 
5492                         .addListener(el, 'dblclick', stop);
 
5495         preventDefault: function (e) {
 
5497                 if (e.preventDefault) {
 
5500                         e.returnValue = false;
 
5505         stop: function (e) {
 
5506                 return L.DomEvent.preventDefault(e).stopPropagation(e);
 
5509         getMousePosition: function (e, container) {
 
5511                 var body = document.body,
 
5512                     docEl = document.documentElement,
 
5513                     x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
 
5514                     y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
 
5515                     pos = new L.Point(x, y);
 
5517                 return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
 
5520         getWheelDelta: function (e) {
 
5525                         delta = e.wheelDelta / 120;
 
5528                         delta = -e.detail / 3;
 
5533         // check if element really left/entered the event target (for mouseenter/mouseleave)
 
5534         _checkMouse: function (el, e) {
 
5536                 var related = e.relatedTarget;
 
5538                 if (!related) { return true; }
 
5541                         while (related && (related !== el)) {
 
5542                                 related = related.parentNode;
 
5547                 return (related !== el);
 
5550         /*jshint noarg:false */
 
5551         _getEvent: function () { // evil magic for IE
 
5553                 var e = window.event;
 
5555                         var caller = arguments.callee.caller;
 
5557                                 e = caller['arguments'][0];
 
5558                                 if (e && window.Event === e.constructor) {
 
5561                                 caller = caller.caller;
 
5566         /*jshint noarg:false */
 
5569 L.DomEvent.on = L.DomEvent.addListener;
 
5570 L.DomEvent.off = L.DomEvent.removeListener;
 
5574  * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
 
5577 L.Draggable = L.Class.extend({
 
5578         includes: L.Mixin.Events,
 
5581                 START: L.Browser.touch ? 'touchstart' : 'mousedown',
 
5582                 END: L.Browser.touch ? 'touchend' : 'mouseup',
 
5583                 MOVE: L.Browser.touch ? 'touchmove' : 'mousemove',
 
5587         initialize: function (element, dragStartTarget, longPress) {
 
5588                 this._element = element;
 
5589                 this._dragStartTarget = dragStartTarget || element;
 
5590                 this._longPress = longPress && !L.Browser.msTouch;
 
5593         enable: function () {
 
5594                 if (this._enabled) { return; }
 
5596                 L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
 
5597                 this._enabled = true;
 
5600         disable: function () {
 
5601                 if (!this._enabled) { return; }
 
5603                 L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
 
5604                 this._enabled = false;
 
5605                 this._moved = false;
 
5608         _onDown: function (e) {
 
5609                 if ((!L.Browser.touch && e.shiftKey) ||
 
5610                     ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
 
5612                 L.DomEvent.preventDefault(e);
 
5613                 L.DomEvent.stopPropagation(e);
 
5615                 if (L.Draggable._disabled) { return; }
 
5617                 this._simulateClick = true;
 
5619                 if (e.touches && e.touches.length > 1) {
 
5620                         this._simulateClick = false;
 
5621                         clearTimeout(this._longPressTimeout);
 
5625                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
 
5628                 if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
 
5629                         L.DomUtil.addClass(el, 'leaflet-active');
 
5632                 this._moved = false;
 
5633                 if (this._moving) { return; }
 
5635                 this._startPoint = new L.Point(first.clientX, first.clientY);
 
5636                 this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
 
5638                 //Touch contextmenu event emulation
 
5639                 if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) {
 
5640                         this._longPressTimeout = setTimeout(L.bind(function () {
 
5641                                 var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
 
5643                                 if (dist < L.Draggable.TAP_TOLERANCE) {
 
5644                                         this._simulateClick = false;
 
5646                                         this._simulateEvent('contextmenu', first);
 
5651                 L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
 
5652                 L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
 
5655         _onMove: function (e) {
 
5656                 if (e.touches && e.touches.length > 1) { return; }
 
5658                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
 
5659                     newPoint = new L.Point(first.clientX, first.clientY),
 
5660                     diffVec = newPoint.subtract(this._startPoint);
 
5662                 if (!diffVec.x && !diffVec.y) { return; }
 
5664                 L.DomEvent.preventDefault(e);
 
5667                         this.fire('dragstart');
 
5670                         this._startPos = L.DomUtil.getPosition(this._element).subtract(diffVec);
 
5672                         if (!L.Browser.touch) {
 
5673                                 L.DomUtil.disableTextSelection();
 
5674                                 this._setMovingCursor();
 
5678                 this._newPos = this._startPos.add(diffVec);
 
5679                 this._moving = true;
 
5681                 L.Util.cancelAnimFrame(this._animRequest);
 
5682                 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
 
5685         _updatePosition: function () {
 
5686                 this.fire('predrag');
 
5687                 L.DomUtil.setPosition(this._element, this._newPos);
 
5691         _onUp: function (e) {
 
5692                 var simulateClickTouch;
 
5693                 clearTimeout(this._longPressTimeout);
 
5694                 if (this._simulateClick && e.changedTouches) {
 
5695                         var first = e.changedTouches[0],
 
5697                             dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
 
5699                         if (el.tagName.toLowerCase() === 'a') {
 
5700                                 L.DomUtil.removeClass(el, 'leaflet-active');
 
5703                         if (dist < L.Draggable.TAP_TOLERANCE) {
 
5704                                 simulateClickTouch = first;
 
5708                 if (!L.Browser.touch) {
 
5709                         L.DomUtil.enableTextSelection();
 
5710                         this._restoreCursor();
 
5713                 L.DomEvent.off(document, L.Draggable.MOVE, this._onMove);
 
5714                 L.DomEvent.off(document, L.Draggable.END, this._onUp);
 
5717                         // ensure drag is not fired after dragend
 
5718                         L.Util.cancelAnimFrame(this._animRequest);
 
5720                         this.fire('dragend');
 
5722                 this._moving = false;
 
5724                 if (simulateClickTouch) {
 
5725                         this._moved = false;
 
5726                         this._simulateEvent('click', simulateClickTouch);
 
5730         _setMovingCursor: function () {
 
5731                 L.DomUtil.addClass(document.body, 'leaflet-dragging');
 
5734         _restoreCursor: function () {
 
5735                 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
 
5738         _simulateEvent: function (type, e) {
 
5739                 var simulatedEvent = document.createEvent('MouseEvents');
 
5741                 simulatedEvent.initMouseEvent(
 
5742                         type, true, true, window, 1,
 
5743                         e.screenX, e.screenY,
 
5744                         e.clientX, e.clientY,
 
5745                         false, false, false, false, 0, null);
 
5747                 e.target.dispatchEvent(simulatedEvent);
 
5753  * L.Handler classes are used internally to inject interaction features to classes like Map and Marker.
 
5756 L.Handler = L.Class.extend({
 
5757         initialize: function (map) {
 
5761         enable: function () {
 
5762                 if (this._enabled) { return; }
 
5764                 this._enabled = true;
 
5768         disable: function () {
 
5769                 if (!this._enabled) { return; }
 
5771                 this._enabled = false;
 
5775         enabled: function () {
 
5776                 return !!this._enabled;
 
5782  * L.Handler.MapDrag is used internally by L.Map to make the map draggable.
 
5785 L.Map.mergeOptions({
 
5788         inertia: !L.Browser.android23,
 
5789         inertiaDeceleration: 3400, // px/s^2
 
5790         inertiaMaxSpeed: Infinity, // px/s
 
5791         inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
 
5792         easeLinearity: 0.25,
 
5796         // TODO refactor, move to CRS
 
5800 L.Map.Drag = L.Handler.extend({
 
5801         addHooks: function () {
 
5802                 if (!this._draggable) {
 
5803                         var map = this._map;
 
5805                         this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress);
 
5807                         this._draggable.on({
 
5808                                 'dragstart': this._onDragStart,
 
5809                                 'drag': this._onDrag,
 
5810                                 'dragend': this._onDragEnd
 
5813                         if (map.options.worldCopyJump) {
 
5814                                 this._draggable.on('predrag', this._onPreDrag, this);
 
5815                                 map.on('viewreset', this._onViewReset, this);
 
5818                 this._draggable.enable();
 
5821         removeHooks: function () {
 
5822                 this._draggable.disable();
 
5825         moved: function () {
 
5826                 return this._draggable && this._draggable._moved;
 
5829         _onDragStart: function () {
 
5830                 var map = this._map;
 
5833                         map._panAnim.stop();
 
5840                 if (map.options.inertia) {
 
5841                         this._positions = [];
 
5846         _onDrag: function () {
 
5847                 if (this._map.options.inertia) {
 
5848                         var time = this._lastTime = +new Date(),
 
5849                             pos = this._lastPos = this._draggable._newPos;
 
5851                         this._positions.push(pos);
 
5852                         this._times.push(time);
 
5854                         if (time - this._times[0] > 200) {
 
5855                                 this._positions.shift();
 
5856                                 this._times.shift();
 
5865         _onViewReset: function () {
 
5866                 var pxCenter = this._map.getSize()._divideBy(2),
 
5867                     pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
 
5869                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
 
5870                 this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
 
5873         _onPreDrag: function () {
 
5874                 // TODO refactor to be able to adjust map pane position after zoom
 
5875                 var map = this._map,
 
5876                     worldWidth = this._worldWidth,
 
5877                     halfWidth = Math.round(worldWidth / 2),
 
5878                     dx = this._initialWorldOffset,
 
5879                     x = this._draggable._newPos.x,
 
5880                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
 
5881                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
 
5882                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
 
5884                 this._draggable._newPos.x = newX;
 
5887         _onDragEnd: function () {
 
5888                 var map = this._map,
 
5889                     options = map.options,
 
5890                     delay = +new Date() - this._lastTime,
 
5892                     noInertia = !options.inertia ||
 
5893                             delay > options.inertiaThreshold ||
 
5894                             !this._positions[0];
 
5897                         map.fire('moveend');
 
5901                         var direction = this._lastPos.subtract(this._positions[0]),
 
5902                             duration = (this._lastTime + delay - this._times[0]) / 1000,
 
5904                             speedVector = direction.multiplyBy(options.easeLinearity / duration),
 
5905                             speed = speedVector.distanceTo(new L.Point(0, 0)),
 
5907                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
 
5908                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
 
5910                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * options.easeLinearity),
 
5911                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
 
5913                         L.Util.requestAnimFrame(function () {
 
5914                                 map.panBy(offset, decelerationDuration, options.easeLinearity);
 
5918                 map.fire('dragend');
 
5920                 if (options.maxBounds) {
 
5921                         // TODO predrag validation instead of animation
 
5922                         L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container);
 
5926         _panInsideMaxBounds: function () {
 
5927                 this.panInsideBounds(this.options.maxBounds);
 
5931 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
 
5935  * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
 
5938 L.Map.mergeOptions({
 
5939         doubleClickZoom: true
 
5942 L.Map.DoubleClickZoom = L.Handler.extend({
 
5943         addHooks: function () {
 
5944                 this._map.on('dblclick', this._onDoubleClick);
 
5947         removeHooks: function () {
 
5948                 this._map.off('dblclick', this._onDoubleClick);
 
5951         _onDoubleClick: function (e) {
 
5952                 this.setView(e.latlng, this._zoom + 1);
 
5956 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
 
5959  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
 
5962 L.Map.mergeOptions({
 
5963         scrollWheelZoom: !L.Browser.touch || L.Browser.msTouch
 
5966 L.Map.ScrollWheelZoom = L.Handler.extend({
 
5967         addHooks: function () {
 
5968                 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
 
5972         removeHooks: function () {
 
5973                 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
 
5976         _onWheelScroll: function (e) {
 
5977                 var delta = L.DomEvent.getWheelDelta(e);
 
5979                 this._delta += delta;
 
5980                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
 
5982                 if (!this._startTime) {
 
5983                         this._startTime = +new Date();
 
5986                 var left = Math.max(40 - (+new Date() - this._startTime), 0);
 
5988                 clearTimeout(this._timer);
 
5989                 this._timer = setTimeout(L.bind(this._performZoom, this), left);
 
5991                 L.DomEvent.preventDefault(e);
 
5992                 L.DomEvent.stopPropagation(e);
 
5995         _performZoom: function () {
 
5996                 var map = this._map,
 
5997                     delta = Math.round(this._delta),
 
5998                     zoom = map.getZoom();
 
6000                 delta = Math.max(Math.min(delta, 4), -4);
 
6001                 delta = map._limitZoom(zoom + delta) - zoom;
 
6005                 this._startTime = null;
 
6007                 if (!delta) { return; }
 
6009                 var newZoom = zoom + delta,
 
6010                     newCenter = this._getCenterForScrollWheelZoom(newZoom);
 
6012                 map.setView(newCenter, newZoom);
 
6015         _getCenterForScrollWheelZoom: function (newZoom) {
 
6016                 var map = this._map,
 
6017                     scale = map.getZoomScale(newZoom),
 
6018                     viewHalf = map.getSize()._divideBy(2),
 
6019                     centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale),
 
6020                     newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset);
 
6022                 return map.unproject(newCenterPoint);
 
6026 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
 
6029 L.extend(L.DomEvent, {
 
6031         _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
 
6032         _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
 
6034         // inspired by Zepto touch code by Thomas Fuchs
 
6035         addDoubleTapListener: function (obj, handler, id) {
 
6041                     touchstart = this._touchstart,
 
6042                     touchend = this._touchend,
 
6043                     trackedTouches = [];
 
6045                 function onTouchStart(e) {
 
6047                         if (L.Browser.msTouch) {
 
6048                                 trackedTouches.push(e.pointerId);
 
6049                                 count = trackedTouches.length;
 
6051                                 count = e.touches.length;
 
6057                         var now = Date.now(),
 
6058                                 delta = now - (last || now);
 
6060                         touch = e.touches ? e.touches[0] : e;
 
6061                         doubleTap = (delta > 0 && delta <= delay);
 
6064                 function onTouchEnd(e) {
 
6065                         if (L.Browser.msTouch) {
 
6066                                 var idx = trackedTouches.indexOf(e.pointerId);
 
6070                                 trackedTouches.splice(idx, 1);
 
6074                                 if (L.Browser.msTouch) {
 
6075                                         //Work around .type being readonly with MSPointer* events
 
6078                                         for (var i in touch) {
 
6079                                                 if (true) { //Make JSHint happy, we want to copy all properties
 
6081                                                         if (typeof prop === 'function') {
 
6082                                                                 newTouch[i] = prop.bind(touch);
 
6090                                 touch.type = 'dblclick';
 
6095                 obj[pre + touchstart + id] = onTouchStart;
 
6096                 obj[pre + touchend + id] = onTouchEnd;
 
6098                 //On msTouch we need to listen on the document otherwise a drag starting on the map and moving off screen will not come through to us
 
6099                 // so we will lose track of how many touches are ongoing
 
6100                 var endElement = L.Browser.msTouch ? document.documentElement : obj;
 
6102                 obj.addEventListener(touchstart, onTouchStart, false);
 
6103                 endElement.addEventListener(touchend, onTouchEnd, false);
 
6104                 if (L.Browser.msTouch) {
 
6105                         endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
 
6110         removeDoubleTapListener: function (obj, id) {
 
6111                 var pre = '_leaflet_';
 
6112                 obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
 
6113                 (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(this._touchend, obj[pre + this._touchend + id], false);
 
6114                 if (L.Browser.msTouch) {
 
6115                         document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
 
6122 L.extend(L.DomEvent, {
 
6125         _msDocumentListener: false,
 
6127         // Provides a touch events wrapper for msPointer events.
 
6128         // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
 
6130         addMsTouchListener: function (obj, type, handler, id) {
 
6134                         return this.addMsTouchListenerStart(obj, type, handler, id);
 
6136                         return this.addMsTouchListenerEnd(obj, type, handler, id);
 
6138                         return this.addMsTouchListenerMove(obj, type, handler, id);
 
6140                         throw 'Unknown touch event type';
 
6144         addMsTouchListenerStart: function (obj, type, handler, id) {
 
6145                 var pre = '_leaflet_',
 
6146                     touches = this._msTouches;
 
6148                 var cb = function (e) {
 
6150                         var alreadyInArray = false;
 
6151                         for (var i = 0; i < touches.length; i++) {
 
6152                                 if (touches[i].pointerId === e.pointerId) {
 
6153                                         alreadyInArray = true;
 
6157                         if (!alreadyInArray) {
 
6161                         e.touches = touches.slice();
 
6162                         e.changedTouches = [e];
 
6167                 obj[pre + 'touchstart' + id] = cb;
 
6168                 obj.addEventListener('MSPointerDown', cb, false);
 
6170                 // need to also listen for end events to keep the _msTouches list accurate
 
6171                 // this needs to be on the body and never go away
 
6172                 if (!this._msDocumentListener) {
 
6173                         var internalCb = function (e) {
 
6174                                 for (var i = 0; i < touches.length; i++) {
 
6175                                         if (touches[i].pointerId === e.pointerId) {
 
6176                                                 touches.splice(i, 1);
 
6181                         //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
 
6182                         document.documentElement.addEventListener('MSPointerUp', internalCb, false);
 
6183                         document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
 
6185                         this._msDocumentListener = true;
 
6191         addMsTouchListenerMove: function (obj, type, handler, id) {
 
6192                 var pre = '_leaflet_',
 
6193                     touches = this._msTouches;
 
6197                         // don't fire touch moves when mouse isn't down
 
6198                         if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
 
6200                         for (var i = 0; i < touches.length; i++) {
 
6201                                 if (touches[i].pointerId === e.pointerId) {
 
6207                         e.touches = touches.slice();
 
6208                         e.changedTouches = [e];
 
6213                 obj[pre + 'touchmove' + id] = cb;
 
6214                 obj.addEventListener('MSPointerMove', cb, false);
 
6219         addMsTouchListenerEnd: function (obj, type, handler, id) {
 
6220                 var pre = '_leaflet_',
 
6221                     touches = this._msTouches;
 
6223                 var cb = function (e) {
 
6224                         for (var i = 0; i < touches.length; i++) {
 
6225                                 if (touches[i].pointerId === e.pointerId) {
 
6226                                         touches.splice(i, 1);
 
6231                         e.touches = touches.slice();
 
6232                         e.changedTouches = [e];
 
6237                 obj[pre + 'touchend' + id] = cb;
 
6238                 obj.addEventListener('MSPointerUp', cb, false);
 
6239                 obj.addEventListener('MSPointerCancel', cb, false);
 
6244         removeMsTouchListener: function (obj, type, id) {
 
6245                 var pre = '_leaflet_',
 
6246                     cb = obj[pre + type + id];
 
6250                         obj.removeEventListener('MSPointerDown', cb, false);
 
6253                         obj.removeEventListener('MSPointerMove', cb, false);
 
6256                         obj.removeEventListener('MSPointerUp', cb, false);
 
6257                         obj.removeEventListener('MSPointerCancel', cb, false);
 
6267  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
 
6270 L.Map.mergeOptions({
 
6271         touchZoom: L.Browser.touch && !L.Browser.android23
 
6274 L.Map.TouchZoom = L.Handler.extend({
 
6275         addHooks: function () {
 
6276                 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
 
6279         removeHooks: function () {
 
6280                 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
 
6283         _onTouchStart: function (e) {
 
6284                 var map = this._map;
 
6286                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
 
6288                 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
 
6289                     p2 = map.mouseEventToLayerPoint(e.touches[1]),
 
6290                     viewCenter = map._getCenterLayerPoint();
 
6292                 this._startCenter = p1.add(p2)._divideBy(2);
 
6293                 this._startDist = p1.distanceTo(p2);
 
6295                 this._moved = false;
 
6296                 this._zooming = true;
 
6298                 this._centerOffset = viewCenter.subtract(this._startCenter);
 
6301                         map._panAnim.stop();
 
6305                     .on(document, 'touchmove', this._onTouchMove, this)
 
6306                     .on(document, 'touchend', this._onTouchEnd, this);
 
6308                 L.DomEvent.preventDefault(e);
 
6311         _onTouchMove: function (e) {
 
6312                 if (!e.touches || e.touches.length !== 2) { return; }
 
6314                 var map = this._map;
 
6316                 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
 
6317                     p2 = map.mouseEventToLayerPoint(e.touches[1]);
 
6319                 this._scale = p1.distanceTo(p2) / this._startDist;
 
6320                 this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
 
6322                 if (this._scale === 1) { return; }
 
6325                         L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
 
6335                 L.Util.cancelAnimFrame(this._animRequest);
 
6336                 this._animRequest = L.Util.requestAnimFrame(
 
6337                         this._updateOnMove, this, true, this._map._container);
 
6339                 L.DomEvent.preventDefault(e);
 
6342         _updateOnMove: function () {
 
6343                 var map = this._map,
 
6344                     origin = this._getScaleOrigin(),
 
6345                     center = map.layerPointToLatLng(origin);
 
6347                 map.fire('zoomanim', {
 
6349                         zoom: map.getScaleZoom(this._scale)
 
6352                 // Used 2 translates instead of transform-origin because of a very strange bug -
 
6353                 // it didn't count the origin on the first touch-zoom but worked correctly afterwards
 
6355                 map._tileBg.style[L.DomUtil.TRANSFORM] =
 
6356                         L.DomUtil.getTranslateString(this._delta) + ' ' +
 
6357                         L.DomUtil.getScaleString(this._scale, this._startCenter);
 
6360         _onTouchEnd: function (e) {
 
6361                 if (!this._moved || !this._zooming) { return; }
 
6363                 var map = this._map;
 
6365                 this._zooming = false;
 
6366                 L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
 
6369                     .off(document, 'touchmove', this._onTouchMove)
 
6370                     .off(document, 'touchend', this._onTouchEnd);
 
6372                 var origin = this._getScaleOrigin(),
 
6373                     center = map.layerPointToLatLng(origin),
 
6375                     oldZoom = map.getZoom(),
 
6376                     floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
 
6377                     roundZoomDelta = (floatZoomDelta > 0 ?
 
6378                             Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
 
6380                     zoom = map._limitZoom(oldZoom + roundZoomDelta);
 
6382                 map.fire('zoomanim', {
 
6387                 map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
 
6390         _getScaleOrigin: function () {
 
6391                 var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
 
6392                 return this._startCenter.add(centerOffset);
 
6396 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
 
6400  * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
 
6403 L.Map.mergeOptions({
 
6407 L.Map.BoxZoom = L.Handler.extend({
 
6408         initialize: function (map) {
 
6410                 this._container = map._container;
 
6411                 this._pane = map._panes.overlayPane;
 
6414         addHooks: function () {
 
6415                 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
 
6418         removeHooks: function () {
 
6419                 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
 
6422         _onMouseDown: function (e) {
 
6423                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
 
6425                 L.DomUtil.disableTextSelection();
 
6427                 this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
 
6429                 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
 
6430                 L.DomUtil.setPosition(this._box, this._startLayerPoint);
 
6432                 //TODO refactor: move cursor to styles
 
6433                 this._container.style.cursor = 'crosshair';
 
6436                     .on(document, 'mousemove', this._onMouseMove, this)
 
6437                     .on(document, 'mouseup', this._onMouseUp, this)
 
6440                 this._map.fire("boxzoomstart");
 
6443         _onMouseMove: function (e) {
 
6444                 var startPoint = this._startLayerPoint,
 
6447                     layerPoint = this._map.mouseEventToLayerPoint(e),
 
6448                     offset = layerPoint.subtract(startPoint),
 
6450                     newPos = new L.Point(
 
6451                         Math.min(layerPoint.x, startPoint.x),
 
6452                         Math.min(layerPoint.y, startPoint.y));
 
6454                 L.DomUtil.setPosition(box, newPos);
 
6456                 // TODO refactor: remove hardcoded 4 pixels
 
6457                 box.style.width  = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
 
6458                 box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
 
6461         _onMouseUp: function (e) {
 
6462                 this._pane.removeChild(this._box);
 
6463                 this._container.style.cursor = '';
 
6465                 L.DomUtil.enableTextSelection();
 
6468                     .off(document, 'mousemove', this._onMouseMove)
 
6469                     .off(document, 'mouseup', this._onMouseUp);
 
6471                 var map = this._map,
 
6472                     layerPoint = map.mouseEventToLayerPoint(e),
 
6474                     bounds = new L.LatLngBounds(
 
6475                         map.layerPointToLatLng(this._startLayerPoint),
 
6476                         map.layerPointToLatLng(layerPoint));
 
6478                 map.fitBounds(bounds);
 
6480                 map.fire("boxzoomend", {
 
6481                         boxZoomBounds: bounds
 
6486 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
 
6489 L.Map.mergeOptions({
 
6491         keyboardPanOffset: 80,
 
6492         keyboardZoomOffset: 1
 
6495 L.Map.Keyboard = L.Handler.extend({
 
6497         // list of e.keyCode values for particular actions
 
6503                 zoomIn:  [187, 107, 61],
 
6507         initialize: function (map) {
 
6510                 this._setPanOffset(map.options.keyboardPanOffset);
 
6511                 this._setZoomOffset(map.options.keyboardZoomOffset);
 
6514         addHooks: function () {
 
6515                 var container = this._map._container;
 
6517                 // make the container focusable by tabbing
 
6518                 if (container.tabIndex === -1) {
 
6519                         container.tabIndex = "0";
 
6523                     .addListener(container, 'focus', this._onFocus, this)
 
6524                     .addListener(container, 'blur', this._onBlur, this)
 
6525                     .addListener(container, 'mousedown', this._onMouseDown, this);
 
6528                     .on('focus', this._addHooks, this)
 
6529                     .on('blur', this._removeHooks, this);
 
6532         removeHooks: function () {
 
6533                 this._removeHooks();
 
6535                 var container = this._map._container;
 
6538                     .removeListener(container, 'focus', this._onFocus, this)
 
6539                     .removeListener(container, 'blur', this._onBlur, this)
 
6540                     .removeListener(container, 'mousedown', this._onMouseDown, this);
 
6543                     .off('focus', this._addHooks, this)
 
6544                     .off('blur', this._removeHooks, this);
 
6547         _onMouseDown: function () {
 
6548                 if (!this._focused) {
 
6549                         this._map._container.focus();
 
6553         _onFocus: function () {
 
6554                 this._focused = true;
 
6555                 this._map.fire('focus');
 
6558         _onBlur: function () {
 
6559                 this._focused = false;
 
6560                 this._map.fire('blur');
 
6563         _setPanOffset: function (pan) {
 
6564                 var keys = this._panKeys = {},
 
6565                     codes = this.keyCodes,
 
6568                 for (i = 0, len = codes.left.length; i < len; i++) {
 
6569                         keys[codes.left[i]] = [-1 * pan, 0];
 
6571                 for (i = 0, len = codes.right.length; i < len; i++) {
 
6572                         keys[codes.right[i]] = [pan, 0];
 
6574                 for (i = 0, len = codes.down.length; i < len; i++) {
 
6575                         keys[codes.down[i]] = [0, pan];
 
6577                 for (i = 0, len = codes.up.length; i < len; i++) {
 
6578                         keys[codes.up[i]] = [0, -1 * pan];
 
6582         _setZoomOffset: function (zoom) {
 
6583                 var keys = this._zoomKeys = {},
 
6584                     codes = this.keyCodes,
 
6587                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
 
6588                         keys[codes.zoomIn[i]] = zoom;
 
6590                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
 
6591                         keys[codes.zoomOut[i]] = -zoom;
 
6595         _addHooks: function () {
 
6596                 L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this);
 
6599         _removeHooks: function () {
 
6600                 L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this);
 
6603         _onKeyDown: function (e) {
 
6604                 var key = e.keyCode;
 
6606                 if (this._panKeys.hasOwnProperty(key)) {
 
6607                         this._map.panBy(this._panKeys[key]);
 
6609                 } else if (this._zoomKeys.hasOwnProperty(key)) {
 
6610                         this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]);
 
6620 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
 
6624  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
 
6627 L.Handler.MarkerDrag = L.Handler.extend({
 
6628         initialize: function (marker) {
 
6629                 this._marker = marker;
 
6632         addHooks: function () {
 
6633                 var icon = this._marker._icon;
 
6634                 if (!this._draggable) {
 
6635                         this._draggable = new L.Draggable(icon, icon)
 
6636                             .on('dragstart', this._onDragStart, this)
 
6637                             .on('drag', this._onDrag, this)
 
6638                             .on('dragend', this._onDragEnd, this);
 
6640                 this._draggable.enable();
 
6643         removeHooks: function () {
 
6644                 this._draggable.disable();
 
6647         moved: function () {
 
6648                 return this._draggable && this._draggable._moved;
 
6651         _onDragStart: function (e) {
 
6658         _onDrag: function (e) {
 
6659                 var marker = this._marker,
 
6660                     shadow = marker._shadow,
 
6661                     iconPos = L.DomUtil.getPosition(marker._icon),
 
6662                     latlng = marker._map.layerPointToLatLng(iconPos);
 
6664                 // update shadow position
 
6666                         L.DomUtil.setPosition(shadow, iconPos);
 
6669                 marker._latlng = latlng;
 
6672                     .fire('move', {latlng: latlng})
 
6676         _onDragEnd: function () {
 
6684 L.Handler.PolyEdit = L.Handler.extend({
 
6686                 icon: new L.DivIcon({
 
6687                         iconSize: new L.Point(8, 8),
 
6688                         className: 'leaflet-div-icon leaflet-editing-icon'
 
6692         initialize: function (poly, options) {
 
6694                 L.setOptions(this, options);
 
6697         addHooks: function () {
 
6698                 if (this._poly._map) {
 
6699                         if (!this._markerGroup) {
 
6700                                 this._initMarkers();
 
6702                         this._poly._map.addLayer(this._markerGroup);
 
6706         removeHooks: function () {
 
6707                 if (this._poly._map) {
 
6708                         this._poly._map.removeLayer(this._markerGroup);
 
6709                         delete this._markerGroup;
 
6710                         delete this._markers;
 
6714         updateMarkers: function () {
 
6715                 this._markerGroup.clearLayers();
 
6716                 this._initMarkers();
 
6719         _initMarkers: function () {
 
6720                 if (!this._markerGroup) {
 
6721                         this._markerGroup = new L.LayerGroup();
 
6725                 var latlngs = this._poly._latlngs,
 
6728                 // TODO refactor holes implementation in Polygon to support it here
 
6730                 for (i = 0, len = latlngs.length; i < len; i++) {
 
6732                         marker = this._createMarker(latlngs[i], i);
 
6733                         marker.on('click', this._onMarkerClick, this);
 
6734                         this._markers.push(marker);
 
6737                 var markerLeft, markerRight;
 
6739                 for (i = 0, j = len - 1; i < len; j = i++) {
 
6740                         if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
 
6744                         markerLeft = this._markers[j];
 
6745                         markerRight = this._markers[i];
 
6747                         this._createMiddleMarker(markerLeft, markerRight);
 
6748                         this._updatePrevNext(markerLeft, markerRight);
 
6752         _createMarker: function (latlng, index) {
 
6753                 var marker = new L.Marker(latlng, {
 
6755                         icon: this.options.icon
 
6758                 marker._origLatLng = latlng;
 
6759                 marker._index = index;
 
6761                 marker.on('drag', this._onMarkerDrag, this);
 
6762                 marker.on('dragend', this._fireEdit, this);
 
6764                 this._markerGroup.addLayer(marker);
 
6769         _fireEdit: function () {
 
6770                 this._poly.fire('edit');
 
6773         _onMarkerDrag: function (e) {
 
6774                 var marker = e.target;
 
6776                 L.extend(marker._origLatLng, marker._latlng);
 
6778                 if (marker._middleLeft) {
 
6779                         marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
 
6781                 if (marker._middleRight) {
 
6782                         marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
 
6785                 this._poly.redraw();
 
6788         _onMarkerClick: function (e) {
 
6789                 // we want to remove the marker on click, but if latlng count < 3, polyline would be invalid
 
6790                 if (this._poly._latlngs.length < 3) { return; }
 
6792                 var marker = e.target,
 
6795                 // remove the marker
 
6796                 this._markerGroup.removeLayer(marker);
 
6797                 this._markers.splice(i, 1);
 
6798                 this._poly.spliceLatLngs(i, 1);
 
6799                 this._updateIndexes(i, -1);
 
6801                 // update prev/next links of adjacent markers
 
6802                 this._updatePrevNext(marker._prev, marker._next);
 
6804                 // remove ghost markers near the removed marker
 
6805                 if (marker._middleLeft) {
 
6806                         this._markerGroup.removeLayer(marker._middleLeft);
 
6808                 if (marker._middleRight) {
 
6809                         this._markerGroup.removeLayer(marker._middleRight);
 
6812                 // create a ghost marker in place of the removed one
 
6813                 if (marker._prev && marker._next) {
 
6814                         this._createMiddleMarker(marker._prev, marker._next);
 
6816                 } else if (!marker._prev) {
 
6817                         marker._next._middleLeft = null;
 
6819                 } else if (!marker._next) {
 
6820                         marker._prev._middleRight = null;
 
6823                 this._poly.fire('edit');
 
6826         _updateIndexes: function (index, delta) {
 
6827                 this._markerGroup.eachLayer(function (marker) {
 
6828                         if (marker._index > index) {
 
6829                                 marker._index += delta;
 
6834         _createMiddleMarker: function (marker1, marker2) {
 
6835                 var latlng = this._getMiddleLatLng(marker1, marker2),
 
6836                     marker = this._createMarker(latlng),
 
6841                 marker.setOpacity(0.6);
 
6843                 marker1._middleRight = marker2._middleLeft = marker;
 
6845                 onDragStart = function () {
 
6846                         var i = marker2._index;
 
6851                             .off('click', onClick)
 
6852                             .on('click', this._onMarkerClick, this);
 
6854                         latlng.lat = marker.getLatLng().lat;
 
6855                         latlng.lng = marker.getLatLng().lng;
 
6856                         this._poly.spliceLatLngs(i, 0, latlng);
 
6857                         this._markers.splice(i, 0, marker);
 
6859                         marker.setOpacity(1);
 
6861                         this._updateIndexes(i, 1);
 
6863                         this._updatePrevNext(marker1, marker);
 
6864                         this._updatePrevNext(marker, marker2);
 
6867                 onDragEnd = function () {
 
6868                         marker.off('dragstart', onDragStart, this);
 
6869                         marker.off('dragend', onDragEnd, this);
 
6871                         this._createMiddleMarker(marker1, marker);
 
6872                         this._createMiddleMarker(marker, marker2);
 
6875                 onClick = function () {
 
6876                         onDragStart.call(this);
 
6877                         onDragEnd.call(this);
 
6878                         this._poly.fire('edit');
 
6882                     .on('click', onClick, this)
 
6883                     .on('dragstart', onDragStart, this)
 
6884                     .on('dragend', onDragEnd, this);
 
6886                 this._markerGroup.addLayer(marker);
 
6889         _updatePrevNext: function (marker1, marker2) {
 
6891                         marker1._next = marker2;
 
6894                         marker2._prev = marker1;
 
6898         _getMiddleLatLng: function (marker1, marker2) {
 
6899                 var map = this._poly._map,
 
6900                     p1 = map.latLngToLayerPoint(marker1.getLatLng()),
 
6901                     p2 = map.latLngToLayerPoint(marker2.getLatLng());
 
6903                 return map.layerPointToLatLng(p1._add(p2)._divideBy(2));
 
6909 L.Control = L.Class.extend({
 
6911                 position: 'topright'
 
6914         initialize: function (options) {
 
6915                 L.setOptions(this, options);
 
6918         getPosition: function () {
 
6919                 return this.options.position;
 
6922         setPosition: function (position) {
 
6923                 var map = this._map;
 
6926                         map.removeControl(this);
 
6929                 this.options.position = position;
 
6932                         map.addControl(this);
 
6938         addTo: function (map) {
 
6941                 var container = this._container = this.onAdd(map),
 
6942                     pos = this.getPosition(),
 
6943                     corner = map._controlCorners[pos];
 
6945                 L.DomUtil.addClass(container, 'leaflet-control');
 
6947                 if (pos.indexOf('bottom') !== -1) {
 
6948                         corner.insertBefore(container, corner.firstChild);
 
6950                         corner.appendChild(container);
 
6956         removeFrom: function (map) {
 
6957                 var pos = this.getPosition(),
 
6958                     corner = map._controlCorners[pos];
 
6960                 corner.removeChild(this._container);
 
6963                 if (this.onRemove) {
 
6971 L.control = function (options) {
 
6972         return new L.Control(options);
 
6977         addControl: function (control) {
 
6978                 control.addTo(this);
 
6982         removeControl: function (control) {
 
6983                 control.removeFrom(this);
 
6987         _initControlPos: function () {
 
6988                 var corners = this._controlCorners = {},
 
6990                     container = this._controlContainer =
 
6991                             L.DomUtil.create('div', l + 'control-container', this._container);
 
6993                 function createCorner(vSide, hSide) {
 
6994                         var className = l + vSide + ' ' + l + hSide;
 
6996                         corners[vSide + hSide] = L.DomUtil.create('div', className, container);
 
6999                 createCorner('top', 'left');
 
7000                 createCorner('top', 'right');
 
7001                 createCorner('bottom', 'left');
 
7002                 createCorner('bottom', 'right');
 
7007 L.Control.Zoom = L.Control.extend({
 
7012         onAdd: function (map) {
 
7013                 var className = 'leaflet-control-zoom',
 
7014                     container = L.DomUtil.create('div', className);
 
7018                 this._createButton('Zoom in', className + '-in', container, this._zoomIn, this);
 
7019                 this._createButton('Zoom out', className + '-out', container, this._zoomOut, this);
 
7024         _zoomIn: function (e) {
 
7025                 this._map.zoomIn(e.shiftKey ? 3 : 1);
 
7028         _zoomOut: function (e) {
 
7029                 this._map.zoomOut(e.shiftKey ? 3 : 1);
 
7032         _createButton: function (title, className, container, fn, context) {
 
7033                 var link = L.DomUtil.create('a', className, container);
 
7038                     .on(link, 'click', L.DomEvent.stopPropagation)
 
7039                     .on(link, 'mousedown', L.DomEvent.stopPropagation)
 
7040                     .on(link, 'dblclick', L.DomEvent.stopPropagation)
 
7041                     .on(link, 'click', L.DomEvent.preventDefault)
 
7042                     .on(link, 'click', fn, context);
 
7048 L.Map.mergeOptions({
 
7052 L.Map.addInitHook(function () {
 
7053         if (this.options.zoomControl) {
 
7054                 this.zoomControl = new L.Control.Zoom();
 
7055                 this.addControl(this.zoomControl);
 
7059 L.control.zoom = function (options) {
 
7060         return new L.Control.Zoom(options);
 
7065 L.Control.Attribution = L.Control.extend({
 
7067                 position: 'bottomright',
 
7068                 prefix: 'Powered by <a href="http://leafletjs.com">Leaflet</a>'
 
7071         initialize: function (options) {
 
7072                 L.setOptions(this, options);
 
7074                 this._attributions = {};
 
7077         onAdd: function (map) {
 
7078                 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
 
7079                 L.DomEvent.disableClickPropagation(this._container);
 
7082                     .on('layeradd', this._onLayerAdd, this)
 
7083                     .on('layerremove', this._onLayerRemove, this);
 
7087                 return this._container;
 
7090         onRemove: function (map) {
 
7092                     .off('layeradd', this._onLayerAdd)
 
7093                     .off('layerremove', this._onLayerRemove);
 
7097         setPrefix: function (prefix) {
 
7098                 this.options.prefix = prefix;
 
7103         addAttribution: function (text) {
 
7104                 if (!text) { return; }
 
7106                 if (!this._attributions[text]) {
 
7107                         this._attributions[text] = 0;
 
7109                 this._attributions[text]++;
 
7116         removeAttribution: function (text) {
 
7117                 if (!text) { return; }
 
7119                 this._attributions[text]--;
 
7125         _update: function () {
 
7126                 if (!this._map) { return; }
 
7130                 for (var i in this._attributions) {
 
7131                         if (this._attributions.hasOwnProperty(i) && this._attributions[i]) {
 
7136                 var prefixAndAttribs = [];
 
7138                 if (this.options.prefix) {
 
7139                         prefixAndAttribs.push(this.options.prefix);
 
7141                 if (attribs.length) {
 
7142                         prefixAndAttribs.push(attribs.join(', '));
 
7145                 this._container.innerHTML = prefixAndAttribs.join(' — ');
 
7148         _onLayerAdd: function (e) {
 
7149                 if (e.layer.getAttribution) {
 
7150                         this.addAttribution(e.layer.getAttribution());
 
7154         _onLayerRemove: function (e) {
 
7155                 if (e.layer.getAttribution) {
 
7156                         this.removeAttribution(e.layer.getAttribution());
 
7161 L.Map.mergeOptions({
 
7162         attributionControl: true
 
7165 L.Map.addInitHook(function () {
 
7166         if (this.options.attributionControl) {
 
7167                 this.attributionControl = (new L.Control.Attribution()).addTo(this);
 
7171 L.control.attribution = function (options) {
 
7172         return new L.Control.Attribution(options);
 
7176 L.Control.Scale = L.Control.extend({
 
7178                 position: 'bottomleft',
 
7182                 updateWhenIdle: false
 
7185         onAdd: function (map) {
 
7188                 var className = 'leaflet-control-scale',
 
7189                     container = L.DomUtil.create('div', className),
 
7190                     options = this.options;
 
7192                 this._addScales(options, className, container);
 
7194                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
 
7195                 map.whenReady(this._update, this);
 
7200         onRemove: function (map) {
 
7201                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
 
7204         _addScales: function (options, className, container) {
 
7205                 if (options.metric) {
 
7206                         this._mScale = L.DomUtil.create('div', className + '-line', container);
 
7208                 if (options.imperial) {
 
7209                         this._iScale = L.DomUtil.create('div', className + '-line', container);
 
7213         _update: function () {
 
7214                 var bounds = this._map.getBounds(),
 
7215                     centerLat = bounds.getCenter().lat,
 
7216                     halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
 
7217                     dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
 
7219                     size = this._map.getSize(),
 
7220                     options = this.options,
 
7224                         maxMeters = dist * (options.maxWidth / size.x);
 
7227                 this._updateScales(options, maxMeters);
 
7230         _updateScales: function (options, maxMeters) {
 
7231                 if (options.metric && maxMeters) {
 
7232                         this._updateMetric(maxMeters);
 
7235                 if (options.imperial && maxMeters) {
 
7236                         this._updateImperial(maxMeters);
 
7240         _updateMetric: function (maxMeters) {
 
7241                 var meters = this._getRoundNum(maxMeters);
 
7243                 this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
 
7244                 this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
 
7247         _updateImperial: function (maxMeters) {
 
7248                 var maxFeet = maxMeters * 3.2808399,
 
7249                     scale = this._iScale,
 
7250                     maxMiles, miles, feet;
 
7252                 if (maxFeet > 5280) {
 
7253                         maxMiles = maxFeet / 5280;
 
7254                         miles = this._getRoundNum(maxMiles);
 
7256                         scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
 
7257                         scale.innerHTML = miles + ' mi';
 
7260                         feet = this._getRoundNum(maxFeet);
 
7262                         scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
 
7263                         scale.innerHTML = feet + ' ft';
 
7267         _getScaleWidth: function (ratio) {
 
7268                 return Math.round(this.options.maxWidth * ratio) - 10;
 
7271         _getRoundNum: function (num) {
 
7272                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
 
7275                 d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
 
7281 L.control.scale = function (options) {
 
7282         return new L.Control.Scale(options);
 
7286 L.Control.Layers = L.Control.extend({
 
7289                 position: 'topright',
 
7293         initialize: function (baseLayers, overlays, options) {
 
7294                 L.setOptions(this, options);
 
7297                 this._lastZIndex = 0;
 
7298                 this._handlingClick = false;
 
7300                 for (var i in baseLayers) {
 
7301                         if (baseLayers.hasOwnProperty(i)) {
 
7302                                 this._addLayer(baseLayers[i], i);
 
7306                 for (i in overlays) {
 
7307                         if (overlays.hasOwnProperty(i)) {
 
7308                                 this._addLayer(overlays[i], i, true);
 
7313         onAdd: function (map) {
 
7318                     .on('layeradd', this._update, this)
 
7319                     .on('layerremove', this._update, this);
 
7321                 return this._container;
 
7324         onRemove: function (map) {
 
7326                     .off('layeradd', this._update)
 
7327                     .off('layerremove', this._update);
 
7330         addBaseLayer: function (layer, name) {
 
7331                 this._addLayer(layer, name);
 
7336         addOverlay: function (layer, name) {
 
7337                 this._addLayer(layer, name, true);
 
7342         removeLayer: function (layer) {
 
7343                 var id = L.stamp(layer);
 
7344                 delete this._layers[id];
 
7349         _initLayout: function () {
 
7350                 var className = 'leaflet-control-layers',
 
7351                     container = this._container = L.DomUtil.create('div', className);
 
7353                 if (!L.Browser.touch) {
 
7354                         L.DomEvent.disableClickPropagation(container);
 
7356                         L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
 
7359                 var form = this._form = L.DomUtil.create('form', className + '-list');
 
7361                 if (this.options.collapsed) {
 
7363                             .on(container, 'mouseover', this._expand, this)
 
7364                             .on(container, 'mouseout', this._collapse, this);
 
7366                         var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
 
7368                         link.title = 'Layers';
 
7370                         if (L.Browser.touch) {
 
7372                                     .on(link, 'click', L.DomEvent.stopPropagation)
 
7373                                     .on(link, 'click', L.DomEvent.preventDefault)
 
7374                                     .on(link, 'click', this._expand, this);
 
7377                                 L.DomEvent.on(link, 'focus', this._expand, this);
 
7380                         this._map.on('movestart', this._collapse, this);
 
7381                         // TODO keyboard accessibility
 
7386                 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
 
7387                 this._separator = L.DomUtil.create('div', className + '-separator', form);
 
7388                 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
 
7390                 container.appendChild(form);
 
7393         _addLayer: function (layer, name, overlay) {
 
7394                 var id = L.stamp(layer);
 
7396                 this._layers[id] = {
 
7402                 if (this.options.autoZIndex && layer.setZIndex) {
 
7404                         layer.setZIndex(this._lastZIndex);
 
7408         _update: function () {
 
7409                 if (!this._container || this._handlingClick) {
 
7413                 this._baseLayersList.innerHTML = '';
 
7414                 this._overlaysList.innerHTML = '';
 
7416                 var baseLayersPresent = false,
 
7417                     overlaysPresent = false;
 
7419                 for (var i in this._layers) {
 
7420                         if (this._layers.hasOwnProperty(i)) {
 
7421                                 var obj = this._layers[i];
 
7423                                 overlaysPresent = overlaysPresent || obj.overlay;
 
7424                                 baseLayersPresent = baseLayersPresent || !obj.overlay;
 
7428                 this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
 
7431         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
 
7432         _createRadioElement: function (name, checked) {
 
7434                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
 
7436                         radioHtml += ' checked="checked"';
 
7440                 var radioFragment = document.createElement('div');
 
7441                 radioFragment.innerHTML = radioHtml;
 
7443                 return radioFragment.firstChild;
 
7446         _addItem: function (obj) {
 
7447                 var label = document.createElement('label'),
 
7449                     checked = this._map.hasLayer(obj.layer);
 
7452                         input = document.createElement('input');
 
7453                         input.type = 'checkbox';
 
7454                         input.className = 'leaflet-control-layers-selector';
 
7455                         input.defaultChecked = checked;
 
7457                         input = this._createRadioElement('leaflet-base-layers', checked);
 
7460                 input.layerId = L.stamp(obj.layer);
 
7462                 L.DomEvent.on(input, 'click', this._onInputClick, this);
 
7464                 var name = document.createElement('span');
 
7465                 name.innerHTML = ' ' + obj.name;
 
7467                 label.appendChild(input);
 
7468                 label.appendChild(name);
 
7470                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
 
7471                 container.appendChild(label);
 
7474         _onInputClick: function () {
 
7476                     inputs = this._form.getElementsByTagName('input'),
 
7477                     inputsLen = inputs.length,
 
7480                 this._handlingClick = true;
 
7482                 for (i = 0; i < inputsLen; i++) {
 
7484                         obj = this._layers[input.layerId];
 
7486                         if (input.checked && !this._map.hasLayer(obj.layer)) {
 
7487                                 this._map.addLayer(obj.layer);
 
7489                                         baseLayer = obj.layer;
 
7491                         } else if (!input.checked && this._map.hasLayer(obj.layer)) {
 
7492                                 this._map.removeLayer(obj.layer);
 
7497                         this._map.fire('baselayerchange', {layer: baseLayer});
 
7500                 this._handlingClick = false;
 
7503         _expand: function () {
 
7504                 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
 
7507         _collapse: function () {
 
7508                 this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
 
7512 L.control.layers = function (baseLayers, overlays, options) {
 
7513         return new L.Control.Layers(baseLayers, overlays, options);
 
7518  * L.PosAnimation is used by Leaflet internally for pan animations.
 
7521 L.PosAnimation = L.Class.extend({
 
7522         includes: L.Mixin.Events,
 
7524         run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
 
7528                 this._inProgress = true;
 
7532                 el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
 
7533                         's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
 
7535                 L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
 
7536                 L.DomUtil.setPosition(el, newPos);
 
7538                 // toggle reflow, Chrome flickers for some reason if you don't do this
 
7539                 L.Util.falseFn(el.offsetWidth);
 
7541                 // there's no native way to track value updates of tranisitioned properties, so we imitate this
 
7542                 this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50);
 
7546                 if (!this._inProgress) { return; }
 
7548                 // if we just removed the transition property, the element would jump to its final position,
 
7549                 // so we need to make it stay at the current position
 
7551                 L.DomUtil.setPosition(this._el, this._getPos());
 
7552                 this._onTransitionEnd();
 
7553                 L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
 
7556         // you can't easily get intermediate values of properties animated with CSS3 Transitions,
 
7557         // we need to parse computed style (in case of transform it returns matrix string)
 
7559         _transformRe: /(-?[\d\.]+), (-?[\d\.]+)\)/,
 
7561         _getPos: function () {
 
7562                 var left, top, matches,
 
7564                     style = window.getComputedStyle(el);
 
7566                 if (L.Browser.any3d) {
 
7567                         matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
 
7568                         left = parseFloat(matches[1]);
 
7569                         top  = parseFloat(matches[2]);
 
7571                         left = parseFloat(style.left);
 
7572                         top  = parseFloat(style.top);
 
7575                 return new L.Point(left, top, true);
 
7578         _onTransitionEnd: function () {
 
7579                 L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
 
7581                 if (!this._inProgress) { return; }
 
7582                 this._inProgress = false;
 
7584                 this._el.style[L.DomUtil.TRANSITION] = '';
 
7586                 clearInterval(this._stepTimer);
 
7588                 this.fire('step').fire('end');
 
7597         setView: function (center, zoom, forceReset) {
 
7598                 zoom = this._limitZoom(zoom);
 
7600                 var zoomChanged = (this._zoom !== zoom);
 
7602                 if (this._loaded && !forceReset && this._layers) {
 
7604                         if (this._panAnim) {
 
7605                                 this._panAnim.stop();
 
7608                         var done = (zoomChanged ?
 
7609                                 this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
 
7610                                 this._panByIfClose(center));
 
7612                         // exit if animated pan or zoom started
 
7614                                 clearTimeout(this._sizeTimer);
 
7619                 // reset the map view
 
7620                 this._resetView(center, zoom);
 
7625         panBy: function (offset, duration, easeLinearity) {
 
7626                 offset = L.point(offset);
 
7628                 if (!(offset.x || offset.y)) {
 
7632                 if (!this._panAnim) {
 
7633                         this._panAnim = new L.PosAnimation();
 
7636                                 'step': this._onPanTransitionStep,
 
7637                                 'end': this._onPanTransitionEnd
 
7641                 this.fire('movestart');
 
7643                 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
 
7645                 var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset);
 
7646                 this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
 
7651         _onPanTransitionStep: function () {
 
7655         _onPanTransitionEnd: function () {
 
7656                 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
 
7657                 this.fire('moveend');
 
7660         _panByIfClose: function (center) {
 
7661                 // difference between the new and current centers in pixels
 
7662                 var offset = this._getCenterOffset(center)._floor();
 
7664                 if (this._offsetIsWithinView(offset)) {
 
7671         _offsetIsWithinView: function (offset, multiplyFactor) {
 
7672                 var m = multiplyFactor || 1,
 
7673                     size = this.getSize();
 
7675                 return (Math.abs(offset.x) <= size.x * m) &&
 
7676                        (Math.abs(offset.y) <= size.y * m);
 
7682  * L.PosAnimation fallback implementation that powers Leaflet pan animations
 
7683  * in browsers that don't support CSS3 Transitions.
 
7686 L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
 
7688         run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
 
7692                 this._inProgress = true;
 
7693                 this._duration = duration || 0.25;
 
7694                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
 
7696                 this._startPos = L.DomUtil.getPosition(el);
 
7697                 this._offset = newPos.subtract(this._startPos);
 
7698                 this._startTime = +new Date();
 
7706                 if (!this._inProgress) { return; }
 
7712         _animate: function () {
 
7714                 this._animId = L.Util.requestAnimFrame(this._animate, this);
 
7718         _step: function () {
 
7719                 var elapsed = (+new Date()) - this._startTime,
 
7720                     duration = this._duration * 1000;
 
7722                 if (elapsed < duration) {
 
7723                         this._runFrame(this._easeOut(elapsed / duration));
 
7730         _runFrame: function (progress) {
 
7731                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
 
7732                 L.DomUtil.setPosition(this._el, pos);
 
7737         _complete: function () {
 
7738                 L.Util.cancelAnimFrame(this._animId);
 
7740                 this._inProgress = false;
 
7744         _easeOut: function (t) {
 
7745                 return 1 - Math.pow(1 - t, this._easeOutPower);
 
7750 L.Map.mergeOptions({
 
7751         zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
 
7754 if (L.DomUtil.TRANSITION) {
 
7755         L.Map.addInitHook(function () {
 
7756                 L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
 
7760 L.Map.include(!L.DomUtil.TRANSITION ? {} : {
 
7762         _zoomToIfClose: function (center, zoom) {
 
7764                 if (this._animatingZoom) { return true; }
 
7766                 if (!this.options.zoomAnimation) { return false; }
 
7768                 var scale = this.getZoomScale(zoom),
 
7769                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
 
7771                 // if offset does not exceed half of the view
 
7772                 if (!this._offsetIsWithinView(offset, 1)) { return false; }
 
7774                 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
 
7780                 this.fire('zoomanim', {
 
7785                 var origin = this._getCenterLayerPoint().add(offset);
 
7787                 this._prepareTileBg();
 
7788                 this._runAnimation(center, zoom, scale, origin);
 
7793         _catchTransitionEnd: function (e) {
 
7794                 if (this._animatingZoom) {
 
7795                         this._onZoomTransitionEnd();
 
7799         _runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
 
7800                 this._animateToCenter = center;
 
7801                 this._animateToZoom = zoom;
 
7802                 this._animatingZoom = true;
 
7805                         L.Draggable._disabled = true;
 
7808                 var transform = L.DomUtil.TRANSFORM,
 
7809                     tileBg = this._tileBg;
 
7811                 clearTimeout(this._clearTileBgTimer);
 
7813                 L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
 
7815                 var scaleStr = L.DomUtil.getScaleString(scale, origin),
 
7816                     oldTransform = tileBg.style[transform];
 
7818                 tileBg.style[transform] = backwardsTransform ?
 
7819                         oldTransform + ' ' + scaleStr :
 
7820                         scaleStr + ' ' + oldTransform;
 
7823         _prepareTileBg: function () {
 
7824                 var tilePane = this._tilePane,
 
7825                     tileBg = this._tileBg;
 
7827                 // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
 
7828                 if (tileBg && this._getLoadedTilesPercentage(tileBg) > 0.5 &&
 
7829                                   this._getLoadedTilesPercentage(tilePane) < 0.5) {
 
7831                         tilePane.style.visibility = 'hidden';
 
7832                         tilePane.empty = true;
 
7833                         this._stopLoadingImages(tilePane);
 
7838                         tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
 
7839                         tileBg.style.zIndex = 1;
 
7842                 // prepare the background pane to become the main tile pane
 
7843                 tileBg.style[L.DomUtil.TRANSFORM] = '';
 
7844                 tileBg.style.visibility = 'hidden';
 
7846                 // tells tile layers to reinitialize their containers
 
7847                 tileBg.empty = true; //new FG
 
7848                 tilePane.empty = false; //new BG
 
7850                 //Switch out the current layer to be the new bg layer (And vice-versa)
 
7851                 this._tilePane = this._panes.tilePane = tileBg;
 
7852                 var newTileBg = this._tileBg = tilePane;
 
7854                 L.DomUtil.addClass(newTileBg, 'leaflet-zoom-animated');
 
7856                 this._stopLoadingImages(newTileBg);
 
7859         _getLoadedTilesPercentage: function (container) {
 
7860                 var tiles = container.getElementsByTagName('img'),
 
7863                 for (i = 0, len = tiles.length; i < len; i++) {
 
7864                         if (tiles[i].complete) {
 
7871         // stops loading all tiles in the background layer
 
7872         _stopLoadingImages: function (container) {
 
7873                 var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
 
7876                 for (i = 0, len = tiles.length; i < len; i++) {
 
7879                         if (!tile.complete) {
 
7880                                 tile.onload = L.Util.falseFn;
 
7881                                 tile.onerror = L.Util.falseFn;
 
7882                                 tile.src = L.Util.emptyImageUrl;
 
7884                                 tile.parentNode.removeChild(tile);
 
7889         _onZoomTransitionEnd: function () {
 
7890                 this._restoreTileFront();
 
7891                 L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
 
7892                 this._resetView(this._animateToCenter, this._animateToZoom, true, true);
 
7894                 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
 
7895                 this._animatingZoom = false;
 
7898                         L.Draggable._disabled = false;
 
7902         _restoreTileFront: function () {
 
7903                 this._tilePane.innerHTML = '';
 
7904                 this._tilePane.style.visibility = '';
 
7905                 this._tilePane.style.zIndex = 2;
 
7906                 this._tileBg.style.zIndex = 1;
 
7909         _clearTileBg: function () {
 
7910                 if (!this._animatingZoom && !this.touchZoom._zooming) {
 
7911                         this._tileBg.innerHTML = '';
 
7918  * Provides L.Map with convenient shortcuts for W3C geolocation.
 
7922         _defaultLocateOptions: {
 
7928                 enableHighAccuracy: false
 
7931         locate: function (/*Object*/ options) {
 
7933                 options = this._locationOptions = L.extend(this._defaultLocateOptions, options);
 
7935                 if (!navigator.geolocation) {
 
7936                         this._handleGeolocationError({
 
7938                                 message: "Geolocation not supported."
 
7943                 var onResponse = L.bind(this._handleGeolocationResponse, this),
 
7944                         onError = L.bind(this._handleGeolocationError, this);
 
7946                 if (options.watch) {
 
7947                         this._locationWatchId =
 
7948                                 navigator.geolocation.watchPosition(onResponse, onError, options);
 
7950                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
 
7955         stopLocate: function () {
 
7956                 if (navigator.geolocation) {
 
7957                         navigator.geolocation.clearWatch(this._locationWatchId);
 
7962         _handleGeolocationError: function (error) {
 
7964                     message = error.message ||
 
7965                             (c === 1 ? "permission denied" :
 
7966                             (c === 2 ? "position unavailable" : "timeout"));
 
7968                 if (this._locationOptions.setView && !this._loaded) {
 
7972                 this.fire('locationerror', {
 
7974                         message: "Geolocation error: " + message + "."
 
7978         _handleGeolocationResponse: function (pos) {
 
7979                 var latAccuracy = 180 * pos.coords.accuracy / 4e7,
 
7980                     lngAccuracy = latAccuracy * 2,
 
7982                     lat = pos.coords.latitude,
 
7983                     lng = pos.coords.longitude,
 
7984                     latlng = new L.LatLng(lat, lng),
 
7986                     sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
 
7987                     ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
 
7988                     bounds = new L.LatLngBounds(sw, ne),
 
7990                     options = this._locationOptions;
 
7992                 if (options.setView) {
 
7993                         var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
 
7994                         this.setView(latlng, zoom);
 
7997                 this.fire('locationfound', {
 
8000                         accuracy: pos.coords.accuracy