]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/leaflet/leaflet.js
Update to Leaflet 0.5
[rails.git] / vendor / assets / leaflet / leaflet.js
index 7c5b717e77389fd839d659f677793a1c19ab7828..2da1987dfa9d32b3b6990d59d90bb5b849746144 100644 (file)
@@ -1,9 +1,11 @@
 /*
- Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin
- Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.
- http://leaflet.cloudmade.com
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin, CloudMade
 */
-(function (window, undefined) {
+(function (window, document, undefined) {/*
+ * The L namespace contains all Leaflet classes and functions.
+ * This code allows you to handle any possible namespace conflicts.
+ */
 
 var L, originalL;
 
@@ -21,19 +23,21 @@ if (typeof exports !== undefined + '') {
        window.L = L;
 }
 
-L.version = '0.4.4';
+L.version = '0.5';
 
 
 /*
- * L.Util is a namespace for various utility functions.
+ * L.Util contains various utility functions used throughout Leaflet code.
  */
 
 L.Util = {
-       extend: function (/*Object*/ dest) /*-> Object*/ {      // merge src properties into dest
-               var sources = Array.prototype.slice.call(arguments, 1);
-               for (var j = 0, len = sources.length, src; j < len; j++) {
+       extend: function (dest) { // (Object[, Object, ...]) ->
+               var sources = Array.prototype.slice.call(arguments, 1),
+                   i, j, len, src;
+
+               for (j = 0, len = sources.length; j < len; j++) {
                        src = sources[j] || {};
-                       for (var i in src) {
+                       for (i in src) {
                                if (src.hasOwnProperty(i)) {
                                        dest[i] = src[i];
                                }
@@ -97,18 +101,18 @@ L.Util = {
        },
 
        setOptions: function (obj, options) {
-               obj.options = L.Util.extend({}, obj.options, options);
+               obj.options = L.extend({}, obj.options, options);
                return obj.options;
        },
 
-       getParamString: function (obj) {
+       getParamString: function (obj, existingUrl) {
                var params = [];
                for (var i in obj) {
                        if (obj.hasOwnProperty(i)) {
                                params.push(i + '=' + obj[i]);
                        }
                }
-               return '?' + params.join('&');
+               return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
        },
 
        template: function (str, data) {
@@ -121,6 +125,10 @@ L.Util = {
                });
        },
 
+       isArray: function (obj) {
+               return (Object.prototype.toString.call(obj) === '[object Array]');
+       },
+
        emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
 };
 
@@ -130,7 +138,7 @@ L.Util = {
 
        function getPrefixed(name) {
                var i, fn,
-                       prefixes = ['webkit', 'moz', 'o', 'ms'];
+                   prefixes = ['webkit', 'moz', 'o', 'ms'];
 
                for (i = 0; i < prefixes.length && !fn; i++) {
                        fn = window[prefixes[i] + name];
@@ -143,25 +151,23 @@ L.Util = {
 
        function timeoutDefer(fn) {
                var time = +new Date(),
-                       timeToCall = Math.max(0, 16 - (time - lastTime));
+                   timeToCall = Math.max(0, 16 - (time - lastTime));
 
                lastTime = time + timeToCall;
                return window.setTimeout(fn, timeToCall);
        }
 
        var requestFn = window.requestAnimationFrame ||
-                       getPrefixed('RequestAnimationFrame') || timeoutDefer;
+               getPrefixed('RequestAnimationFrame') || timeoutDefer;
 
        var cancelFn = window.cancelAnimationFrame ||
-                       getPrefixed('CancelAnimationFrame') ||
-                       getPrefixed('CancelRequestAnimationFrame') ||
-                       function (id) {
-                               window.clearTimeout(id);
-                       };
+               getPrefixed('CancelAnimationFrame') ||
+               getPrefixed('CancelRequestAnimationFrame') ||
+               function (id) { window.clearTimeout(id); };
 
 
        L.Util.requestAnimFrame = function (fn, context, immediate, element) {
-               fn = L.Util.bind(fn, context);
+               fn = L.bind(fn, context);
 
                if (immediate && requestFn === timeoutDefer) {
                        fn();
@@ -178,20 +184,34 @@ L.Util = {
 
 }());
 
+// shortcuts for most used utility functions
+L.extend = L.Util.extend;
+L.bind = L.Util.bind;
+L.stamp = L.Util.stamp;
+L.setOptions = L.Util.setOptions;
+
 
 /*
- * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
+ * L.Class powers the OOP facilities of the library.
+ * Thanks to John Resig and Dean Edwards for inspiration!
  */
 
 L.Class = function () {};
 
-L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
+L.Class.extend = function (props) {
 
        // extended class with the new prototype
        var NewClass = function () {
+
+               // call the constructor
                if (this.initialize) {
                        this.initialize.apply(this, arguments);
                }
+
+               // call all constructor hooks
+               if (this._initHooks) {
+                       this.callInitHooks();
+               }
        };
 
        // instantiate class without calling constructor
@@ -212,7 +232,7 @@ L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
 
        // mix static properties into the class
        if (props.statics) {
-               L.Util.extend(NewClass, props.statics);
+               L.extend(NewClass, props.statics);
                delete props.statics;
        }
 
@@ -224,11 +244,30 @@ L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
 
        // merge options
        if (props.options && proto.options) {
-               props.options = L.Util.extend({}, proto.options, props.options);
+               props.options = L.extend({}, proto.options, props.options);
        }
 
        // mix given properties into the prototype
-       L.Util.extend(proto, props);
+       L.extend(proto, props);
+
+       proto._initHooks = [];
+
+       var parent = this;
+       // add method for calling all hooks
+       proto.callInitHooks = function () {
+
+               if (this._initHooksCalled) { return; }
+
+               if (parent.prototype.callInitHooks) {
+                       parent.prototype.callInitHooks.call(this);
+               }
+
+               this._initHooksCalled = true;
+
+               for (var i = 0, len = proto._initHooks.length; i < len; i++) {
+                       proto._initHooks[i].call(this);
+               }
+       };
 
        return NewClass;
 };
@@ -236,15 +275,29 @@ L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
 
 // method for adding properties to prototype
 L.Class.include = function (props) {
-       L.Util.extend(this.prototype, props);
+       L.extend(this.prototype, props);
 };
 
+// merge new default options to the Class
 L.Class.mergeOptions = function (options) {
-       L.Util.extend(this.prototype.options, options);
+       L.extend(this.prototype.options, options);
+};
+
+// add a constructor hook
+L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
+       var args = Array.prototype.slice.call(arguments, 1);
+
+       var init = typeof fn === 'function' ? fn : function () {
+               this[fn].apply(this, args);
+       };
+
+       this.prototype._initHooks = this.prototype._initHooks || [];
+       this.prototype._initHooks.push(init);
 };
 
+
 /*
- * L.Mixin.Events adds custom events functionality to Leaflet classes
+ * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
  */
 
 var key = '_leaflet_events';
@@ -252,11 +305,11 @@ var key = '_leaflet_events';
 L.Mixin = {};
 
 L.Mixin.Events = {
-       
+
        addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
                var events = this[key] = this[key] || {},
                        type, i, len;
-               
+
                // Types can be a map of types/handlers
                if (typeof types === 'object') {
                        for (type in types) {
@@ -264,12 +317,12 @@ L.Mixin.Events = {
                                        this.addEventListener(type, types[type], fn);
                                }
                        }
-                       
+
                        return this;
                }
-               
+
                types = L.Util.splitWords(types);
-               
+
                for (i = 0, len = types.length; i < len; i++) {
                        events[types[i]] = events[types[i]] || [];
                        events[types[i]].push({
@@ -277,7 +330,7 @@ L.Mixin.Events = {
                                context: context || this
                        });
                }
-               
+
                return this;
        },
 
@@ -288,24 +341,24 @@ L.Mixin.Events = {
        removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
                var events = this[key],
                        type, i, len, listeners, j;
-               
+
                if (typeof types === 'object') {
                        for (type in types) {
                                if (types.hasOwnProperty(type)) {
                                        this.removeEventListener(type, types[type], fn);
                                }
                        }
-                       
+
                        return this;
                }
-               
+
                types = L.Util.splitWords(types);
 
                for (i = 0, len = types.length; i < len; i++) {
 
                        if (this.hasEventListeners(types[i])) {
                                listeners = events[types[i]];
-                               
+
                                for (j = listeners.length - 1; j >= 0; j--) {
                                        if (
                                                (!fn || listeners[j].action === fn) &&
@@ -316,7 +369,7 @@ L.Mixin.Events = {
                                }
                        }
                }
-               
+
                return this;
        },
 
@@ -325,7 +378,7 @@ L.Mixin.Events = {
                        return this;
                }
 
-               var event = L.Util.extend({
+               var event = L.extend({
                        type: type,
                        target: this
                }, data);
@@ -345,35 +398,50 @@ L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
 L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
 
 
+/*
+ * L.Browser handles different browser and feature detections for internal Leaflet use.
+ */
+
 (function () {
-       var ua = navigator.userAgent.toLowerCase(),
-               ie = !!window.ActiveXObject,
-               ie6 = ie && !window.XMLHttpRequest,
-               webkit = ua.indexOf("webkit") !== -1,
-               gecko = ua.indexOf("gecko") !== -1,
-               //Terrible browser detection to work around a safari / iOS / android browser bug. See TileLayer._addTile and debug/hacks/jitter.html
-               chrome = ua.indexOf("chrome") !== -1,
-               opera = window.opera,
-               android = ua.indexOf("android") !== -1,
-               android23 = ua.search("android [23]") !== -1,
-               mobile = typeof orientation !== undefined + '' ? true : false,
-               doc = document.documentElement,
-               ie3d = ie && ('transition' in doc.style),
-               webkit3d = webkit && ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
-               gecko3d = gecko && ('MozPerspective' in doc.style),
-               opera3d = opera && ('OTransition' in doc.style);
+
+       var ie = !!window.ActiveXObject,
+           ie6 = ie && !window.XMLHttpRequest,
+           ie7 = ie && !document.querySelector,
+
+           // terrible browser detection to work around Safari / iOS / Android browser bugs
+           ua = navigator.userAgent.toLowerCase(),
+           webkit = ua.indexOf('webkit') !== -1,
+           chrome = ua.indexOf('chrome') !== -1,
+           android = ua.indexOf('android') !== -1,
+           android23 = ua.search('android [23]') !== -1,
+
+           mobile = typeof orientation !== undefined + '',
+           msTouch = window.navigator && window.navigator.msPointerEnabled &&
+                     window.navigator.msMaxTouchPoints,
+           retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
+                    ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
+                     window.matchMedia('(min-resolution:144dpi)').matches),
+
+           doc = document.documentElement,
+           ie3d = ie && ('transition' in doc.style),
+           webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
+           gecko3d = 'MozPerspective' in doc.style,
+           opera3d = 'OTransition' in doc.style,
+           any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d);
+
 
        var touch = !window.L_NO_TOUCH && (function () {
+
                var startName = 'ontouchstart';
 
-               // WebKit, etc
-               if (startName in doc) {
+               // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
+               if (msTouch || (startName in doc)) {
                        return true;
                }
 
                // Firefox/Gecko
                var div = document.createElement('div'),
-                       supported = false;
+                   supported = false;
 
                if (!div.setAttribute) {
                        return false;
@@ -390,15 +458,13 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
                return supported;
        }());
 
-       var retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) || ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches));
 
        L.Browser = {
-               ua: ua,
                ie: ie,
                ie6: ie6,
+               ie7: ie7,
                webkit: webkit,
-               gecko: gecko,
-               opera: opera,
+
                android: android,
                android23: android23,
 
@@ -408,17 +474,19 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
                webkit3d: webkit3d,
                gecko3d: gecko3d,
                opera3d: opera3d,
-               any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d),
+               any3d: any3d,
 
                mobile: mobile,
                mobileWebkit: mobile && webkit,
                mobileWebkit3d: mobile && webkit3d,
-               mobileOpera: mobile && opera,
+               mobileOpera: mobile && window.opera,
 
                touch: touch,
+               msTouch: msTouch,
 
                retina: retina
        };
+
 }());
 
 
@@ -503,15 +571,20 @@ L.Point.prototype = {
                point = L.point(point);
 
                var x = point.x - this.x,
-                       y = point.y - this.y;
+                   y = point.y - this.y;
 
                return Math.sqrt(x * x + y * y);
        },
 
+       equals: function (point) {
+               return point.x === this.x &&
+                      point.y === this.y;
+       },
+
        toString: function () {
                return 'Point(' +
-                               L.Util.formatNum(this.x) + ', ' +
-                               L.Util.formatNum(this.y) + ')';
+                       L.Util.formatNum(this.x) + ', ' +
+                       L.Util.formatNum(this.y) + ')';
        }
 };
 
@@ -519,7 +592,7 @@ L.point = function (x, y, round) {
        if (x instanceof L.Point) {
                return x;
        }
-       if (x instanceof Array) {
+       if (L.Util.isArray(x)) {
                return new L.Point(x[0], x[1]);
        }
        if (isNaN(x)) {
@@ -533,18 +606,17 @@ L.point = function (x, y, round) {
  * L.Bounds represents a rectangular area on the screen in pixel coordinates.
  */
 
-L.Bounds = L.Class.extend({
+L.Bounds = function (a, b) { //(Point, Point) or Point[]
+       if (!a) { return; }
 
-       initialize: function (a, b) {   //(Point, Point) or Point[]
-               if (!a) { return; }
+       var points = b ? [a, b] : a;
 
-               var points = b ? [a, b] : a;
-
-               for (var i = 0, len = points.length; i < len; i++) {
-                       this.extend(points[i]);
-               }
-       },
+       for (var i = 0, len = points.length; i < len; i++) {
+               this.extend(points[i]);
+       }
+};
 
+L.Bounds.prototype = {
        // extend the bounds to contain the given point
        extend: function (point) { // (Point)
                point = L.point(point);
@@ -563,8 +635,8 @@ L.Bounds = L.Class.extend({
 
        getCenter: function (round) { // (Boolean) -> Point
                return new L.Point(
-                               (this.min.x + this.max.x) / 2,
-                               (this.min.y + this.max.y) / 2, round);
+                       (this.min.x + this.max.x) / 2,
+                       (this.min.y + this.max.y) / 2, round);
        },
 
        getBottomLeft: function () { // -> Point
@@ -575,6 +647,10 @@ L.Bounds = L.Class.extend({
                return new L.Point(this.max.x, this.min.y);
        },
 
+       getSize: function () {
+               return this.max.subtract(this.min);
+       },
+
        contains: function (obj) { // (Bounds) or (Point) -> Boolean
                var min, max;
 
@@ -592,21 +668,20 @@ L.Bounds = L.Class.extend({
                }
 
                return (min.x >= this.min.x) &&
-                               (max.x <= this.max.x) &&
-                               (min.y >= this.min.y) &&
-                               (max.y <= this.max.y);
+                      (max.x <= this.max.x) &&
+                      (min.y >= this.min.y) &&
+                      (max.y <= this.max.y);
        },
 
        intersects: function (bounds) { // (Bounds) -> Boolean
                bounds = L.bounds(bounds);
 
                var min = this.min,
-                       max = this.max,
-                       min2 = bounds.min,
-                       max2 = bounds.max;
-
-               var xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
-                       yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
+                   max = this.max,
+                   min2 = bounds.min,
+                   max2 = bounds.max,
+                   xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
+                   yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
 
                return xIntersects && yIntersects;
        },
@@ -614,7 +689,7 @@ L.Bounds = L.Class.extend({
        isValid: function () {
                return !!(this.min && this.max);
        }
-});
+};
 
 L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
        if (!a || a instanceof L.Bounds) {
@@ -628,33 +703,33 @@ L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
  * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
  */
 
-L.Transformation = L.Class.extend({
-       initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {
-               this._a = a;
-               this._b = b;
-               this._c = c;
-               this._d = d;
-       },
+L.Transformation = function (a, b, c, d) {
+       this._a = a;
+       this._b = b;
+       this._c = c;
+       this._d = d;
+};
 
-       transform: function (point, scale) {
+L.Transformation.prototype = {
+       transform: function (point, scale) { // (Point, Number) -> Point
                return this._transform(point.clone(), scale);
        },
 
        // destructive transform (faster)
-       _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+       _transform: function (point, scale) {
                scale = scale || 1;
                point.x = scale * (this._a * point.x + this._b);
                point.y = scale * (this._c * point.y + this._d);
                return point;
        },
 
-       untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+       untransform: function (point, scale) {
                scale = scale || 1;
                return new L.Point(
-                       (point.x / scale - this._b) / this._a,
-                       (point.y / scale - this._d) / this._c);
+                       (point.x / scale - this._b) / this._a,
+                       (point.y / scale - this._d) / this._c);
        }
-});
+};
 
 
 /*
@@ -674,7 +749,7 @@ L.DomUtil = {
                        value = el.currentStyle[style];
                }
 
-               if (!value || value === 'auto') {
+               if ((!value || value === 'auto') && document.defaultView) {
                        var css = document.defaultView.getComputedStyle(el, null);
                        value = css ? css[style] : null;
                }
@@ -685,14 +760,20 @@ L.DomUtil = {
        getViewportOffset: function (element) {
 
                var top = 0,
-                       left = 0,
-                       el = element,
-                       docBody = document.body,
-                       pos;
+                   left = 0,
+                   el = element,
+                   docBody = document.body,
+                   pos,
+                   ie7 = L.Browser.ie7;
 
                do {
-                       top += el.offsetTop || 0;
+                       top  += el.offsetTop  || 0;
                        left += el.offsetLeft || 0;
+
+                       //add borders
+                       top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0;
+                       left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 0;
+
                        pos = L.DomUtil.getStyle(el, 'position');
 
                        if (el.offsetParent === docBody && pos === 'absolute') { break; }
@@ -714,12 +795,33 @@ L.DomUtil = {
                        top  -= el.scrollTop  || 0;
                        left -= el.scrollLeft || 0;
 
+                       // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
+                       // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
+                       if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
+                               left += el.scrollWidth - el.clientWidth;
+
+                               // ie7 shows the scrollbar by default and provides clientWidth counting it, so we
+                               // need to add it back in if it is visible; scrollbar is on the left as we are RTL
+                               if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
+                                          L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
+                                       left += 17;
+                               }
+                       }
+
                        el = el.parentNode;
                } while (el);
 
                return new L.Point(left, top);
        },
 
+       documentIsLtr: function () {
+               if (!L.DomUtil._docIsLtrCached) {
+                       L.DomUtil._docIsLtrCached = true;
+                       L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === "ltr";
+               }
+               return L.DomUtil._docIsLtr;
+       },
+
        create: function (tagName, className, container) {
 
                var el = document.createElement(tagName);
@@ -737,7 +839,7 @@ L.DomUtil = {
                        document.selection.empty();
                }
                if (!this._onselectstart) {
-                       this._onselectstart = document.onselectstart;
+                       this._onselectstart = document.onselectstart || null;
                        document.onselectstart = L.Util.falseFn;
                }
        },
@@ -751,7 +853,7 @@ L.DomUtil = {
 
        hasClass: function (el, name) {
                return (el.className.length > 0) &&
-                               new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
+                       new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
        },
 
        addClass: function (el, name) {
@@ -768,8 +870,8 @@ L.DomUtil = {
                }
 
                el.className = el.className
-                               .replace(/(\S+)\s*/g, replaceFn)
-                               .replace(/(^\s+|\s+$)/, '');
+                       .replace(/(\S+)\s*/g, replaceFn)
+                       .replace(/(^\s+|\s+$)/, '');
        },
 
        setOpacity: function (el, value) {
@@ -777,10 +879,10 @@ L.DomUtil = {
                if ('opacity' in el.style) {
                        el.style.opacity = value;
 
-               } else if (L.Browser.ie) {
+               } else if ('filter' in el.style) {
 
                        var filter = false,
-                               filterName = 'DXImageTransform.Microsoft.Alpha';
+                           filterName = 'DXImageTransform.Microsoft.Alpha';
 
                        // filters collection throws an error if we try to retrieve a filter that doesn't exist
                        try { filter = el.filters.item(filterName); } catch (e) {}
@@ -814,8 +916,8 @@ L.DomUtil = {
                // (same speed either way), Opera 12 doesn't support translate3d
 
                var is3d = L.Browser.webkit3d,
-                       open = 'translate' + (is3d ? '3d' : '') + '(',
-                       close = (is3d ? ',0' : '') + ')';
+                   open = 'translate' + (is3d ? '3d' : '') + '(',
+                   close = (is3d ? ',0' : '') + ')';
 
                return open + point.x + 'px,' + point.y + 'px' + close;
        },
@@ -856,38 +958,36 @@ L.DomUtil = {
 // prefix style property names
 
 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
-               ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+        ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
 
 L.DomUtil.TRANSITION = L.DomUtil.testProp(
-               ['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']);
+        ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
 
 L.DomUtil.TRANSITION_END =
-               L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
-               L.DomUtil.TRANSITION + 'End' : 'transitionend';
+        L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
+        L.DomUtil.TRANSITION + 'End' : 'transitionend';
 
 
 /*
      CM.LatLng represents a geographical point with latitude and longtitude coordinates.
-*/
* L.LatLng represents a geographical point with latitude and longitude coordinates.
+ */
 
-L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
+L.LatLng = function (rawLat, rawLng) { // (Number, Number)
        var lat = parseFloat(rawLat),
-               lng = parseFloat(rawLng);
+           lng = parseFloat(rawLng);
 
        if (isNaN(lat) || isNaN(lng)) {
                throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
        }
 
-       if (noWrap !== true) {
-               lat = Math.max(Math.min(lat, 90), -90);                                 // clamp latitude into -90..90
-               lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180);   // wrap longtitude into -180..180
-       }
-
        this.lat = lat;
        this.lng = lng;
 };
 
-L.Util.extend(L.LatLng, {
+L.extend(L.LatLng, {
        DEG_TO_RAD: Math.PI / 180,
        RAD_TO_DEG: 180 / Math.PI,
        MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
@@ -899,46 +999,61 @@ L.LatLng.prototype = {
 
                obj = L.latLng(obj);
 
-               var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
+               var margin = Math.max(
+                       Math.abs(this.lat - obj.lat),
+                       Math.abs(this.lng - obj.lng));
+
                return margin <= L.LatLng.MAX_MARGIN;
        },
 
-       toString: function (precision) { // -> String
+       toString: function (precision) { // (Number) -> String
                return 'LatLng(' +
-                               L.Util.formatNum(this.lat, precision) + ', ' +
-                               L.Util.formatNum(this.lng, precision) + ')';
+                       L.Util.formatNum(this.lat, precision) + ', ' +
+                       L.Util.formatNum(this.lng, precision) + ')';
        },
 
        // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
+       // TODO move to projection code, LatLng shouldn't know about Earth
        distanceTo: function (other) { // (LatLng) -> Number
                other = L.latLng(other);
 
                var R = 6378137, // earth radius in meters
-                       d2r = L.LatLng.DEG_TO_RAD,
-                       dLat = (other.lat - this.lat) * d2r,
-                       dLon = (other.lng - this.lng) * d2r,
-                       lat1 = this.lat * d2r,
-                       lat2 = other.lat * d2r,
-                       sin1 = Math.sin(dLat / 2),
-                       sin2 = Math.sin(dLon / 2);
+                   d2r = L.LatLng.DEG_TO_RAD,
+                   dLat = (other.lat - this.lat) * d2r,
+                   dLon = (other.lng - this.lng) * d2r,
+                   lat1 = this.lat * d2r,
+                   lat2 = other.lat * d2r,
+                   sin1 = Math.sin(dLat / 2),
+                   sin2 = Math.sin(dLon / 2);
 
                var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
 
                return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+       },
+
+       wrap: function (a, b) { // (Number, Number) -> LatLng
+               var lng = this.lng;
+
+               a = a || -180;
+               b = b ||  180;
+
+               lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
+
+               return new L.LatLng(this.lat, lng);
        }
 };
 
-L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
+L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
        if (a instanceof L.LatLng) {
                return a;
        }
-       if (a instanceof Array) {
+       if (L.Util.isArray(a)) {
                return new L.LatLng(a[0], a[1]);
        }
        if (isNaN(a)) {
                return a;
        }
-       return new L.LatLng(a, b, c);
+       return new L.LatLng(a, b);
 };
 
 
@@ -947,20 +1062,20 @@ L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Nu
  * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
  */
 
-L.LatLngBounds = L.Class.extend({
-       initialize: function (southWest, northEast) {   // (LatLng, LatLng) or (LatLng[])
-               if (!southWest) { return; }
+L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
+       if (!southWest) { return; }
 
-               var latlngs = northEast ? [southWest, northEast] : southWest;
+       var latlngs = northEast ? [southWest, northEast] : southWest;
 
-               for (var i = 0, len = latlngs.length; i < len; i++) {
-                       this.extend(latlngs[i]);
-               }
-       },
+       for (var i = 0, len = latlngs.length; i < len; i++) {
+               this.extend(latlngs[i]);
+       }
+};
 
+L.LatLngBounds.prototype = {
        // extend the bounds to contain the given point or bounds
        extend: function (obj) { // (LatLng) or (LatLngBounds)
-               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+               if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
                        obj = L.latLng(obj);
                } else {
                        obj = L.latLngBounds(obj);
@@ -968,8 +1083,8 @@ L.LatLngBounds = L.Class.extend({
 
                if (obj instanceof L.LatLng) {
                        if (!this._southWest && !this._northEast) {
-                               this._southWest = new L.LatLng(obj.lat, obj.lng, true);
-                               this._northEast = new L.LatLng(obj.lat, obj.lng, true);
+                               this._southWest = new L.LatLng(obj.lat, obj.lng);
+                               this._northEast = new L.LatLng(obj.lat, obj.lng);
                        } else {
                                this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
                                this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
@@ -979,7 +1094,7 @@ L.LatLngBounds = L.Class.extend({
                        }
                } else if (obj instanceof L.LatLngBounds) {
                        this.extend(obj._southWest);
-            this.extend(obj._northEast);
+                       this.extend(obj._northEast);
                }
                return this;
        },
@@ -987,19 +1102,19 @@ L.LatLngBounds = L.Class.extend({
        // extend the bounds by a percentage
        pad: function (bufferRatio) { // (Number) -> LatLngBounds
                var sw = this._southWest,
-                       ne = this._northEast,
-                       heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
-                       widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
+                   ne = this._northEast,
+                   heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
+                   widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
 
                return new L.LatLngBounds(
-                       new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
-                       new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
+                       new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
+                       new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
        },
 
        getCenter: function () { // -> LatLng
                return new L.LatLng(
-                               (this._southWest.lat + this._northEast.lat) / 2,
-                               (this._southWest.lng + this._northEast.lng) / 2);
+                       (this._southWest.lat + this._northEast.lat) / 2,
+                       (this._southWest.lng + this._northEast.lng) / 2);
        },
 
        getSouthWest: function () {
@@ -1011,11 +1126,11 @@ L.LatLngBounds = L.Class.extend({
        },
 
        getNorthWest: function () {
-               return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
+               return new L.LatLng(this._northEast.lat, this._southWest.lng);
        },
 
        getSouthEast: function () {
-               return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
+               return new L.LatLng(this._southWest.lat, this._northEast.lng);
        },
 
        contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
@@ -1026,8 +1141,8 @@ L.LatLngBounds = L.Class.extend({
                }
 
                var sw = this._southWest,
-                       ne = this._northEast,
-                       sw2, ne2;
+                   ne = this._northEast,
+                   sw2, ne2;
 
                if (obj instanceof L.LatLngBounds) {
                        sw2 = obj.getSouthWest();
@@ -1037,26 +1152,27 @@ L.LatLngBounds = L.Class.extend({
                }
 
                return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
-                               (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
+                      (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
        },
 
        intersects: function (bounds) { // (LatLngBounds)
                bounds = L.latLngBounds(bounds);
 
                var sw = this._southWest,
-                       ne = this._northEast,
-                       sw2 = bounds.getSouthWest(),
-                       ne2 = bounds.getNorthEast();
+                   ne = this._northEast,
+                   sw2 = bounds.getSouthWest(),
+                   ne2 = bounds.getNorthEast(),
 
-               var latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
-                       lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
+                   latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
+                   lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
 
                return latIntersects && lngIntersects;
        },
 
        toBBoxString: function () {
                var sw = this._southWest,
-                       ne = this._northEast;
+                   ne = this._northEast;
+
                return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
        },
 
@@ -1072,7 +1188,7 @@ L.LatLngBounds = L.Class.extend({
        isValid: function () {
                return !!(this._southWest && this._northEast);
        }
-});
+};
 
 //TODO International date line?
 
@@ -1091,16 +1207,20 @@ L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
 L.Projection = {};
 
 
+/*
+ * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
+ */
 
 L.Projection.SphericalMercator = {
        MAX_LATITUDE: 85.0511287798,
 
        project: function (latlng) { // (LatLng) -> Point
                var d = L.LatLng.DEG_TO_RAD,
-                       max = this.MAX_LATITUDE,
-                       lat = Math.max(Math.min(max, latlng.lat), -max),
-                       x = latlng.lng * d,
-                       y = lat * d;
+                   max = this.MAX_LATITUDE,
+                   lat = Math.max(Math.min(max, latlng.lat), -max),
+                   x = latlng.lng * d,
+                   y = lat * d;
+
                y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
 
                return new L.Point(x, y);
@@ -1108,15 +1228,17 @@ L.Projection.SphericalMercator = {
 
        unproject: function (point) { // (Point, Boolean) -> LatLng
                var d = L.LatLng.RAD_TO_DEG,
-                       lng = point.x * d,
-                       lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
+                   lng = point.x * d,
+                   lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
 
-               // TODO refactor LatLng wrapping
-               return new L.LatLng(lat, lng, true);
+               return new L.LatLng(lat, lng);
        }
 };
 
 
+/*
+ * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
+ */
 
 L.Projection.LonLat = {
        project: function (latlng) {
@@ -1124,11 +1246,14 @@ L.Projection.LonLat = {
        },
 
        unproject: function (point) {
-               return new L.LatLng(point.y, point.x, true);
+               return new L.LatLng(point.y, point.x);
        }
 };
 
 
+/*
+ * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
+ */
 
 L.CRS = {
        latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
@@ -1155,8 +1280,26 @@ L.CRS = {
 };
 
 
+/*
+ * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
+ */
+
+L.CRS.Simple = L.extend({}, L.CRS, {
+       projection: L.Projection.LonLat,
+       transformation: new L.Transformation(1, 0, -1, 0),
+
+       scale: function (zoom) {
+               return Math.pow(2, zoom);
+       }
+});
+
+
+/*
+ * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
+ * and is used by Leaflet by default.
+ */
 
-L.CRS.EPSG3857 = L.Util.extend({}, L.CRS, {
+L.CRS.EPSG3857 = L.extend({}, L.CRS, {
        code: 'EPSG:3857',
 
        projection: L.Projection.SphericalMercator,
@@ -1164,18 +1307,21 @@ L.CRS.EPSG3857 = L.Util.extend({}, L.CRS, {
 
        project: function (latlng) { // (LatLng) -> Point
                var projectedPoint = this.projection.project(latlng),
-                       earthRadius = 6378137;
+                   earthRadius = 6378137;
                return projectedPoint.multiplyBy(earthRadius);
        }
 });
 
-L.CRS.EPSG900913 = L.Util.extend({}, L.CRS.EPSG3857, {
+L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
        code: 'EPSG:900913'
 });
 
 
+/*
+ * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
+ */
 
-L.CRS.EPSG4326 = L.Util.extend({}, L.CRS, {
+L.CRS.EPSG4326 = L.extend({}, L.CRS, {
        code: 'EPSG:4326',
 
        projection: L.Projection.LonLat,
@@ -1206,11 +1352,11 @@ L.Map = L.Class.extend({
        },
 
        initialize: function (id, options) { // (HTMLElement or String, Object)
-               options = L.Util.setOptions(this, options);
+               options = L.setOptions(this, options);
 
                this._initContainer(id);
                this._initLayout();
-               this._initHooks();
+               this.callInitHooks();
                this._initEvents();
 
                if (options.maxBounds) {
@@ -1326,18 +1472,16 @@ L.Map = L.Class.extend({
        addLayer: function (layer) {
                // TODO method is too big, refactor
 
-               var id = L.Util.stamp(layer);
+               var id = L.stamp(layer);
 
                if (this._layers[id]) { return this; }
 
                this._layers[id] = layer;
 
                // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
-               if (layer.options && !isNaN(layer.options.maxZoom)) {
-                       this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
-               }
-               if (layer.options && !isNaN(layer.options.minZoom)) {
-                       this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
+               if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
+                       this._zoomBoundLayers[id] = layer;
+                       this._updateZoomLevels();
                }
 
                // TODO looks ugly, refactor!!!
@@ -1356,13 +1500,17 @@ L.Map = L.Class.extend({
        },
 
        removeLayer: function (layer) {
-               var id = L.Util.stamp(layer);
+               var id = L.stamp(layer);
 
                if (!this._layers[id]) { return; }
 
                layer.onRemove(this);
 
                delete this._layers[id];
+               if (this._zoomBoundLayers[id]) {
+                       delete this._zoomBoundLayers[id];
+                       this._updateZoomLevels();
+               }
 
                // TODO looks ugly, refactor
                if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
@@ -1375,7 +1523,7 @@ L.Map = L.Class.extend({
        },
 
        hasLayer: function (layer) {
-               var id = L.Util.stamp(layer);
+               var id = L.stamp(layer);
                return this._layers.hasOwnProperty(id);
        },
 
@@ -1400,7 +1548,7 @@ L.Map = L.Class.extend({
                        this.fire('move');
 
                        clearTimeout(this._sizeTimer);
-                       this._sizeTimer = setTimeout(L.Util.bind(this.fire, this, 'moveend'), 200);
+                       this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
                }
                return this;
        },
@@ -1473,7 +1621,10 @@ L.Map = L.Class.extend({
                        zoom++;
                        nePoint = this.project(ne, zoom);
                        swPoint = this.project(sw, zoom);
-                       boundsSize = new L.Point(Math.abs(nePoint.x - swPoint.x), Math.abs(swPoint.y - nePoint.y));
+
+                       boundsSize = new L.Point(
+                               Math.abs(nePoint.x - swPoint.x),
+                               Math.abs(swPoint.y - nePoint.y));
 
                        if (!inside) {
                                zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
@@ -1597,7 +1748,6 @@ L.Map = L.Class.extend({
        _initLayout: function () {
                var container = this._container;
 
-               container.innerHTML = '';
                L.DomUtil.addClass(container, 'leaflet-container');
 
                if (L.Browser.touch) {
@@ -1627,8 +1777,7 @@ L.Map = L.Class.extend({
                this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
 
                this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
-               this._objectsPane = panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
-
+               panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
                panes.shadowPane = this._createPane('leaflet-shadow-pane');
                panes.overlayPane = this._createPane('leaflet-overlay-pane');
                panes.markerPane = this._createPane('leaflet-marker-pane');
@@ -1644,22 +1793,14 @@ L.Map = L.Class.extend({
        },
 
        _createPane: function (className, container) {
-               return L.DomUtil.create('div', className, container || this._objectsPane);
-       },
-
-       _initializers: [],
-
-       _initHooks: function () {
-               var i, len;
-               for (i = 0, len = this._initializers.length; i < len; i++) {
-                       this._initializers[i].call(this);
-               }
+               return L.DomUtil.create('div', className, container || this._panes.objectsPane);
        },
 
        _initLayers: function (layers) {
-               layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
+               layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
 
                this._layers = {};
+               this._zoomBoundLayers = {};
                this._tileLayersNum = 0;
 
                var i, len;
@@ -1718,6 +1859,30 @@ L.Map = L.Class.extend({
                L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
        },
 
+       _updateZoomLevels: function () {
+               var i,
+                       minZoom = Infinity,
+                       maxZoom = -Infinity;
+
+               for (i in this._zoomBoundLayers) {
+                       if (this._zoomBoundLayers.hasOwnProperty(i)) {
+                               var layer = this._zoomBoundLayers[i];
+                               if (!isNaN(layer.options.minZoom)) {
+                                       minZoom = Math.min(minZoom, layer.options.minZoom);
+                               }
+                               if (!isNaN(layer.options.maxZoom)) {
+                                       maxZoom = Math.max(maxZoom, layer.options.maxZoom);
+                               }
+                       }
+               }
+
+               if (i === undefined) { // we have no tilelayers
+                       this._layersMaxZoom = this._layersMinZoom = undefined;
+               } else {
+                       this._layersMaxZoom = maxZoom;
+                       this._layersMinZoom = minZoom;
+               }
+       },
 
        // map events
 
@@ -1726,8 +1891,9 @@ L.Map = L.Class.extend({
 
                L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
 
-               var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'],
-                       i, len;
+               var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
+                             'mouseleave', 'mousemove', 'contextmenu'],
+                   i, len;
 
                for (i = 0, len = events.length; i < len; i++) {
                        L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
@@ -1740,7 +1906,8 @@ L.Map = L.Class.extend({
 
        _onResize: function () {
                L.Util.cancelAnimFrame(this._resizeRequest);
-               this._resizeRequest = L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);
+               this._resizeRequest = L.Util.requestAnimFrame(
+                       this.invalidateSize, this, false, this._container);
        },
 
        _onMouseClick: function (e) {
@@ -1764,8 +1931,8 @@ L.Map = L.Class.extend({
                }
 
                var containerPoint = this.mouseEventToContainerPoint(e),
-                       layerPoint = this.containerPointToLayerPoint(containerPoint),
-                       latlng = this.layerPointToLatLng(layerPoint);
+                   layerPoint = this.containerPointToLayerPoint(containerPoint),
+                   latlng = this.layerPointToLatLng(layerPoint);
 
                this.fire(type, {
                        latlng: latlng,
@@ -1781,7 +1948,7 @@ L.Map = L.Class.extend({
                this._tileLayersToLoad--;
                if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
                        clearTimeout(this._clearTileBgTimer);
-                       this._clearTileBgTimer = setTimeout(L.Util.bind(this._clearTileBg, this), 500);
+                       this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500);
                }
        },
 
@@ -1830,27 +1997,21 @@ L.Map = L.Class.extend({
 
        _limitZoom: function (zoom) {
                var min = this.getMinZoom(),
-                       max = this.getMaxZoom();
+                   max = this.getMaxZoom();
 
                return Math.max(min, Math.min(max, zoom));
        }
 });
 
-L.Map.addInitHook = function (fn) {
-       var args = Array.prototype.slice.call(arguments, 1);
-
-       var init = typeof fn === 'function' ? fn : function () {
-               this[fn].apply(this, args);
-       };
-
-       this.prototype._initializers.push(init);
-};
-
 L.map = function (id, options) {
        return new L.Map(id, options);
 };
 
 
+/*
+ * Mercator projection that takes into account that the Earth is not a perfect sphere.
+ * Less popular than spherical mercator; used by projections like EPSG:3395.
+ */
 
 L.Projection.Mercator = {
        MAX_LATITUDE: 85.0840591556,
@@ -1860,15 +2021,15 @@ L.Projection.Mercator = {
 
        project: function (latlng) { // (LatLng) -> Point
                var d = L.LatLng.DEG_TO_RAD,
-                       max = this.MAX_LATITUDE,
-                       lat = Math.max(Math.min(max, latlng.lat), -max),
-                       r = this.R_MAJOR,
-                       r2 = this.R_MINOR,
-                       x = latlng.lng * d * r,
-                       y = lat * d,
-                       tmp = r2 / r,
-                       eccent = Math.sqrt(1.0 - tmp * tmp),
-                       con = eccent * Math.sin(y);
+                   max = this.MAX_LATITUDE,
+                   lat = Math.max(Math.min(max, latlng.lat), -max),
+                   r = this.R_MAJOR,
+                   r2 = this.R_MINOR,
+                   x = latlng.lng * d * r,
+                   y = lat * d,
+                   tmp = r2 / r,
+                   eccent = Math.sqrt(1.0 - tmp * tmp),
+                   con = eccent * Math.sin(y);
 
                con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
 
@@ -1880,40 +2041,41 @@ L.Projection.Mercator = {
 
        unproject: function (point) { // (Point, Boolean) -> LatLng
                var d = L.LatLng.RAD_TO_DEG,
-                       r = this.R_MAJOR,
-                       r2 = this.R_MINOR,
-                       lng = point.x * d / r,
-                       tmp = r2 / r,
-                       eccent = Math.sqrt(1 - (tmp * tmp)),
-                       ts = Math.exp(- point.y / r2),
-                       phi = (Math.PI / 2) - 2 * Math.atan(ts),
-                       numIter = 15,
-                       tol = 1e-7,
-                       i = numIter,
-                       dphi = 0.1,
-                       con;
+                   r = this.R_MAJOR,
+                   r2 = this.R_MINOR,
+                   lng = point.x * d / r,
+                   tmp = r2 / r,
+                   eccent = Math.sqrt(1 - (tmp * tmp)),
+                   ts = Math.exp(- point.y / r2),
+                   phi = (Math.PI / 2) - 2 * Math.atan(ts),
+                   numIter = 15,
+                   tol = 1e-7,
+                   i = numIter,
+                   dphi = 0.1,
+                   con;
 
                while ((Math.abs(dphi) > tol) && (--i > 0)) {
                        con = eccent * Math.sin(phi);
-                       dphi = (Math.PI / 2) - 2 * Math.atan(ts * Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
+                       dphi = (Math.PI / 2) - 2 * Math.atan(ts *
+                                   Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
                        phi += dphi;
                }
 
-               return new L.LatLng(phi * d, lng, true);
+               return new L.LatLng(phi * d, lng);
        }
 };
 
 
 
-L.CRS.EPSG3395 = L.Util.extend({}, L.CRS, {
+L.CRS.EPSG3395 = L.extend({}, L.CRS, {
        code: 'EPSG:3395',
 
        projection: L.Projection.Mercator,
 
        transformation: (function () {
                var m = L.Projection.Mercator,
-                       r = m.R_MAJOR,
-                       r2 = m.R_MINOR;
+                   r = m.R_MAJOR,
+                   r2 = m.R_MINOR;
 
                return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
        }())
@@ -1950,7 +2112,7 @@ L.TileLayer = L.Class.extend({
        },
 
        initialize: function (url, options) {
-               options = L.Util.setOptions(this, options);
+               options = L.setOptions(this, options);
 
                // detecting retina displays, adjusting tileSize and zoom levels
                if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
@@ -2003,7 +2165,7 @@ L.TileLayer = L.Class.extend({
        },
 
        onRemove: function (map) {
-               map._panes.tilePane.removeChild(this._container);
+               this._container.parentNode.removeChild(this._container);
 
                map.off({
                        'viewreset': this._resetCallback,
@@ -2088,11 +2250,11 @@ L.TileLayer = L.Class.extend({
 
        _setAutoZIndex: function (pane, compare) {
 
-               var layers = pane.getElementsByClassName('leaflet-layer'),
-                       edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
-                       zIndex;
+               var layers = pane.children,
+                   edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
+                   zIndex, i, len;
 
-               for (var i = 0, len = layers.length; i < len; i++) {
+               for (i = 0, len = layers.length; i < len; i++) {
 
                        if (layers[i] !== this._container) {
                                zIndex = parseInt(layers[i].style.zIndex, 10);
@@ -2103,7 +2265,8 @@ L.TileLayer = L.Class.extend({
                        }
                }
 
-               this.options.zIndex = this._container.style.zIndex = (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
+               this.options.zIndex = this._container.style.zIndex =
+                       (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
        },
 
        _updateOpacity: function () {
@@ -2111,7 +2274,7 @@ L.TileLayer = L.Class.extend({
 
                // stupid webkit hack to force redrawing of tiles
                var i,
-                       tiles = this._tiles;
+                   tiles = this._tiles;
 
                if (L.Browser.webkit) {
                        for (i in tiles) {
@@ -2143,10 +2306,9 @@ L.TileLayer = L.Class.extend({
        },
 
        _reset: function (clearOldContainer) {
-               var key,
-                       tiles = this._tiles;
+               var tiles = this._tiles;
 
-               for (key in tiles) {
+               for (var key in tiles) {
                        if (tiles.hasOwnProperty(key)) {
                                this.fire('tileunload', {tile: tiles[key]});
                        }
@@ -2166,12 +2328,12 @@ L.TileLayer = L.Class.extend({
                this._initContainer();
        },
 
-       _update: function (e) {
+       _update: function () {
 
                if (!this._map) { return; }
 
-               var bounds   = this._map.getPixelBounds(),
-                   zoom     = this._map.getZoom(),
+               var bounds = this._map.getPixelBounds(),
+                   zoom = this._map.getZoom(),
                    tileSize = this.options.tileSize;
 
                if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
@@ -2179,12 +2341,14 @@ L.TileLayer = L.Class.extend({
                }
 
                var nwTilePoint = new L.Point(
-                               Math.floor(bounds.min.x / tileSize),
-                               Math.floor(bounds.min.y / tileSize)),
-                       seTilePoint = new L.Point(
-                               Math.floor(bounds.max.x / tileSize),
-                               Math.floor(bounds.max.y / tileSize)),
-                       tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
+                       Math.floor(bounds.min.x / tileSize),
+                       Math.floor(bounds.min.y / tileSize)),
+
+                   seTilePoint = new L.Point(
+                       Math.floor(bounds.max.x / tileSize),
+                       Math.floor(bounds.max.y / tileSize)),
+
+                   tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
 
                this._addTilesFromCenterOut(tileBounds);
 
@@ -2195,7 +2359,7 @@ L.TileLayer = L.Class.extend({
 
        _addTilesFromCenterOut: function (bounds) {
                var queue = [],
-                       center = bounds.getCenter();
+                   center = bounds.getCenter();
 
                var j, i, point;
 
@@ -2276,11 +2440,13 @@ L.TileLayer = L.Class.extend({
                if (this.options.reuseTiles) {
                        L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
                        this._unusedTiles.push(tile);
+
                } else if (tile.parentNode === this._container) {
                        this._container.removeChild(tile);
                }
 
-               if (!L.Browser.android) { //For https://github.com/CloudMade/Leaflet/issues/137
+               // for https://github.com/CloudMade/Leaflet/issues/137
+               if (!L.Browser.android) {
                        tile.src = L.Util.emptyImageUrl;
                }
 
@@ -2293,11 +2459,14 @@ L.TileLayer = L.Class.extend({
                // get unused tile - or create a new tile
                var tile = this._getTile();
 
-               // Chrome 20 layouts much faster with top/left (Verify with timeline, frames)
-               // android 4 browser has display issues with top/left and requires transform instead
-               // android 3 browser not tested
-               // android 2 browser requires top/left or tiles disappear on load or first drag (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
-               // (other browsers don't currently care) - see debug/hacks/jitter.html for an example
+               /*
+               Chrome 20 layouts much faster with top/left (verify with timeline, frames)
+               Android 4 browser has display issues with top/left and requires transform instead
+               Android 3 browser not tested
+               Android 2 browser requires top/left or tiles disappear on load or first drag
+               (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
+               (other browsers don't currently care) - see debug/hacks/jitter.html for an example
+               */
                L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
 
                this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
@@ -2312,7 +2481,7 @@ L.TileLayer = L.Class.extend({
        _getZoomForUrl: function () {
 
                var options = this.options,
-                       zoom = this._map.getZoom();
+                   zoom = this._map.getZoom();
 
                if (options.zoomReverse) {
                        zoom = options.maxZoom - zoom;
@@ -2323,7 +2492,7 @@ L.TileLayer = L.Class.extend({
 
        _getTilePos: function (tilePoint) {
                var origin = this._map.getPixelOrigin(),
-                       tileSize = this.options.tileSize;
+                   tileSize = this.options.tileSize;
 
                return tilePoint.multiplyBy(tileSize).subtract(origin);
        },
@@ -2333,7 +2502,7 @@ L.TileLayer = L.Class.extend({
        getTileUrl: function (tilePoint) {
                this._adjustTilePoint(tilePoint);
 
-               return L.Util.template(this._url, L.Util.extend({
+               return L.Util.template(this._url, L.extend({
                        s: this._getSubdomain(tilePoint),
                        z: this._getZoomForUrl(),
                        x: tilePoint.x,
@@ -2367,11 +2536,8 @@ L.TileLayer = L.Class.extend({
 
        _createTileProto: function () {
                var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
+               img.style.width = img.style.height = this.options.tileSize + 'px';
                img.galleryimg = 'no';
-
-               var tileSize = this.options.tileSize;
-               img.style.width = tileSize + 'px';
-               img.style.height = tileSize + 'px';
        },
 
        _getTile: function () {
@@ -2383,9 +2549,8 @@ L.TileLayer = L.Class.extend({
                return this._createTile();
        },
 
-       _resetTile: function (tile) {
-               // Override if data stored on a tile needs to be cleaned up before reuse
-       },
+       // Override if data stored on a tile needs to be cleaned up before reuse
+       _resetTile: function (/*tile*/) {},
 
        _createTile: function () {
                var tile = this._tileImg.cloneNode(false);
@@ -2408,7 +2573,7 @@ L.TileLayer = L.Class.extend({
         }
     },
 
-       _tileOnLoad: function (e) {
+       _tileOnLoad: function () {
                var layer = this._layer;
 
                //Only if we are loading an actual image
@@ -2424,7 +2589,7 @@ L.TileLayer = L.Class.extend({
                layer._tileLoaded();
        },
 
-       _tileOnError: function (e) {
+       _tileOnError: function () {
                var layer = this._layer;
 
                layer.fire('tileerror', {
@@ -2446,6 +2611,10 @@ L.tileLayer = function (url, options) {
 };
 
 
+/*
+ * L.TileLayer.WMS is used for putting WMS tile layers on the map.
+ */
+
 L.TileLayer.WMS = L.TileLayer.extend({
 
        defaultWmsParams: {
@@ -2462,7 +2631,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
 
                this._url = url;
 
-               var wmsParams = L.Util.extend({}, this.defaultWmsParams);
+               var wmsParams = L.extend({}, this.defaultWmsParams);
 
                if (options.detectRetina && L.Browser.retina) {
                        wmsParams.width = wmsParams.height = this.options.tileSize * 2;
@@ -2479,7 +2648,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
 
                this.wmsParams = wmsParams;
 
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
        },
 
        onAdd: function (map) {
@@ -2492,26 +2661,28 @@ L.TileLayer.WMS = L.TileLayer.extend({
 
        getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
 
+               this._adjustTilePoint(tilePoint);
+
                var map = this._map,
-                       crs = map.options.crs,
-                       tileSize = this.options.tileSize,
+                   crs = map.options.crs,
+                   tileSize = this.options.tileSize,
 
-                       nwPoint = tilePoint.multiplyBy(tileSize),
-                       sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
+                   nwPoint = tilePoint.multiplyBy(tileSize),
+                   sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
 
-                       nw = crs.project(map.unproject(nwPoint, zoom)),
-                       se = crs.project(map.unproject(sePoint, zoom)),
+                   nw = crs.project(map.unproject(nwPoint, zoom)),
+                   se = crs.project(map.unproject(sePoint, zoom)),
 
-                       bbox = [nw.x, se.y, se.x, nw.y].join(','),
+                   bbox = [nw.x, se.y, se.x, nw.y].join(','),
 
-                       url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
+                   url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
 
-               return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
+               return url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox;
        },
 
        setParams: function (params, noRedraw) {
 
-               L.Util.extend(this.wmsParams, params);
+               L.extend(this.wmsParams, params);
 
                if (!noRedraw) {
                        this.redraw();
@@ -2526,20 +2697,24 @@ L.tileLayer.wms = function (url, options) {
 };
 
 
+/*
+ * L.TileLayer.Canvas is a class that you can use as a base for creating
+ * dynamically drawn Canvas-based tile layers.
+ */
+
 L.TileLayer.Canvas = L.TileLayer.extend({
        options: {
                async: false
        },
 
        initialize: function (options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
        },
 
        redraw: function () {
-               var i,
-                       tiles = this._tiles;
+               var tiles = this._tiles;
 
-               for (i in tiles) {
+               for (var i in tiles) {
                        if (tiles.hasOwnProperty(i)) {
                                this._redrawTile(tiles[i]);
                        }
@@ -2547,15 +2722,12 @@ L.TileLayer.Canvas = L.TileLayer.extend({
        },
 
        _redrawTile: function (tile) {
-               this.drawTile(tile, tile._tilePoint, tile._zoom);
+               this.drawTile(tile, tile._tilePoint, this._map._zoom);
        },
 
        _createTileProto: function () {
                var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
-
-               var tileSize = this.options.tileSize;
-               proto.width = tileSize;
-               proto.height = tileSize;
+               proto.width = proto.height = this.options.tileSize;
        },
 
        _createTile: function () {
@@ -2564,19 +2736,18 @@ L.TileLayer.Canvas = L.TileLayer.extend({
                return tile;
        },
 
-       _loadTile: function (tile, tilePoint, zoom) {
+       _loadTile: function (tile, tilePoint) {
                tile._layer = this;
                tile._tilePoint = tilePoint;
-               tile._zoom = zoom;
 
-               this.drawTile(tile, tilePoint, zoom);
+               this._redrawTile(tile);
 
                if (!this.options.async) {
                        this.tileDrawn(tile);
                }
        },
 
-       drawTile: function (tile, tilePoint, zoom) {
+       drawTile: function (/*tile, tilePoint*/) {
                // override with rendering code
        },
 
@@ -2590,6 +2761,11 @@ L.tileLayer.canvas = function (options) {
        return new L.TileLayer.Canvas(options);
 };
 
+
+/*
+ * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
+ */
+
 L.ImageOverlay = L.Class.extend({
        includes: L.Mixin.Events,
 
@@ -2601,7 +2777,7 @@ L.ImageOverlay = L.Class.extend({
                this._url = url;
                this._bounds = L.latLngBounds(bounds);
 
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
        },
 
        onAdd: function (map) {
@@ -2671,34 +2847,34 @@ L.ImageOverlay = L.Class.extend({
                this._updateOpacity();
 
                //TODO createImage util method to remove duplication
-               L.Util.extend(this._image, {
+               L.extend(this._image, {
                        galleryimg: 'no',
                        onselectstart: L.Util.falseFn,
                        onmousemove: L.Util.falseFn,
-                       onload: L.Util.bind(this._onImageLoad, this),
+                       onload: L.bind(this._onImageLoad, this),
                        src: this._url
                });
        },
 
        _animateZoom: function (e) {
                var map = this._map,
-                       image = this._image,
+                   image = this._image,
                    scale = map.getZoomScale(e.zoom),
                    nw = this._bounds.getNorthWest(),
                    se = this._bounds.getSouthEast(),
 
                    topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
                    size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
-                   currentSize = map.latLngToLayerPoint(se)._subtract(map.latLngToLayerPoint(nw)),
-                   origin = topLeft._add(size._subtract(currentSize)._divideBy(2));
+                   origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
 
-               image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
+               image.style[L.DomUtil.TRANSFORM] =
+                       L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
        },
 
        _reset: function () {
                var image   = this._image,
                    topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
-                   size    = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
+                   size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
 
                L.DomUtil.setPosition(image, topLeft);
 
@@ -2720,14 +2896,20 @@ L.imageOverlay = function (url, bounds, options) {
 };
 
 
+/*
+ * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
+ */
+
 L.Icon = L.Class.extend({
        options: {
                /*
                iconUrl: (String) (required)
+               iconRetinaUrl: (String) (optional, used for retina devices if detected)
                iconSize: (Point) (can be set through CSS)
-               iconAnchor: (Point) (centered by default if size is specified, can be set in CSS with negative margins)
+               iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
                popupAnchor: (Point) (if not specified, popup opens in the anchor point)
                shadowUrl: (Point) (no shadow by default)
+               shadowRetinaUrl: (String) (optional, used for retina devices if detected)
                shadowSize: (Point)
                shadowAnchor: (Point)
                */
@@ -2735,7 +2917,7 @@ L.Icon = L.Class.extend({
        },
 
        initialize: function (options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
        },
 
        createIcon: function () {
@@ -2764,8 +2946,8 @@ L.Icon = L.Class.extend({
 
        _setIconStyles: function (img, name) {
                var options = this.options,
-                       size = L.point(options[name + 'Size']),
-                       anchor;
+                   size = L.point(options[name + 'Size']),
+                   anchor;
 
                if (name === 'shadow') {
                        anchor = L.point(options.shadowAnchor || options.iconAnchor);
@@ -2798,12 +2980,16 @@ L.Icon = L.Class.extend({
                        el.src = src;
                } else {
                        el = document.createElement('div');
-                       el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
+                       el.style.filter =
+                               'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
                }
                return el;
        },
 
        _getIconUrl: function (name) {
+               if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
+                       return this.options[name + 'RetinaUrl'];
+               }
                return this.options[name + 'Url'];
        }
 });
@@ -2813,6 +2999,9 @@ L.icon = function (options) {
 };
 
 
+/*
+ * L.Icon.Default is the blue marker icon used by default in Leaflet.
+ */
 
 L.Icon.Default = L.Icon.extend({
 
@@ -2831,6 +3020,10 @@ L.Icon.Default = L.Icon.extend({
                        return this.options[key];
                }
 
+               if (L.Browser.retina && name === 'icon') {
+                       name += '@2x';
+               }
+
                var path = L.Icon.Default.imagePath;
 
                if (!path) {
@@ -2872,11 +3065,13 @@ L.Marker = L.Class.extend({
                clickable: true,
                draggable: false,
                zIndexOffset: 0,
-               opacity: 1
+               opacity: 1,
+               riseOnHover: false,
+               riseOffset: 250
        },
 
        initialize: function (latlng, options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
                this._latlng = L.latLng(latlng);
        },
 
@@ -2920,12 +3115,14 @@ L.Marker = L.Class.extend({
 
                this.update();
 
-               this.fire('move', { latlng: this._latlng });
+               return this.fire('move', { latlng: this._latlng });
        },
 
        setZIndexOffset: function (offset) {
                this.options.zIndexOffset = offset;
                this.update();
+
+               return this;
        },
 
        setIcon: function (icon) {
@@ -2939,13 +3136,17 @@ L.Marker = L.Class.extend({
                        this._initIcon();
                        this.update();
                }
+
+               return this;
        },
 
        update: function () {
-               if (!this._icon) { return; }
+               if (this._icon) {
+                       var pos = this._map.latLngToLayerPoint(this._latlng).round();
+                       this._setPos(pos);
+               }
 
-               var pos = this._map.latLngToLayerPoint(this._latlng).round();
-               this._setPos(pos);
+               return this;
        },
 
        _initIcon: function () {
@@ -2966,7 +3167,14 @@ L.Marker = L.Class.extend({
                        needOpacityUpdate = (this.options.opacity < 1);
 
                        L.DomUtil.addClass(this._icon, classToAdd);
+
+                       if (options.riseOnHover) {
+                               L.DomEvent
+                                       .on(this._icon, 'mouseover', this._bringToFront, this)
+                                       .on(this._icon, 'mouseout', this._resetZIndex, this);
+                       }
                }
+
                if (!this._shadow) {
                        this._shadow = options.icon.createShadow();
 
@@ -2992,6 +3200,12 @@ L.Marker = L.Class.extend({
        _removeIcon: function () {
                var panes = this._map._panes;
 
+               if (this.options.riseOnHover) {
+                       L.DomEvent
+                           .off(this._icon, 'mouseover', this._bringToFront)
+                           .off(this._icon, 'mouseout', this._resetZIndex);
+               }
+
                panes.markerPane.removeChild(this._icon);
 
                if (this._shadow) {
@@ -3008,7 +3222,13 @@ L.Marker = L.Class.extend({
                        L.DomUtil.setPosition(this._shadow, pos);
                }
 
-               this._icon.style.zIndex = pos.y + this.options.zIndexOffset;
+               this._zIndex = pos.y + this.options.zIndexOffset;
+
+               this._resetZIndex();
+       },
+
+       _updateZIndex: function (offset) {
+               this._icon.style.zIndex = this._zIndex + offset;
        },
 
        _animateZoom: function (opt) {
@@ -3018,12 +3238,13 @@ L.Marker = L.Class.extend({
        },
 
        _initInteraction: function () {
-               if (!this.options.clickable) {
-                       return;
-               }
+
+               if (!this.options.clickable) { return; }
+
+               // TODO refactor into something shared with Map/Path/etc. to DRY it up
 
                var icon = this._icon,
-                       events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
+                   events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
 
                L.DomUtil.addClass(icon, 'leaflet-clickable');
                L.DomEvent.on(icon, 'click', this._onMouseClick, this);
@@ -3042,20 +3263,32 @@ L.Marker = L.Class.extend({
        },
 
        _onMouseClick: function (e) {
-               if (this.hasEventListeners(e.type)) {
+               var wasDragged = this.dragging && this.dragging.moved();
+
+               if (this.hasEventListeners(e.type) || wasDragged) {
                        L.DomEvent.stopPropagation(e);
                }
-               if (this.dragging && this.dragging.moved()) { return; }
-               if (this._map.dragging && this._map.dragging.moved()) { return; }
+
+               if (wasDragged) { return; }
+
+               if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
+
                this.fire(e.type, {
                        originalEvent: e
                });
        },
 
        _fireMouseEvent: function (e) {
+
                this.fire(e.type, {
                        originalEvent: e
                });
+
+               // TODO proper custom event propagation
+               // this line will always be called if marker is in a FeatureGroup
+               if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
+                       L.DomEvent.preventDefault(e);
+               }
                if (e.type !== 'mousedown') {
                        L.DomEvent.stopPropagation(e);
                }
@@ -3073,6 +3306,14 @@ L.Marker = L.Class.extend({
                if (this._shadow) {
                        L.DomUtil.setOpacity(this._shadow, this.options.opacity);
                }
+       },
+
+       _bringToFront: function () {
+               this._updateZIndex(this.options.riseOffset);
+       },
+
+       _resetZIndex: function () {
+               this._updateZIndex(0);
        }
 });
 
@@ -3081,6 +3322,11 @@ L.marker = function (latlng, options) {
 };
 
 
+/*
+ * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
+ * to use with L.Marker.
+ */
+
 L.DivIcon = L.Icon.extend({
        options: {
                iconSize: new L.Point(12, 12), // also can be set through CSS
@@ -3103,7 +3349,7 @@ L.DivIcon = L.Icon.extend({
 
                if (options.bgPos) {
                        div.style.backgroundPosition =
-                                       (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
+                               (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
                }
 
                this._setIconStyles(div, 'icon');
@@ -3120,6 +3366,9 @@ L.divIcon = function (options) {
 };
 
 
+/*
+ * L.Popup is used for displaying popups on the map.
+ */
 
 L.Map.mergeOptions({
        closePopupOnClick: true
@@ -3136,13 +3385,15 @@ L.Popup = L.Class.extend({
                closeButton: true,
                offset: new L.Point(0, 6),
                autoPanPadding: new L.Point(5, 5),
-               className: ''
+               className: '',
+               zoomAnimation: true
        },
 
        initialize: function (options, source) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
 
                this._source = source;
+               this._animated = L.Browser.any3d && this.options.zoomAnimation;
        },
 
        onAdd: function (map) {
@@ -3162,7 +3413,7 @@ L.Popup = L.Class.extend({
 
                map.on('viewreset', this._updatePosition, this);
 
-               if (L.Browser.any3d) {
+               if (this._animated) {
                        map.on('zoomanim', this._zoomAnimation, this);
                }
 
@@ -3224,25 +3475,29 @@ L.Popup = L.Class.extend({
                        map._popup = null;
 
                        map
-                               .removeLayer(this)
-                               .fire('popupclose', {popup: this});
+                           .removeLayer(this)
+                           .fire('popupclose', {popup: this});
                }
        },
 
        _initLayout: function () {
                var prefix = 'leaflet-popup',
-                       container = this._container = L.DomUtil.create('div', prefix + ' ' + this.options.className + ' leaflet-zoom-animated'),
+                       containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
+                               (this._animated ? 'animated' : 'hide'),
+                       container = this._container = L.DomUtil.create('div', containerClass),
                        closeButton;
 
                if (this.options.closeButton) {
-                       closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
+                       closeButton = this._closeButton =
+                               L.DomUtil.create('a', prefix + '-close-button', container);
                        closeButton.href = '#close';
                        closeButton.innerHTML = '&#215;';
 
                        L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
                }
 
-               var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
+               var wrapper = this._wrapper =
+                       L.DomUtil.create('div', prefix + '-content-wrapper', container);
                L.DomEvent.disableClickPropagation(wrapper);
 
                this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
@@ -3282,7 +3537,7 @@ L.Popup = L.Class.extend({
 
        _updateLayout: function () {
                var container = this._contentNode,
-                       style = container.style;
+                   style = container.style;
 
                style.width = '';
                style.whiteSpace = 'nowrap';
@@ -3297,8 +3552,8 @@ L.Popup = L.Class.extend({
                style.height = '';
 
                var height = container.offsetHeight,
-                       maxHeight = this.options.maxHeight,
-                       scrolledClass = 'leaflet-popup-scrolled';
+                   maxHeight = this.options.maxHeight,
+                   scrolledClass = 'leaflet-popup-scrolled';
 
                if (maxHeight && height > maxHeight) {
                        style.height = maxHeight + 'px';
@@ -3311,16 +3566,18 @@ L.Popup = L.Class.extend({
        },
 
        _updatePosition: function () {
+               if (!this._map) { return; }
+
                var pos = this._map.latLngToLayerPoint(this._latlng),
-                       is3d = L.Browser.any3d,
-                       offset = this.options.offset;
+                   animated = this._animated,
+                   offset = this.options.offset;
 
-               if (is3d) {
+               if (animated) {
                        L.DomUtil.setPosition(this._container, pos);
                }
 
-               this._containerBottom = -offset.y - (is3d ? 0 : pos.y);
-               this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x);
+               this._containerBottom = -offset.y - (animated ? 0 : pos.y);
+               this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
 
                //Bottom position the popup in case the height of the popup changes (images loading etc)
                this._container.style.bottom = this._containerBottom + 'px';
@@ -3337,20 +3594,20 @@ L.Popup = L.Class.extend({
                if (!this.options.autoPan) { return; }
 
                var map = this._map,
-                       containerHeight = this._container.offsetHeight,
-                       containerWidth = this._containerWidth,
+                   containerHeight = this._container.offsetHeight,
+                   containerWidth = this._containerWidth,
 
-                       layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
+                   layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
 
-               if (L.Browser.any3d) {
+               if (this._animated) {
                        layerPos._add(L.DomUtil.getPosition(this._container));
                }
 
                var containerPos = map.layerPointToContainerPoint(layerPos),
-                       padding = this.options.autoPanPadding,
-                       size = map.getSize(),
-                       dx = 0,
-                       dy = 0;
+                   padding = this.options.autoPanPadding,
+                   size = map.getSize(),
+                   dx = 0,
+                   dy = 0;
 
                if (containerPos.x < 0) {
                        dx = containerPos.x - padding.x;
@@ -3382,7 +3639,7 @@ L.popup = function (options, source) {
 
 
 /*
- * Popup extension to L.Marker, adding openPopup & bindPopup methods.
+ * Popup extension to L.Marker, adding popup-related methods.
  */
 
 L.Marker.include({
@@ -3411,13 +3668,13 @@ L.Marker.include({
                        anchor = anchor.add(options.offset);
                }
 
-               options = L.Util.extend({offset: anchor}, options);
+               options = L.extend({offset: anchor}, options);
 
                if (!this._popup) {
                        this
-                               .on('click', this.openPopup, this)
-                               .on('remove', this.closePopup, this)
-                               .on('move', this._movePopup, this);
+                           .on('click', this.openPopup, this)
+                           .on('remove', this.closePopup, this)
+                           .on('move', this._movePopup, this);
                }
 
                this._popup = new L.Popup(options, this)
@@ -3430,9 +3687,9 @@ L.Marker.include({
                if (this._popup) {
                        this._popup = null;
                        this
-                               .off('click', this.openPopup)
-                               .off('remove', this.closePopup)
-                               .off('move', this._movePopup);
+                           .off('click', this.openPopup)
+                           .off('remove', this.closePopup)
+                           .off('move', this._movePopup);
                }
                return this;
        },
@@ -3443,6 +3700,9 @@ L.Marker.include({
 });
 
 
+/*
+ * Adds popup-related methods to L.Map.
+ */
 
 L.Map.include({
        openPopup: function (popup) {
@@ -3451,8 +3711,8 @@ L.Map.include({
                this._popup = popup;
 
                return this
-                       .addLayer(popup)
-                       .fire('popupopen', {popup: this._popup});
+                   .addLayer(popup)
+                   .fire('popupopen', {popup: this._popup});
        },
 
        closePopup: function () {
@@ -3463,8 +3723,10 @@ L.Map.include({
        }
 });
 
+
 /*
- * L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer.
+ * L.LayerGroup is a class to combine several layers into one so that
+ * you can manipulate the group (e.g. add/remove it) as one layer.
  */
 
 L.LayerGroup = L.Class.extend({
@@ -3481,7 +3743,7 @@ L.LayerGroup = L.Class.extend({
        },
 
        addLayer: function (layer) {
-               var id = L.Util.stamp(layer);
+               var id = L.stamp(layer);
 
                this._layers[id] = layer;
 
@@ -3493,7 +3755,7 @@ L.LayerGroup = L.Class.extend({
        },
 
        removeLayer: function (layer) {
-               var id = L.Util.stamp(layer);
+               var id = L.stamp(layer);
 
                delete this._layers[id];
 
@@ -3511,7 +3773,7 @@ L.LayerGroup = L.Class.extend({
 
        invoke: function (methodName) {
                var args = Array.prototype.slice.call(arguments, 1),
-                       i, layer;
+                   i, layer;
 
                for (i in this._layers) {
                        if (this._layers.hasOwnProperty(i)) {
@@ -3547,6 +3809,10 @@ L.LayerGroup = L.Class.extend({
                                method.call(context, this._layers[i]);
                        }
                }
+       },
+
+       setZIndex: function (zIndex) {
+               return this.invoke('setZIndex', zIndex);
        }
 });
 
@@ -3556,43 +3822,50 @@ L.layerGroup = function (layers) {
 
 
 /*
- * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
+ * shared between a group of interactive layers (like vectors or markers).
  */
 
 L.FeatureGroup = L.LayerGroup.extend({
        includes: L.Mixin.Events,
 
+       statics: {
+               EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu'
+       },
+
        addLayer: function (layer) {
-               if (this._layers[L.Util.stamp(layer)]) {
+               if (this._layers[L.stamp(layer)]) {
                        return this;
                }
 
-               layer.on('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
+               layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
 
                L.LayerGroup.prototype.addLayer.call(this, layer);
 
                if (this._popupContent && layer.bindPopup) {
-                       layer.bindPopup(this._popupContent);
+                       layer.bindPopup(this._popupContent, this._popupOptions);
                }
 
-               return this;
+               return this.fire('layeradd', {layer: layer});
        },
 
        removeLayer: function (layer) {
-               layer.off('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
+               layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
 
                L.LayerGroup.prototype.removeLayer.call(this, layer);
 
+
                if (this._popupContent) {
-                       return this.invoke('unbindPopup');
-               } else {
-                       return this;
+                       this.invoke('unbindPopup');
                }
+
+               return this.fire('layerremove', {layer: layer});
        },
 
-       bindPopup: function (content) {
+       bindPopup: function (content, options) {
                this._popupContent = content;
-               return this.invoke('bindPopup', content);
+               this._popupOptions = options;
+               return this.invoke('bindPopup', content, options);
        },
 
        setStyle: function (style) {
@@ -3609,9 +3882,11 @@ L.FeatureGroup = L.LayerGroup.extend({
 
        getBounds: function () {
                var bounds = new L.LatLngBounds();
+
                this.eachLayer(function (layer) {
                        bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
-               }, this);
+               });
+
                return bounds;
        },
 
@@ -3629,7 +3904,7 @@ L.featureGroup = function (layers) {
 
 
 /*
- * L.Path is a base class for rendering vector paths on a map. It's inherited by Polyline, Circle, etc.
+ * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
  */
 
 L.Path = L.Class.extend({
@@ -3638,11 +3913,10 @@ L.Path = L.Class.extend({
        statics: {
                // how much to extend the clip area around the map view
                // (relative to its size, e.g. 0.5 is half the screen in each direction)
-               // set in such way that SVG element doesn't exceed 1280px (vector layers flicker on dragend if it is)
+               // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
                CLIP_PADDING: L.Browser.mobile ?
                        Math.max(0, Math.min(0.5,
-                               (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2))
-                       : 0.5
+                               (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5
        },
 
        options: {
@@ -3660,7 +3934,7 @@ L.Path = L.Class.extend({
        },
 
        initialize: function (options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
        },
 
        onAdd: function (map) {
@@ -3678,6 +3952,8 @@ L.Path = L.Class.extend({
                        this._map._pathRoot.appendChild(this._container);
                }
 
+               this.fire('add');
+
                map.on({
                        'viewreset': this.projectLatlngs,
                        'moveend': this._updatePath
@@ -3692,6 +3968,8 @@ L.Path = L.Class.extend({
        onRemove: function (map) {
                map._pathRoot.removeChild(this._container);
 
+               // Need to fire remove event before we set _map to null as the event hooks might need the object
+               this.fire('remove');
                this._map = null;
 
                if (L.Browser.vml) {
@@ -3700,8 +3978,6 @@ L.Path = L.Class.extend({
                        this._fill = null;
                }
 
-               this.fire('remove');
-
                map.off({
                        'viewreset': this.projectLatlngs,
                        'moveend': this._updatePath
@@ -3713,7 +3989,7 @@ L.Path = L.Class.extend({
        },
 
        setStyle: function (style) {
-               L.Util.setOptions(this, style);
+               L.setOptions(this, style);
 
                if (this._container) {
                        this._updateStyle();
@@ -3734,16 +4010,20 @@ L.Path = L.Class.extend({
 L.Map.include({
        _updatePathViewport: function () {
                var p = L.Path.CLIP_PADDING,
-                       size = this.getSize(),
-                       panePos = L.DomUtil.getPosition(this._mapPane),
-                       min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
-                       max = min.add(size.multiplyBy(1 + p * 2)._round());
+                   size = this.getSize(),
+                   panePos = L.DomUtil.getPosition(this._mapPane),
+                   min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
+                   max = min.add(size.multiplyBy(1 + p * 2)._round());
 
                this._pathViewport = new L.Bounds(min, max);
        }
 });
 
 
+/*
+ * Extends L.Path with SVG-specific rendering code.
+ */
+
 L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
 
 L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
@@ -3755,7 +4035,7 @@ L.Path = L.Path.extend({
 
        bringToFront: function () {
                var root = this._map._pathRoot,
-                       path = this._container;
+                   path = this._container;
 
                if (path && root.lastChild !== path) {
                        root.appendChild(path);
@@ -3765,8 +4045,8 @@ L.Path = L.Path.extend({
 
        bringToBack: function () {
                var root = this._map._pathRoot,
-                       path = this._container,
-                       first = root.firstChild;
+                   path = this._container,
+                   first = root.firstChild;
 
                if (path && first !== path) {
                        root.insertBefore(path, first);
@@ -3845,7 +4125,8 @@ L.Path = L.Path.extend({
 
                        L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
 
-                       var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'mousemove', 'contextmenu'];
+                       var events = ['dblclick', 'mousedown', 'mouseover',
+                                     'mouseout', 'mousemove', 'contextmenu'];
                        for (var i = 0; i < events.length; i++) {
                                L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
                        }
@@ -3853,22 +4134,18 @@ L.Path = L.Path.extend({
        },
 
        _onMouseClick: function (e) {
-               if (this._map.dragging && this._map.dragging.moved()) {
-                       return;
-               }
+               if (this._map.dragging && this._map.dragging.moved()) { return; }
 
                this._fireMouseEvent(e);
        },
 
        _fireMouseEvent: function (e) {
-               if (!this.hasEventListeners(e.type)) {
-                       return;
-               }
+               if (!this.hasEventListeners(e.type)) { return; }
 
                var map = this._map,
-                       containerPoint = map.mouseEventToContainerPoint(e),
-                       layerPoint = map.containerPointToLayerPoint(containerPoint),
-                       latlng = map.layerPointToLatLng(layerPoint);
+                   containerPoint = map.mouseEventToContainerPoint(e),
+                   layerPoint = map.containerPointToLayerPoint(containerPoint),
+                   latlng = map.layerPointToLatLng(layerPoint);
 
                this.fire(e.type, {
                        latlng: latlng,
@@ -3880,7 +4157,9 @@ L.Path = L.Path.extend({
                if (e.type === 'contextmenu') {
                        L.DomEvent.preventDefault(e);
                }
-               L.DomEvent.stopPropagation(e);
+               if (e.type !== 'mousemove') {
+                       L.DomEvent.stopPropagation(e);
+               }
        }
 });
 
@@ -3906,13 +4185,12 @@ L.Map.include({
                }
        },
 
-       _animatePathZoom: function (opt) {
-               var scale = this.getZoomScale(opt.zoom),
-                       offset = this._getCenterOffset(opt.center).divideBy(1 - 1 / scale),
-                       viewportPos = this.containerPointToLayerPoint(this.getSize().multiplyBy(-L.Path.CLIP_PADDING)),
-                       origin = viewportPos.add(offset).round();
+       _animatePathZoom: function (e) {
+               var scale = this.getZoomScale(e.zoom),
+                   offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
 
-               this._pathRoot.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString((origin.multiplyBy(-1).add(L.DomUtil.getPosition(this._pathRoot)).multiplyBy(scale).add(origin))) + ' scale(' + scale + ') ';
+               this._pathRoot.style[L.DomUtil.TRANSFORM] =
+                       L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
 
                this._pathZooming = true;
        },
@@ -3922,6 +4200,7 @@ L.Map.include({
        },
 
        _updateSvgViewport: function () {
+
                if (this._pathZooming) {
                        // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
                        // When the zoom animation ends we will be updated again anyway
@@ -3932,12 +4211,12 @@ L.Map.include({
                this._updatePathViewport();
 
                var vp = this._pathViewport,
-                       min = vp.min,
-                       max = vp.max,
-                       width = max.x - min.x,
-                       height = max.y - min.y,
-                       root = this._pathRoot,
-                       pane = this._panes.overlayPane;
+                   min = vp.min,
+                   max = vp.max,
+                   width = max.x - min.x,
+                   height = max.y - min.y,
+                   root = this._pathRoot,
+                   pane = this._panes.overlayPane;
 
                // Hack to make flicker on drag end on mobile webkit less irritating
                if (L.Browser.mobileWebkit) {
@@ -3957,14 +4236,14 @@ L.Map.include({
 
 
 /*
- * Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method.
+ * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
  */
 
 L.Path.include({
 
        bindPopup: function (content, options) {
 
-               if (!this._popup || this._popup.options !== options) {
+               if (!this._popup || options) {
                        this._popup = new L.Popup(options, this);
                }
 
@@ -3972,8 +4251,9 @@ L.Path.include({
 
                if (!this._popupHandlersAdded) {
                        this
-                               .on('click', this._openPopup, this)
-                               .on('remove', this._closePopup, this);
+                           .on('click', this._openPopup, this)
+                           .on('remove', this.closePopup, this);
+
                        this._popupHandlersAdded = true;
                }
 
@@ -3984,8 +4264,10 @@ L.Path.include({
                if (this._popup) {
                        this._popup = null;
                        this
-                               .off('click', this.openPopup)
-                               .off('remove', this.closePopup);
+                           .off('click', this._openPopup)
+                           .off('remove', this.closePopup);
+
+                       this._popupHandlersAdded = false;
                }
                return this;
        },
@@ -3993,8 +4275,9 @@ L.Path.include({
        openPopup: function (latlng) {
 
                if (this._popup) {
+                       // open the popup from one of the path's points if not specified
                        latlng = latlng || this._latlng ||
-                                       this._latlngs[Math.floor(this._latlngs.length / 2)];
+                                this._latlngs[Math.floor(this._latlngs.length / 2)];
 
                        this._openPopup({latlng: latlng});
                }
@@ -4002,13 +4285,16 @@ L.Path.include({
                return this;
        },
 
+       closePopup: function () {
+               if (this._popup) {
+                       this._popup._close();
+               }
+               return this;
+       },
+
        _openPopup: function (e) {
                this._popup.setLatLng(e.latlng);
                this._map.openPopup(this._popup);
-       },
-
-       _closePopup: function () {
-               this._popup._close();
        }
 });
 
@@ -4027,6 +4313,7 @@ L.Browser.vml = !L.Browser.svg && (function () {
                shape.style.behavior = 'url(#default#VML)';
 
                return shape && (typeof shape.adj === 'object');
+
        } catch (e) {
                return false;
        }
@@ -4046,7 +4333,8 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
                        };
                } catch (e) {
                        return function (name) {
-                               return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
+                               return document.createElement(
+                                       '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
                        };
                }
        }()),
@@ -4071,9 +4359,9 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
 
        _updateStyle: function () {
                var stroke = this._stroke,
-                       fill = this._fill,
-                       options = this.options,
-                       container = this._container;
+                   fill = this._fill,
+                   options = this.options,
+                   container = this._container;
 
                container.stroked = options.stroke;
                container.filled = options.fill;
@@ -4087,11 +4375,15 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
                        stroke.weight = options.weight + 'px';
                        stroke.color = options.color;
                        stroke.opacity = options.opacity;
+
                        if (options.dashArray) {
-                               stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
+                               stroke.dashStyle = options.dashArray instanceof Array ?
+                                   options.dashArray.join(' ') :
+                                   options.dashArray.replace(/ *, */g, ' ');
                        } else {
                                stroke.dashStyle = '';
                        }
+
                } else if (stroke) {
                        container.removeChild(stroke);
                        this._stroke = null;
@@ -4104,6 +4396,7 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
                        }
                        fill.color = options.fillColor || options.color;
                        fill.opacity = options.fillOpacity;
+
                } else if (fill) {
                        container.removeChild(fill);
                        this._fill = null;
@@ -4157,7 +4450,7 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
        },
 
        setStyle: function (style) {
-               L.Util.setOptions(this, style);
+               L.setOptions(this, style);
 
                if (this._map) {
                        this._updateStyle();
@@ -4171,6 +4464,10 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
                    .off('viewreset', this.projectLatlngs, this)
                    .off('moveend', this._updatePath, this);
 
+               if (this.options.clickable) {
+                       this._map.off('click', this._onClick, this);
+               }
+
                this._requestUpdate();
 
                this._map = null;
@@ -4231,23 +4528,19 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
                if (this._checkIfEmpty()) { return; }
 
                var ctx = this._ctx,
-                       options = this.options;
+                   options = this.options;
 
                this._drawPath();
                ctx.save();
                this._updateStyle();
 
                if (options.fill) {
-                       if (options.fillOpacity < 1) {
-                               ctx.globalAlpha = options.fillOpacity;
-                       }
+                       ctx.globalAlpha = options.fillOpacity;
                        ctx.fill();
                }
 
                if (options.stroke) {
-                       if (options.opacity < 1) {
-                               ctx.globalAlpha = options.opacity;
-                       }
+                       ctx.globalAlpha = options.opacity;
                        ctx.stroke();
                }
 
@@ -4266,7 +4559,12 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
 
        _onClick: function (e) {
                if (this._containsPoint(e.layerPoint)) {
-                       this.fire('click', e);
+                       this.fire('click', {
+                               latlng: e.latlng,
+                               layerPoint: e.layerPoint,
+                               containerPoint: e.containerPoint,
+                               originalEvent: e
+                       });
                }
        }
 });
@@ -4274,7 +4572,7 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
 L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
        _initPathRoot: function () {
                var root = this._pathRoot,
-                       ctx;
+                   ctx;
 
                if (!root) {
                        root = this._pathRoot = document.createElement("canvas");
@@ -4297,16 +4595,14 @@ L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {}
        },
 
        _updateCanvasViewport: function () {
-               if (this._pathZooming) {
-                       //Don't redraw while zooming. See _updateSvgViewport for more details
-                       return;
-               }
+               // don't redraw while zooming. See _updateSvgViewport for more details
+               if (this._pathZooming) { return; }
                this._updatePathViewport();
 
                var vp = this._pathViewport,
-                       min = vp.min,
-                       size = vp.max.subtract(min),
-                       root = this._pathRoot;
+                   min = vp.min,
+                   size = vp.max.subtract(min),
+                   root = this._pathRoot;
 
                //TODO check if this works properly on mobile webkit
                L.DomUtil.setPosition(root, min);
@@ -4322,6 +4618,8 @@ L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {}
  * and polylines (clipping, simplification, distances, etc.)
  */
 
+/*jshint bitwise:false */ // allow bitwise oprations for this file
+
 L.LineUtil = {
 
        // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
@@ -4356,15 +4654,15 @@ L.LineUtil = {
        _simplifyDP: function (points, sqTolerance) {
 
                var len = points.length,
-                       ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
-                       markers = new ArrayConstructor(len);
+                   ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
+                   markers = new ArrayConstructor(len);
 
                markers[0] = markers[len - 1] = 1;
 
                this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
 
                var i,
-                       newPoints = [];
+                   newPoints = [];
 
                for (i = 0; i < len; i++) {
                        if (markers[i]) {
@@ -4378,7 +4676,7 @@ L.LineUtil = {
        _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
 
                var maxSqDist = 0,
-                       index, i, sqDist;
+                   index, i, sqDist;
 
                for (i = first + 1; i <= last - 1; i++) {
                        sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
@@ -4413,17 +4711,14 @@ L.LineUtil = {
                return reducedPoints;
        },
 
-       /*jshint bitwise:false */ // temporarily allow bitwise oprations
-
        // Cohen-Sutherland line clipping algorithm.
        // Used to avoid rendering parts of a polyline that are not currently visible.
 
        clipSegment: function (a, b, bounds, useLastCode) {
-               var min = bounds.min,
-                       max = bounds.max;
-
                var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
-                       codeB = this._getBitCode(b, bounds);
+                   codeB = this._getBitCode(b, bounds),
+
+                   codeOut, p, newCode;
 
                // save 2nd code to avoid calculating it on the next segment
                this._lastCode = codeB;
@@ -4437,9 +4732,9 @@ L.LineUtil = {
                                return false;
                        // other cases
                        } else {
-                               var codeOut = codeA || codeB,
-                                       p = this._getEdgeIntersection(a, b, codeOut, bounds),
-                                       newCode = this._getBitCode(p, bounds);
+                               codeOut = codeA || codeB,
+                               p = this._getEdgeIntersection(a, b, codeOut, bounds),
+                               newCode = this._getBitCode(p, bounds);
 
                                if (codeOut === codeA) {
                                        a = p;
@@ -4454,9 +4749,9 @@ L.LineUtil = {
 
        _getEdgeIntersection: function (a, b, code, bounds) {
                var dx = b.x - a.x,
-                       dy = b.y - a.y,
-                       min = bounds.min,
-                       max = bounds.max;
+                   dy = b.y - a.y,
+                   min = bounds.min,
+                   max = bounds.max;
 
                if (code & 8) { // top
                        return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
@@ -4486,23 +4781,21 @@ L.LineUtil = {
                return code;
        },
 
-       /*jshint bitwise:true */
-
        // square distance (to avoid unnecessary Math.sqrt calls)
        _sqDist: function (p1, p2) {
                var dx = p2.x - p1.x,
-                       dy = p2.y - p1.y;
+                   dy = p2.y - p1.y;
                return dx * dx + dy * dy;
        },
 
        // return closest point on segment or distance to that point
        _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
                var x = p1.x,
-                       y = p1.y,
-                       dx = p2.x - x,
-                       dy = p2.y - y,
-                       dot = dx * dx + dy * dy,
-                       t;
+                   y = p1.y,
+                   dx = p2.x - x,
+                   dy = p2.y - y,
+                   dot = dx * dx + dy * dy,
+                   t;
 
                if (dot > 0) {
                        t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
@@ -4524,20 +4817,15 @@ L.LineUtil = {
 };
 
 
+/*
+ * L.Polygon is used to display polylines on a map.
+ */
+
 L.Polyline = L.Path.extend({
        initialize: function (latlngs, options) {
                L.Path.prototype.initialize.call(this, options);
 
                this._latlngs = this._convertLatLngs(latlngs);
-
-               // TODO refactor: move to Polyline.Edit.js
-               if (L.Handler.PolyEdit) {
-                       this.editing = new L.Handler.PolyEdit(this);
-
-                       if (this.options.editable) {
-                               this.editing.enable();
-                       }
-               }
        },
 
        options: {
@@ -4576,7 +4864,7 @@ L.Polyline = L.Path.extend({
                return this.redraw();
        },
 
-       spliceLatLngs: function (index, howMany) {
+       spliceLatLngs: function () { // (Number index, Number howMany)
                var removed = [].splice.apply(this._latlngs, arguments);
                this._convertLatLngs(this._latlngs);
                this.redraw();
@@ -4605,35 +4893,21 @@ L.Polyline = L.Path.extend({
        },
 
        getBounds: function () {
-               var b = new L.LatLngBounds();
-               var latLngs = this.getLatLngs();
-               for (var i = 0, len = latLngs.length; i < len; i++) {
-                       b.extend(latLngs[i]);
-               }
-               return b;
-       },
-
-       // TODO refactor: move to Polyline.Edit.js
-       onAdd: function (map) {
-               L.Path.prototype.onAdd.call(this, map);
-
-               if (this.editing && this.editing.enabled()) {
-                       this.editing.addHooks();
-               }
-       },
+               var bounds = new L.LatLngBounds(),
+                   latLngs = this.getLatLngs(),
+                   i, len;
 
-       onRemove: function (map) {
-               if (this.editing && this.editing.enabled()) {
-                       this.editing.removeHooks();
+               for (i = 0, len = latLngs.length; i < len; i++) {
+                       bounds.extend(latLngs[i]);
                }
 
-               L.Path.prototype.onRemove.call(this, map);
+               return bounds;
        },
 
        _convertLatLngs: function (latlngs) {
                var i, len;
                for (i = 0, len = latlngs.length; i < len; i++) {
-                       if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
+                       if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
                                return;
                        }
                        latlngs[i] = L.latLng(latlngs[i]);
@@ -4660,8 +4934,8 @@ L.Polyline = L.Path.extend({
 
        _clipPoints: function () {
                var points = this._originalPoints,
-                       len = points.length,
-                       i, k, segment;
+                   len = points.length,
+                   i, k, segment;
 
                if (this.options.noClip) {
                        this._parts = [points];
@@ -4671,8 +4945,8 @@ L.Polyline = L.Path.extend({
                this._parts = [];
 
                var parts = this._parts,
-                       vp = this._map._pathViewport,
-                       lu = L.LineUtil;
+                   vp = this._map._pathViewport,
+                   lu = L.LineUtil;
 
                for (i = 0, k = 0; i < len - 1; i++) {
                        segment = lu.clipSegment(points[i], points[i + 1], vp, i);
@@ -4694,7 +4968,7 @@ L.Polyline = L.Path.extend({
        // simplify each clipped part of the polyline
        _simplifyPoints: function () {
                var parts = this._parts,
-                       lu = L.LineUtil;
+                   lu = L.LineUtil;
 
                for (var i = 0, len = parts.length; i < len; i++) {
                        parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
@@ -4717,10 +4991,10 @@ L.polyline = function (latlngs, options) {
 
 
 /*
- * L.PolyUtil contains utilify functions for polygons (clipping, etc.).
+ * L.PolyUtil contains utility functions for polygons (clipping, etc.).
  */
 
-/*jshint bitwise:false */ // allow bitwise oprations here
+/*jshint bitwise:false */ // allow bitwise operations here
 
 L.PolyUtil = {};
 
@@ -4729,14 +5003,12 @@ L.PolyUtil = {};
  * Used to avoid rendering parts of a polygon that are not currently visible.
  */
 L.PolyUtil.clipPolygon = function (points, bounds) {
-       var min = bounds.min,
-               max = bounds.max,
-               clippedPoints,
-               edges = [1, 4, 2, 8],
-               i, j, k,
-               a, b,
-               len, edge, p,
-               lu = L.LineUtil;
+       var clippedPoints,
+           edges = [1, 4, 2, 8],
+           i, j, k,
+           a, b,
+           len, edge, p,
+           lu = L.LineUtil;
 
        for (i = 0, len = points.length; i < len; i++) {
                points[i]._code = lu._getBitCode(points[i], bounds);
@@ -4774,8 +5046,6 @@ L.PolyUtil.clipPolygon = function (points, bounds) {
        return points;
 };
 
-/*jshint bitwise:true */
-
 
 /*
  * L.Polygon is used to display polygons on a map.
@@ -4789,7 +5059,7 @@ L.Polygon = L.Polyline.extend({
        initialize: function (latlngs, options) {
                L.Polyline.prototype.initialize.call(this, latlngs, options);
 
-               if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
+               if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
                        this._latlngs = this._convertLatLngs(latlngs[0]);
                        this._holes = latlngs.slice(1);
                }
@@ -4802,14 +5072,14 @@ L.Polygon = L.Polyline.extend({
                // TODO move this logic to Polyline to get rid of duplication
                this._holePoints = [];
 
-               if (!this._holes) {
-                       return;
-               }
+               if (!this._holes) { return; }
+
+               var i, j, len, len2;
 
-               for (var i = 0, len = this._holes.length, hole; i < len; i++) {
+               for (i = 0, len = this._holes.length; i < len; i++) {
                        this._holePoints[i] = [];
 
-                       for (var j = 0, len2 = this._holes[i].length; j < len2; j++) {
+                       for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
                                this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
                        }
                }
@@ -4817,20 +5087,17 @@ L.Polygon = L.Polyline.extend({
 
        _clipPoints: function () {
                var points = this._originalPoints,
-                       newParts = [];
+                   newParts = [];
 
                this._parts = [points].concat(this._holePoints);
 
-               if (this.options.noClip) {
-                       return;
-               }
+               if (this.options.noClip) { return; }
 
                for (var i = 0, len = this._parts.length; i < len; i++) {
                        var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
-                       if (!clipped.length) {
-                               continue;
+                       if (clipped.length) {
+                               newParts.push(clipped);
                        }
-                       newParts.push(clipped);
                }
 
                this._parts = newParts;
@@ -4853,7 +5120,9 @@ L.polygon = function (latlngs, options) {
 
 (function () {
        function createMulti(Klass) {
+
                return L.FeatureGroup.extend({
+
                        initialize: function (latlngs, options) {
                                this._layers = {};
                                this._options = options;
@@ -4861,7 +5130,8 @@ L.polygon = function (latlngs, options) {
                        },
 
                        setLatLngs: function (latlngs) {
-                               var i = 0, len = latlngs.length;
+                               var i = 0,
+                                   len = latlngs.length;
 
                                this.eachLayer(function (layer) {
                                        if (i < len) {
@@ -4894,7 +5164,7 @@ L.polygon = function (latlngs, options) {
 
 
 /*
- * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
  */
 
 L.Rectangle = L.Polygon.extend({
@@ -4908,13 +5178,12 @@ L.Rectangle = L.Polygon.extend({
 
        _boundsToLatLngs: function (latLngBounds) {
                latLngBounds = L.latLngBounds(latLngBounds);
-           return [
-               latLngBounds.getSouthWest(),
-               latLngBounds.getNorthWest(),
-               latLngBounds.getNorthEast(),
-               latLngBounds.getSouthEast(),
-               latLngBounds.getSouthWest()
-           ];
+               return [
+                       latLngBounds.getSouthWest(),
+                       latLngBounds.getNorthWest(),
+                       latLngBounds.getNorthEast(),
+                       latLngBounds.getSouthEast()
+               ];
        }
 });
 
@@ -4951,8 +5220,8 @@ L.Circle = L.Path.extend({
 
        projectLatlngs: function () {
                var lngRadius = this._getLngRadius(),
-                       latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
-                       point2 = this._map.latLngToLayerPoint(latlng2);
+                   latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius),
+                   point2 = this._map.latLngToLayerPoint(latlng2);
 
                this._point = this._map.latLngToLayerPoint(this._latlng);
                this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
@@ -4960,10 +5229,10 @@ L.Circle = L.Path.extend({
 
        getBounds: function () {
                var lngRadius = this._getLngRadius(),
-                       latRadius = (this._mRadius / 40075017) * 360,
-                       latlng = this._latlng,
-                       sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
-                       ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
+                   latRadius = (this._mRadius / 40075017) * 360,
+                   latlng = this._latlng,
+                   sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
+                   ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
 
                return new L.LatLngBounds(sw, ne);
        },
@@ -4974,7 +5243,7 @@ L.Circle = L.Path.extend({
 
        getPathString: function () {
                var p = this._point,
-                       r = this._radius;
+                   r = this._radius;
 
                if (this._checkIfEmpty()) {
                        return '';
@@ -4982,8 +5251,8 @@ L.Circle = L.Path.extend({
 
                if (L.Browser.svg) {
                        return "M" + p.x + "," + (p.y - r) +
-                                       "A" + r + "," + r + ",0,1,1," +
-                                       (p.x - 0.1) + "," + (p.y - r) + " z";
+                              "A" + r + "," + r + ",0,1,1," +
+                              (p.x - 0.1) + "," + (p.y - r) + " z";
                } else {
                        p._round();
                        r = Math.round(r);
@@ -5010,11 +5279,11 @@ L.Circle = L.Path.extend({
                        return false;
                }
                var vp = this._map._pathViewport,
-                       r = this._radius,
-                       p = this._point;
+                   r = this._radius,
+                   p = this._point;
 
                return p.x - r > vp.max.x || p.y - r > vp.max.y ||
-                       p.x + r < vp.min.x || p.y + r < vp.min.y;
+                      p.x + r < vp.min.x || p.y + r < vp.min.y;
        }
 });
 
@@ -5041,6 +5310,11 @@ L.CircleMarker = L.Circle.extend({
        projectLatlngs: function () {
                this._point = this._map.latLngToLayerPoint(this._latlng);
        },
+       
+       _updateStyle : function () {
+               L.Circle.prototype._updateStyle.call(this);
+               this.setRadius(this.options.radius);
+       },
 
        setRadius: function (radius) {
                this._radius = radius;
@@ -5053,11 +5327,14 @@ L.circleMarker = function (latlng, options) {
 };
 
 
+/*
+ * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
+ */
 
 L.Polyline.include(!L.Path.CANVAS ? {} : {
        _containsPoint: function (p, closed) {
                var i, j, k, len, len2, dist, part,
-                       w = this.options.weight / 2;
+                   w = this.options.weight / 2;
 
                if (L.Browser.touch) {
                        w += 10; // polyline click tolerance on touch devices
@@ -5082,13 +5359,16 @@ L.Polyline.include(!L.Path.CANVAS ? {} : {
 });
 
 
+/*
+ * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
+ */
 
 L.Polygon.include(!L.Path.CANVAS ? {} : {
        _containsPoint: function (p) {
                var inside = false,
-                       part, p1, p2,
-                       i, j, k,
-                       len, len2;
+                   part, p1, p2,
+                   i, j, k,
+                   len, len2;
 
                // TODO optimization: check if within bounds first
 
@@ -5119,7 +5399,7 @@ L.Polygon.include(!L.Path.CANVAS ? {} : {
 
 
 /*
- * Circle canvas specific drawing parts.
+ * Extends L.Circle with Canvas-specific code.
  */
 
 L.Circle.include(!L.Path.CANVAS ? {} : {
@@ -5131,16 +5411,21 @@ L.Circle.include(!L.Path.CANVAS ? {} : {
 
        _containsPoint: function (p) {
                var center = this._point,
-                       w2 = this.options.stroke ? this.options.weight / 2 : 0;
+                   w2 = this.options.stroke ? this.options.weight / 2 : 0;
 
                return (p.distanceTo(center) <= this._radius + w2);
        }
 });
 
 
+/*
+ * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
+ */
+
 L.GeoJSON = L.FeatureGroup.extend({
+
        initialize: function (geojson, options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
 
                this._layers = {};
 
@@ -5150,12 +5435,15 @@ L.GeoJSON = L.FeatureGroup.extend({
        },
 
        addData: function (geojson) {
-               var features = geojson instanceof Array ? geojson : geojson.features,
+               var features = L.Util.isArray(geojson) ? geojson : geojson.features,
                    i, len;
 
                if (features) {
                        for (i = 0, len = features.length; i < len; i++) {
-                               this.addData(features[i]);
+                               // Only add this if geometry or geometries are set and not null
+                               if (features[i].geometries || features[i].geometry) {
+                                       this.addData(features[i]);
+                               }
                        }
                        return this;
                }
@@ -5167,6 +5455,7 @@ L.GeoJSON = L.FeatureGroup.extend({
                var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
                layer.feature = geojson;
 
+               layer.defaultOptions = layer.options;
                this.resetStyle(layer);
 
                if (options.onEachFeature) {
@@ -5179,6 +5468,9 @@ L.GeoJSON = L.FeatureGroup.extend({
        resetStyle: function (layer) {
                var style = this.options.style;
                if (style) {
+                       // reset any custom styles
+                       L.Util.extend(layer.options, layer.defaultOptions);
+
                        this._setLayerStyle(layer, style);
                }
        },
@@ -5199,7 +5491,7 @@ L.GeoJSON = L.FeatureGroup.extend({
        }
 });
 
-L.Util.extend(L.GeoJSON, {
+L.extend(L.GeoJSON, {
        geometryToLayer: function (geojson, pointToLayer) {
                var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
                    coords = geometry.coordinates,
@@ -5231,13 +5523,17 @@ L.Util.extend(L.GeoJSON, {
                        latlngs = this.coordsToLatLngs(coords, 1);
                        return new L.MultiPolyline(latlngs);
 
-               case "MultiPolygon":
+               case 'MultiPolygon':
                        latlngs = this.coordsToLatLngs(coords, 2);
                        return new L.MultiPolygon(latlngs);
 
-               case "GeometryCollection":
+               case 'GeometryCollection':
                        for (i = 0, len = geometry.geometries.length; i < len; i++) {
-                               layer = this.geometryToLayer(geometry.geometries[i], pointToLayer);
+                               layer = this.geometryToLayer({
+                                       geometry: geometry.geometries[i],
+                                       type: 'Feature',
+                                       properties: geojson.properties
+                               }, pointToLayer);
                                layers.push(layer);
                        }
                        return new L.FeatureGroup(layers);
@@ -5251,7 +5547,7 @@ L.Util.extend(L.GeoJSON, {
                var lat = parseFloat(coords[reverse ? 0 : 1]),
                    lng = parseFloat(coords[reverse ? 1 : 0]);
 
-               return new L.LatLng(lat, lng, true);
+               return new L.LatLng(lat, lng);
        },
 
        coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
@@ -5261,8 +5557,8 @@ L.Util.extend(L.GeoJSON, {
 
                for (i = 0, len = coords.length; i < len; i++) {
                        latlng = levelsDeep ?
-                                       this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
-                                       this.coordsToLatLng(coords[i], reverse);
+                               this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
+                               this.coordsToLatLng(coords[i], reverse);
 
                        latlngs.push(latlng);
                }
@@ -5281,12 +5577,12 @@ L.geoJson = function (geojson, options) {
  */
 
 L.DomEvent = {
-       /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
+       /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
        addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
 
-               var id = L.Util.stamp(fn),
-                       key = '_leaflet_' + type + id,
-                       handler, originalHandler, newType;
+               var id = L.stamp(fn),
+                   key = '_leaflet_' + type + id,
+                   handler, originalHandler, newType;
 
                if (obj[key]) { return this; }
 
@@ -5294,11 +5590,15 @@ L.DomEvent = {
                        return fn.call(context || obj, e || L.DomEvent._getEvent());
                };
 
+               if (L.Browser.msTouch && type.indexOf('touch') === 0) {
+                       return this.addMsTouchListener(obj, type, handler, id);
+               }
                if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
-                       return this.addDoubleTapListener(obj, handler, id);
+                       this.addDoubleTapListener(obj, handler, id);
+               }
+
+               if ('addEventListener' in obj) {
 
-               } else if ('addEventListener' in obj) {
-                       
                        if (type === 'mousewheel') {
                                obj.addEventListener('DOMMouseScroll', handler, false);
                                obj.addEventListener(type, handler, false);
@@ -5330,13 +5630,15 @@ L.DomEvent = {
 
        removeListener: function (obj, type, fn) {  // (HTMLElement, String, Function)
 
-               var id = L.Util.stamp(fn),
-                       key = '_leaflet_' + type + id,
-                       handler = obj[key];
+               var id = L.stamp(fn),
+                   key = '_leaflet_' + type + id,
+                   handler = obj[key];
 
                if (!handler) { return; }
 
-               if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
+               if (L.Browser.msTouch && type.indexOf('touch') === 0) {
+                       this.removeMsTouchListener(obj, type, id);
+               } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
                        this.removeDoubleTapListener(obj, id);
 
                } else if ('removeEventListener' in obj) {
@@ -5372,9 +5674,12 @@ L.DomEvent = {
        disableClickPropagation: function (el) {
 
                var stop = L.DomEvent.stopPropagation;
-               
+
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+                       L.DomEvent.addListener(el, L.Draggable.START[i], stop);
+               }
+
                return L.DomEvent
-                       .addListener(el, L.Draggable.START, stop)
                        .addListener(el, 'click', stop)
                        .addListener(el, 'dblclick', stop);
        },
@@ -5396,10 +5701,10 @@ L.DomEvent = {
        getMousePosition: function (e, container) {
 
                var body = document.body,
-                       docEl = document.documentElement,
-                       x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
-                       y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
-                       pos = new L.Point(x, y);
+                   docEl = document.documentElement,
+                   x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
+                   y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
+                   pos = new L.Point(x, y);
 
                return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
        },
@@ -5434,9 +5739,8 @@ L.DomEvent = {
                return (related !== el);
        },
 
-       /*jshint noarg:false */
        _getEvent: function () { // evil magic for IE
-
+               /*jshint noarg:false */
                var e = window.event;
                if (!e) {
                        var caller = arguments.callee.caller;
@@ -5450,12 +5754,12 @@ L.DomEvent = {
                }
                return e;
        }
-       /*jshint noarg:false */
 };
 
 L.DomEvent.on = L.DomEvent.addListener;
 L.DomEvent.off = L.DomEvent.removeListener;
 
+
 /*
  * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
  */
@@ -5464,39 +5768,51 @@ L.Draggable = L.Class.extend({
        includes: L.Mixin.Events,
 
        statics: {
-               START: L.Browser.touch ? 'touchstart' : 'mousedown',
-               END: L.Browser.touch ? 'touchend' : 'mouseup',
-               MOVE: L.Browser.touch ? 'touchmove' : 'mousemove',
+               START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
+               END: {
+                       mousedown: 'mouseup',
+                       touchstart: 'touchend',
+                       MSPointerDown: 'touchend'
+               },
+               MOVE: {
+                       mousedown: 'mousemove',
+                       touchstart: 'touchmove',
+                       MSPointerDown: 'touchmove'
+               },
                TAP_TOLERANCE: 15
        },
 
-       initialize: function (element, dragStartTarget) {
+       initialize: function (element, dragStartTarget, longPress) {
                this._element = element;
                this._dragStartTarget = dragStartTarget || element;
+               this._longPress = longPress && !L.Browser.msTouch;
        },
 
        enable: function () {
-               if (this._enabled) {
-                       return;
+               if (this._enabled) { return; }
+
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+                       L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
                }
-               L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
                this._enabled = true;
        },
 
        disable: function () {
-               if (!this._enabled) {
-                       return;
+               if (!this._enabled) { return; }
+
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+                       L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
                }
-               L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
                this._enabled = false;
                this._moved = false;
        },
 
        _onDown: function (e) {
                if ((!L.Browser.touch && e.shiftKey) ||
-                       ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
+                   ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
 
                L.DomEvent.preventDefault(e);
+               L.DomEvent.stopPropagation(e);
 
                if (L.Draggable._disabled) { return; }
 
@@ -5504,34 +5820,46 @@ L.Draggable = L.Class.extend({
 
                if (e.touches && e.touches.length > 1) {
                        this._simulateClick = false;
+                       clearTimeout(this._longPressTimeout);
                        return;
                }
 
                var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
-                       el = first.target;
+                   el = first.target;
 
                if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
                        L.DomUtil.addClass(el, 'leaflet-active');
                }
 
                this._moved = false;
-               if (this._moving) {
-                       return;
-               }
+               if (this._moving) { return; }
 
                this._startPoint = new L.Point(first.clientX, first.clientY);
                this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
 
-               L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
-               L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
+               //Touch contextmenu event emulation
+               if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) {
+                       this._longPressTimeout = setTimeout(L.bind(function () {
+                               var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
+
+                               if (dist < L.Draggable.TAP_TOLERANCE) {
+                                       this._simulateClick = false;
+                                       this._onUp();
+                                       this._simulateEvent('contextmenu', first);
+                               }
+                       }, this), 1000);
+               }
+
+               L.DomEvent.on(document, L.Draggable.MOVE[e.type], this._onMove, this);
+               L.DomEvent.on(document, L.Draggable.END[e.type], this._onUp, this);
        },
 
        _onMove: function (e) {
                if (e.touches && e.touches.length > 1) { return; }
 
                var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
-                       newPoint = new L.Point(first.clientX, first.clientY),
-                       diffVec = newPoint.subtract(this._startPoint);
+                   newPoint = new L.Point(first.clientX, first.clientY),
+                   diffVec = newPoint.subtract(this._startPoint);
 
                if (!diffVec.x && !diffVec.y) { return; }
 
@@ -5563,17 +5891,19 @@ L.Draggable = L.Class.extend({
        },
 
        _onUp: function (e) {
+               var simulateClickTouch;
+               clearTimeout(this._longPressTimeout);
                if (this._simulateClick && e.changedTouches) {
                        var first = e.changedTouches[0],
-                               el = first.target,
-                               dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
+                           el = first.target,
+                           dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
 
                        if (el.tagName.toLowerCase() === 'a') {
                                L.DomUtil.removeClass(el, 'leaflet-active');
                        }
 
                        if (dist < L.Draggable.TAP_TOLERANCE) {
-                               this._simulateEvent('click', first);
+                               simulateClickTouch = first;
                        }
                }
 
@@ -5582,8 +5912,12 @@ L.Draggable = L.Class.extend({
                        this._restoreCursor();
                }
 
-               L.DomEvent.off(document, L.Draggable.MOVE, this._onMove);
-               L.DomEvent.off(document, L.Draggable.END, this._onUp);
+               for (var i in L.Draggable.MOVE) {
+                       if (L.Draggable.MOVE.hasOwnProperty(i)) {
+                               L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove);
+                               L.DomEvent.off(document, L.Draggable.END[i], this._onUp);
+                       }
+               }
 
                if (this._moved) {
                        // ensure drag is not fired after dragend
@@ -5592,6 +5926,11 @@ L.Draggable = L.Class.extend({
                        this.fire('dragend');
                }
                this._moving = false;
+
+               if (simulateClickTouch) {
+                       this._moved = false;
+                       this._simulateEvent('click', simulateClickTouch);
+               }
        },
 
        _setMovingCursor: function () {
@@ -5606,10 +5945,10 @@ L.Draggable = L.Class.extend({
                var simulatedEvent = document.createEvent('MouseEvents');
 
                simulatedEvent.initMouseEvent(
-                               type, true, true, window, 1,
-                               e.screenX, e.screenY,
-                               e.clientX, e.clientY,
-                               false, false, false, false, 0, null);
+                       type, true, true, window, 1,
+                       e.screenX, e.screenY,
+                       e.clientX, e.clientY,
+                       false, false, false, false, 0, null);
 
                e.target.dispatchEvent(simulatedEvent);
        }
@@ -5617,8 +5956,9 @@ L.Draggable = L.Class.extend({
 
 
 /*
- * L.Handler classes are used internally to inject interaction features to classes like Map and Marker.
- */
+       L.Handler is a base class for handler classes that are used internally to inject
+       interaction features like dragging to classes like Map and Marker.
+*/
 
 L.Handler = L.Class.extend({
        initialize: function (map) {
@@ -5646,7 +5986,7 @@ L.Handler = L.Class.extend({
 
 
 /*
- * L.Handler.MapDrag is used internally by L.Map to make the map draggable.
+ * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
  */
 
 L.Map.mergeOptions({
@@ -5654,17 +5994,22 @@ L.Map.mergeOptions({
 
        inertia: !L.Browser.android23,
        inertiaDeceleration: 3400, // px/s^2
-       inertiaMaxSpeed: 6000, // px/s
+       inertiaMaxSpeed: Infinity, // px/s
        inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
+       easeLinearity: 0.25,
+
+       longPress: true,
 
        // TODO refactor, move to CRS
-       worldCopyJump: true
+       worldCopyJump: false
 });
 
 L.Map.Drag = L.Handler.extend({
        addHooks: function () {
                if (!this._draggable) {
-                       this._draggable = new L.Draggable(this._map._mapPane, this._map._container);
+                       var map = this._map;
+
+                       this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress);
 
                        this._draggable.on({
                                'dragstart': this._onDragStart,
@@ -5672,11 +6017,9 @@ L.Map.Drag = L.Handler.extend({
                                'dragend': this._onDragEnd
                        }, this);
 
-                       var options = this._map.options;
-
-                       if (options.worldCopyJump) {
+                       if (map.options.worldCopyJump) {
                                this._draggable.on('predrag', this._onPreDrag, this);
-                               this._map.on('viewreset', this._onViewReset, this);
+                               map.on('viewreset', this._onViewReset, this);
                        }
                }
                this._draggable.enable();
@@ -5698,8 +6041,8 @@ L.Map.Drag = L.Handler.extend({
                }
 
                map
-                       .fire('movestart')
-                       .fire('dragstart');
+                   .fire('movestart')
+                   .fire('dragstart');
 
                if (map.options.inertia) {
                        this._positions = [];
@@ -5722,13 +6065,14 @@ L.Map.Drag = L.Handler.extend({
                }
 
                this._map
-                       .fire('move')
-                       .fire('drag');
+                   .fire('move')
+                   .fire('drag');
        },
 
        _onViewReset: function () {
+               // TODO fix hardcoded Earth values
                var pxCenter = this._map.getSize()._divideBy(2),
-                       pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
+                   pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
 
                this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
                this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
@@ -5736,26 +6080,23 @@ L.Map.Drag = L.Handler.extend({
 
        _onPreDrag: function () {
                // TODO refactor to be able to adjust map pane position after zoom
-               var map = this._map,
-                       worldWidth = this._worldWidth,
-                       halfWidth = Math.round(worldWidth / 2),
-                       dx = this._initialWorldOffset,
-                       x = this._draggable._newPos.x,
-                       newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
-                       newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
-                       newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
+               var worldWidth = this._worldWidth,
+                   halfWidth = Math.round(worldWidth / 2),
+                   dx = this._initialWorldOffset,
+                   x = this._draggable._newPos.x,
+                   newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
+                   newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
+                   newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
 
                this._draggable._newPos.x = newX;
        },
 
        _onDragEnd: function () {
                var map = this._map,
-                       options = map.options,
-                       delay = +new Date() - this._lastTime,
+                   options = map.options,
+                   delay = +new Date() - this._lastTime,
 
-                       noInertia = !options.inertia ||
-                                       delay > options.inertiaThreshold ||
-                                       this._positions[0] === undefined;
+                   noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
 
                if (noInertia) {
                        map.fire('moveend');
@@ -5763,20 +6104,21 @@ L.Map.Drag = L.Handler.extend({
                } else {
 
                        var direction = this._lastPos.subtract(this._positions[0]),
-                               duration = (this._lastTime + delay - this._times[0]) / 1000,
+                           duration = (this._lastTime + delay - this._times[0]) / 1000,
+                           ease = options.easeLinearity,
 
-                               speedVector = direction.multiplyBy(0.58 / duration),
-                               speed = speedVector.distanceTo(new L.Point(0, 0)),
+                           speedVector = direction.multiplyBy(ease / duration),
+                           speed = speedVector.distanceTo(new L.Point(0, 0)),
 
-                               limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
-                               limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
+                           limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
+                           limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
 
-                               decelerationDuration = limitedSpeed / options.inertiaDeceleration,
-                               offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
+                           decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
+                           offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
 
-                       L.Util.requestAnimFrame(L.Util.bind(function () {
-                               this._map.panBy(offset, decelerationDuration);
-                       }, this));
+                       L.Util.requestAnimFrame(function () {
+                               map.panBy(offset, decelerationDuration, ease);
+                       });
                }
 
                map.fire('dragend');
@@ -5796,7 +6138,7 @@ L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
 
 
 /*
- * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
+ * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
  */
 
 L.Map.mergeOptions({
@@ -5819,12 +6161,13 @@ L.Map.DoubleClickZoom = L.Handler.extend({
 
 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
 
+
 /*
- * L.Handler.ScrollWheelZoom is used internally by L.Map to enable mouse scroll wheel zooming on the map.
+ * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
  */
 
 L.Map.mergeOptions({
-       scrollWheelZoom: !L.Browser.touch
+       scrollWheelZoom: true
 });
 
 L.Map.ScrollWheelZoom = L.Handler.extend({
@@ -5850,7 +6193,7 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
                var left = Math.max(40 - (+new Date() - this._startTime), 0);
 
                clearTimeout(this._timer);
-               this._timer = setTimeout(L.Util.bind(this._performZoom, this), left);
+               this._timer = setTimeout(L.bind(this._performZoom, this), left);
 
                L.DomEvent.preventDefault(e);
                L.DomEvent.stopPropagation(e);
@@ -5858,9 +6201,10 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
 
        _performZoom: function () {
                var map = this._map,
-                       delta = Math.round(this._delta),
-                       zoom = map.getZoom();
+                   delta = this._delta,
+                   zoom = map.getZoom();
 
+               delta = delta > 0 ? Math.ceil(delta) : Math.round(delta);
                delta = Math.max(Math.min(delta, 4), -4);
                delta = map._limitZoom(zoom + delta) - zoom;
 
@@ -5871,17 +6215,17 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
                if (!delta) { return; }
 
                var newZoom = zoom + delta,
-                       newCenter = this._getCenterForScrollWheelZoom(newZoom);
+                   newCenter = this._getCenterForScrollWheelZoom(newZoom);
 
                map.setView(newCenter, newZoom);
        },
 
        _getCenterForScrollWheelZoom: function (newZoom) {
                var map = this._map,
-                       scale = map.getZoomScale(newZoom),
-                       viewHalf = map.getSize()._divideBy(2),
-                       centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale),
-                       newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset);
+                   scale = map.getZoomScale(newZoom),
+                   viewHalf = map.getSize()._divideBy(2),
+                   centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale),
+                   newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset);
 
                return map.unproject(newCenterPoint);
        }
@@ -5890,31 +6234,72 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
 
 
-L.Util.extend(L.DomEvent, {
+/*
+ * Extends the event handling code with double tap support for mobile browsers.
+ */
+
+L.extend(L.DomEvent, {
+
+       _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
+       _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
+
        // inspired by Zepto touch code by Thomas Fuchs
        addDoubleTapListener: function (obj, handler, id) {
                var last,
-                       doubleTap = false,
-                       delay = 250,
-                       touch,
-                       pre = '_leaflet_',
-                       touchstart = 'touchstart',
-                       touchend = 'touchend';
+                   doubleTap = false,
+                   delay = 250,
+                   touch,
+                   pre = '_leaflet_',
+                   touchstart = this._touchstart,
+                   touchend = this._touchend,
+                   trackedTouches = [];
 
                function onTouchStart(e) {
-                       if (e.touches.length !== 1) {
+                       var count;
+                       if (L.Browser.msTouch) {
+                               trackedTouches.push(e.pointerId);
+                               count = trackedTouches.length;
+                       } else {
+                               count = e.touches.length;
+                       }
+                       if (count > 1) {
                                return;
                        }
 
                        var now = Date.now(),
                                delta = now - (last || now);
 
-                       touch = e.touches[0];
+                       touch = e.touches ? e.touches[0] : e;
                        doubleTap = (delta > 0 && delta <= delay);
                        last = now;
                }
+
                function onTouchEnd(e) {
+                       /*jshint forin:false */
+                       if (L.Browser.msTouch) {
+                               var idx = trackedTouches.indexOf(e.pointerId);
+                               if (idx === -1) {
+                                       return;
+                               }
+                               trackedTouches.splice(idx, 1);
+                       }
+
                        if (doubleTap) {
+                               if (L.Browser.msTouch) {
+                                       //Work around .type being readonly with MSPointer* events
+                                       var newTouch = { },
+                                               prop;
+
+                                       for (var i in touch) {
+                                               prop = touch[i];
+                                               if (typeof prop === 'function') {
+                                                       newTouch[i] = prop.bind(touch);
+                                               } else {
+                                                       newTouch[i] = prop;
+                                               }
+                                       }
+                                       touch = newTouch;
+                               }
                                touch.type = 'dblclick';
                                handler(touch);
                                last = null;
@@ -5923,22 +6308,180 @@ L.Util.extend(L.DomEvent, {
                obj[pre + touchstart + id] = onTouchStart;
                obj[pre + touchend + id] = onTouchEnd;
 
+               //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
+               // so we will lose track of how many touches are ongoing
+               var endElement = L.Browser.msTouch ? document.documentElement : obj;
+
                obj.addEventListener(touchstart, onTouchStart, false);
-               obj.addEventListener(touchend, onTouchEnd, false);
+               endElement.addEventListener(touchend, onTouchEnd, false);
+               if (L.Browser.msTouch) {
+                       endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
+               }
                return this;
        },
 
        removeDoubleTapListener: function (obj, id) {
                var pre = '_leaflet_';
-               obj.removeEventListener(obj, obj[pre + 'touchstart' + id], false);
-               obj.removeEventListener(obj, obj[pre + 'touchend' + id], false);
+               obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
+               (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(this._touchend, obj[pre + this._touchend + id], false);
+               if (L.Browser.msTouch) {
+                       document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
+               }
                return this;
        }
 });
 
 
 /*
- * L.Handler.TouchZoom is used internally by L.Map to add touch-zooming on Webkit-powered mobile browsers.
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
+L.extend(L.DomEvent, {
+
+       _msTouches: [],
+       _msDocumentListener: false,
+
+       // Provides a touch events wrapper for msPointer events.
+       // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
+
+       addMsTouchListener: function (obj, type, handler, id) {
+
+               switch (type) {
+               case 'touchstart':
+                       return this.addMsTouchListenerStart(obj, type, handler, id);
+               case 'touchend':
+                       return this.addMsTouchListenerEnd(obj, type, handler, id);
+               case 'touchmove':
+                       return this.addMsTouchListenerMove(obj, type, handler, id);
+               default:
+                       throw 'Unknown touch event type';
+               }
+       },
+
+       addMsTouchListenerStart: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._msTouches;
+
+               var cb = function (e) {
+
+                       var alreadyInArray = false;
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       alreadyInArray = true;
+                                       break;
+                               }
+                       }
+                       if (!alreadyInArray) {
+                               touches.push(e);
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchstart' + id] = cb;
+               obj.addEventListener('MSPointerDown', cb, false);
+
+               // need to also listen for end events to keep the _msTouches list accurate
+               // this needs to be on the body and never go away
+               if (!this._msDocumentListener) {
+                       var internalCb = function (e) {
+                               for (var i = 0; i < touches.length; i++) {
+                                       if (touches[i].pointerId === e.pointerId) {
+                                               touches.splice(i, 1);
+                                               break;
+                                       }
+                               }
+                       };
+                       //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
+                       document.documentElement.addEventListener('MSPointerUp', internalCb, false);
+                       document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
+
+                       this._msDocumentListener = true;
+               }
+
+               return this;
+       },
+
+       addMsTouchListenerMove: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._msTouches;
+
+               function cb(e) {
+
+                       // don't fire touch moves when mouse isn't down
+                       if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
+
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches[i] = e;
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               }
+
+               obj[pre + 'touchmove' + id] = cb;
+               obj.addEventListener('MSPointerMove', cb, false);
+
+               return this;
+       },
+
+       addMsTouchListenerEnd: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._msTouches;
+
+               var cb = function (e) {
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches.splice(i, 1);
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchend' + id] = cb;
+               obj.addEventListener('MSPointerUp', cb, false);
+               obj.addEventListener('MSPointerCancel', cb, false);
+
+               return this;
+       },
+
+       removeMsTouchListener: function (obj, type, id) {
+               var pre = '_leaflet_',
+                   cb = obj[pre + type + id];
+
+               switch (type) {
+               case 'touchstart':
+                       obj.removeEventListener('MSPointerDown', cb, false);
+                       break;
+               case 'touchmove':
+                       obj.removeEventListener('MSPointerMove', cb, false);
+                       break;
+               case 'touchend':
+                       obj.removeEventListener('MSPointerUp', cb, false);
+                       obj.removeEventListener('MSPointerCancel', cb, false);
+                       break;
+               }
+
+               return this;
+       }
+});
+
+
+/*
+ * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
  */
 
 L.Map.mergeOptions({
@@ -5960,8 +6503,8 @@ L.Map.TouchZoom = L.Handler.extend({
                if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
 
                var p1 = map.mouseEventToLayerPoint(e.touches[0]),
-                       p2 = map.mouseEventToLayerPoint(e.touches[1]),
-                       viewCenter = map._getCenterLayerPoint();
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]),
+                   viewCenter = map._getCenterLayerPoint();
 
                this._startCenter = p1.add(p2)._divideBy(2);
                this._startDist = p1.distanceTo(p2);
@@ -5976,8 +6519,8 @@ L.Map.TouchZoom = L.Handler.extend({
                }
 
                L.DomEvent
-                       .on(document, 'touchmove', this._onTouchMove, this)
-                       .on(document, 'touchend', this._onTouchEnd, this);
+                   .on(document, 'touchmove', this._onTouchMove, this)
+                   .on(document, 'touchend', this._onTouchEnd, this);
 
                L.DomEvent.preventDefault(e);
        },
@@ -5988,7 +6531,7 @@ L.Map.TouchZoom = L.Handler.extend({
                var map = this._map;
 
                var p1 = map.mouseEventToLayerPoint(e.touches[0]),
-                       p2 = map.mouseEventToLayerPoint(e.touches[1]);
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]);
 
                this._scale = p1.distanceTo(p2) / this._startDist;
                this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
@@ -5999,23 +6542,24 @@ L.Map.TouchZoom = L.Handler.extend({
                        L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
 
                        map
-                               .fire('movestart')
-                               .fire('zoomstart')
-                               ._prepareTileBg();
+                           .fire('movestart')
+                           .fire('zoomstart')
+                           ._prepareTileBg();
 
                        this._moved = true;
                }
 
                L.Util.cancelAnimFrame(this._animRequest);
-               this._animRequest = L.Util.requestAnimFrame(this._updateOnMove, this, true, this._map._container);
+               this._animRequest = L.Util.requestAnimFrame(
+                       this._updateOnMove, this, true, this._map._container);
 
                L.DomEvent.preventDefault(e);
        },
 
        _updateOnMove: function () {
                var map = this._map,
-                       origin = this._getScaleOrigin(),
-                       center = map.layerPointToLatLng(origin);
+                   origin = this._getScaleOrigin(),
+                   center = map.layerPointToLatLng(origin);
 
                map.fire('zoomanim', {
                        center: center,
@@ -6026,11 +6570,11 @@ L.Map.TouchZoom = L.Handler.extend({
                // it didn't count the origin on the first touch-zoom but worked correctly afterwards
 
                map._tileBg.style[L.DomUtil.TRANSFORM] =
-                       L.DomUtil.getTranslateString(this._delta) + ' ' +
-                       L.DomUtil.getScaleString(this._scale, this._startCenter);
+                       L.DomUtil.getTranslateString(this._delta) + ' ' +
+                       L.DomUtil.getScaleString(this._scale, this._startCenter);
        },
 
-       _onTouchEnd: function (e) {
+       _onTouchEnd: function () {
                if (!this._moved || !this._zooming) { return; }
 
                var map = this._map;
@@ -6039,16 +6583,18 @@ L.Map.TouchZoom = L.Handler.extend({
                L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
 
                L.DomEvent
-                       .off(document, 'touchmove', this._onTouchMove)
-                       .off(document, 'touchend', this._onTouchEnd);
+                   .off(document, 'touchmove', this._onTouchMove)
+                   .off(document, 'touchend', this._onTouchEnd);
 
                var origin = this._getScaleOrigin(),
-                       center = map.layerPointToLatLng(origin),
+                   center = map.layerPointToLatLng(origin),
 
-                       oldZoom = map.getZoom(),
-                       floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
-                       roundZoomDelta = (floatZoomDelta > 0 ? Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
-                       zoom = map._limitZoom(oldZoom + roundZoomDelta);
+                   oldZoom = map.getZoom(),
+                   floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
+                   roundZoomDelta = (floatZoomDelta > 0 ?
+                           Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
+
+                   zoom = map._limitZoom(oldZoom + roundZoomDelta);
 
                map.fire('zoomanim', {
                        center: center,
@@ -6068,7 +6614,8 @@ L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
 
 
 /*
- * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
+ * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
+  * (zoom to a selected bounding box), enabled by default.
  */
 
 L.Map.mergeOptions({
@@ -6104,29 +6651,29 @@ L.Map.BoxZoom = L.Handler.extend({
                this._container.style.cursor = 'crosshair';
 
                L.DomEvent
-                       .on(document, 'mousemove', this._onMouseMove, this)
-                       .on(document, 'mouseup', this._onMouseUp, this)
-                       .preventDefault(e);
-                       
+                   .on(document, 'mousemove', this._onMouseMove, this)
+                   .on(document, 'mouseup', this._onMouseUp, this)
+                   .preventDefault(e);
+
                this._map.fire("boxzoomstart");
        },
 
        _onMouseMove: function (e) {
                var startPoint = this._startLayerPoint,
-                       box = this._box,
+                   box = this._box,
 
-                       layerPoint = this._map.mouseEventToLayerPoint(e),
-                       offset = layerPoint.subtract(startPoint),
+                   layerPoint = this._map.mouseEventToLayerPoint(e),
+                   offset = layerPoint.subtract(startPoint),
 
-                       newPos = new L.Point(
-                               Math.min(layerPoint.x, startPoint.x),
-                               Math.min(layerPoint.y, startPoint.y));
+                   newPos = new L.Point(
+                       Math.min(layerPoint.x, startPoint.x),
+                       Math.min(layerPoint.y, startPoint.y));
 
                L.DomUtil.setPosition(box, newPos);
 
                // TODO refactor: remove hardcoded 4 pixels
-               box.style.width  = (Math.abs(offset.x) - 4) + 'px';
-               box.style.height = (Math.abs(offset.y) - 4) + 'px';
+               box.style.width  = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
+               box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
        },
 
        _onMouseUp: function (e) {
@@ -6136,18 +6683,20 @@ L.Map.BoxZoom = L.Handler.extend({
                L.DomUtil.enableTextSelection();
 
                L.DomEvent
-                       .off(document, 'mousemove', this._onMouseMove)
-                       .off(document, 'mouseup', this._onMouseUp);
+                   .off(document, 'mousemove', this._onMouseMove)
+                   .off(document, 'mouseup', this._onMouseUp);
 
                var map = this._map,
-                       layerPoint = map.mouseEventToLayerPoint(e);
+                   layerPoint = map.mouseEventToLayerPoint(e);
+
+               if (this._startLayerPoint.equals(layerPoint)) { return; }
 
                var bounds = new L.LatLngBounds(
-                               map.layerPointToLatLng(this._startLayerPoint),
-                               map.layerPointToLatLng(layerPoint));
+                       map.layerPointToLatLng(this._startLayerPoint),
+                       map.layerPointToLatLng(layerPoint));
 
                map.fitBounds(bounds);
-               
+
                map.fire("boxzoomend", {
                        boxZoomBounds: bounds
                });
@@ -6157,6 +6706,10 @@ L.Map.BoxZoom = L.Handler.extend({
 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
 
 
+/*
+ * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
+ */
+
 L.Map.mergeOptions({
        keyboard: true,
        keyboardPanOffset: 80,
@@ -6165,14 +6718,13 @@ L.Map.mergeOptions({
 
 L.Map.Keyboard = L.Handler.extend({
 
-       // list of e.keyCode values for particular actions
        keyCodes: {
                left:    [37],
                right:   [39],
                down:    [40],
                up:      [38],
                zoomIn:  [187, 107, 61],
-               zoomOut: [189, 109]
+               zoomOut: [189, 109, 173]
        },
 
        initialize: function (map) {
@@ -6191,27 +6743,28 @@ L.Map.Keyboard = L.Handler.extend({
                }
 
                L.DomEvent
-                       .addListener(container, 'focus', this._onFocus, this)
-                       .addListener(container, 'blur', this._onBlur, this)
-                       .addListener(container, 'mousedown', this._onMouseDown, this);
+                   .on(container, 'focus', this._onFocus, this)
+                   .on(container, 'blur', this._onBlur, this)
+                   .on(container, 'mousedown', this._onMouseDown, this);
 
                this._map
-                       .on('focus', this._addHooks, this)
-                       .on('blur', this._removeHooks, this);
+                   .on('focus', this._addHooks, this)
+                   .on('blur', this._removeHooks, this);
        },
 
        removeHooks: function () {
                this._removeHooks();
 
                var container = this._map._container;
+
                L.DomEvent
-                       .removeListener(container, 'focus', this._onFocus, this)
-                       .removeListener(container, 'blur', this._onBlur, this)
-                       .removeListener(container, 'mousedown', this._onMouseDown, this);
+                   .off(container, 'focus', this._onFocus, this)
+                   .off(container, 'blur', this._onBlur, this)
+                   .off(container, 'mousedown', this._onMouseDown, this);
 
                this._map
-                       .off('focus', this._addHooks, this)
-                       .off('blur', this._removeHooks, this);
+                   .off('focus', this._addHooks, this)
+                   .off('blur', this._removeHooks, this);
        },
 
        _onMouseDown: function () {
@@ -6251,7 +6804,7 @@ L.Map.Keyboard = L.Handler.extend({
 
        _setZoomOffset: function (zoom) {
                var keys = this._zoomKeys = {},
-                       codes = this.keyCodes,
+                   codes = this.keyCodes,
                    i, len;
 
                for (i = 0, len = codes.zoomIn.length; i < len; i++) {
@@ -6263,21 +6816,26 @@ L.Map.Keyboard = L.Handler.extend({
        },
 
        _addHooks: function () {
-               L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this);
+               L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
        },
 
        _removeHooks: function () {
-               L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this);
+               L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
        },
 
        _onKeyDown: function (e) {
-               var key = e.keyCode;
+               var key = e.keyCode,
+                   map = this._map;
 
                if (this._panKeys.hasOwnProperty(key)) {
-                       this._map.panBy(this._panKeys[key]);
+                       map.panBy(this._panKeys[key]);
+
+                       if (map.options.maxBounds) {
+                               map.panInsideBounds(map.options.maxBounds);
+                       }
 
                } else if (this._zoomKeys.hasOwnProperty(key)) {
-                       this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]);
+                       map.setZoom(map.getZoom() + this._zoomKeys[key]);
 
                } else {
                        return;
@@ -6303,9 +6861,9 @@ L.Handler.MarkerDrag = L.Handler.extend({
                var icon = this._marker._icon;
                if (!this._draggable) {
                        this._draggable = new L.Draggable(icon, icon)
-                               .on('dragstart', this._onDragStart, this)
-                               .on('drag', this._onDrag, this)
-                               .on('dragend', this._onDragEnd, this);
+                           .on('dragstart', this._onDragStart, this)
+                           .on('drag', this._onDrag, this)
+                           .on('dragend', this._onDragEnd, this);
                }
                this._draggable.enable();
        },
@@ -6318,18 +6876,18 @@ L.Handler.MarkerDrag = L.Handler.extend({
                return this._draggable && this._draggable._moved;
        },
 
-       _onDragStart: function (e) {
+       _onDragStart: function () {
                this._marker
-                       .closePopup()
-                       .fire('movestart')
-                       .fire('dragstart');
+                   .closePopup()
+                   .fire('movestart')
+                   .fire('dragstart');
        },
 
-       _onDrag: function (e) {
+       _onDrag: function () {
                var marker = this._marker,
-                       shadow = marker._shadow,
-                       iconPos = L.DomUtil.getPosition(marker._icon),
-                       latlng = marker._map.layerPointToLatLng(iconPos);
+                   shadow = marker._shadow,
+                   iconPos = L.DomUtil.getPosition(marker._icon),
+                   latlng = marker._map.layerPointToLatLng(iconPos);
 
                // update shadow position
                if (shadow) {
@@ -6339,18 +6897,22 @@ L.Handler.MarkerDrag = L.Handler.extend({
                marker._latlng = latlng;
 
                marker
-                       .fire('move', { latlng: latlng })
-                       .fire('drag');
+                   .fire('move', {latlng: latlng})
+                   .fire('drag');
        },
 
        _onDragEnd: function () {
                this._marker
-                       .fire('moveend')
-                       .fire('dragend');
+                   .fire('moveend')
+                   .fire('dragend');
        }
 });
 
 
+/*
+ * L.Handler.PolyEdit is an editing handler for polylines and polygons.
+ */
+
 L.Handler.PolyEdit = L.Handler.extend({
        options: {
                icon: new L.DivIcon({
@@ -6361,7 +6923,7 @@ L.Handler.PolyEdit = L.Handler.extend({
 
        initialize: function (poly, options) {
                this._poly = poly;
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
        },
 
        addHooks: function () {
@@ -6443,7 +7005,7 @@ L.Handler.PolyEdit = L.Handler.extend({
        _onMarkerDrag: function (e) {
                var marker = e.target;
 
-               L.Util.extend(marker._origLatLng, marker._latlng);
+               L.extend(marker._origLatLng, marker._latlng);
 
                if (marker._middleLeft) {
                        marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
@@ -6456,36 +7018,40 @@ L.Handler.PolyEdit = L.Handler.extend({
        },
 
        _onMarkerClick: function (e) {
-               // Default action on marker click is to remove that marker, but if we remove the marker when latlng count < 3, we don't have a valid polyline anymore
-               if (this._poly._latlngs.length < 3) {
-                       return;
-               }
+               // we want to remove the marker on click, but if latlng count < 3, polyline would be invalid
+               if (this._poly._latlngs.length < 3) { return; }
 
                var marker = e.target,
                    i = marker._index;
 
-               // Check existence of previous and next markers since they wouldn't exist for edge points on the polyline
-               if (marker._prev && marker._next) {
-                       this._createMiddleMarker(marker._prev, marker._next);
-               } else if (!marker._prev) {
-                       marker._next._middleLeft = null;
-               } else if (!marker._next) {
-                       marker._prev._middleRight = null;
-               }
+               // remove the marker
+               this._markerGroup.removeLayer(marker);
+               this._markers.splice(i, 1);
+               this._poly.spliceLatLngs(i, 1);
+               this._updateIndexes(i, -1);
+
+               // update prev/next links of adjacent markers
                this._updatePrevNext(marker._prev, marker._next);
 
-               // The marker itself is guaranteed to exist and present in the layer, since we managed to click on it
-               this._markerGroup.removeLayer(marker);
-               // Check for the existence of middle left or middle right
+               // remove ghost markers near the removed marker
                if (marker._middleLeft) {
                        this._markerGroup.removeLayer(marker._middleLeft);
                }
                if (marker._middleRight) {
                        this._markerGroup.removeLayer(marker._middleRight);
                }
-               this._markers.splice(i, 1);
-               this._poly.spliceLatLngs(i, 1);
-               this._updateIndexes(i, -1);
+
+               // create a ghost marker in place of the removed one
+               if (marker._prev && marker._next) {
+                       this._createMiddleMarker(marker._prev, marker._next);
+
+               } else if (!marker._prev) {
+                       marker._next._middleLeft = null;
+
+               } else if (!marker._next) {
+                       marker._prev._middleRight = null;
+               }
+
                this._poly.fire('edit');
        },
 
@@ -6499,10 +7065,10 @@ L.Handler.PolyEdit = L.Handler.extend({
 
        _createMiddleMarker: function (marker1, marker2) {
                var latlng = this._getMiddleLatLng(marker1, marker2),
-                       marker = this._createMarker(latlng),
-                       onClick,
-                       onDragStart,
-                       onDragEnd;
+                   marker = this._createMarker(latlng),
+                   onClick,
+                   onDragStart,
+                   onDragEnd;
 
                marker.setOpacity(0.6);
 
@@ -6514,8 +7080,8 @@ L.Handler.PolyEdit = L.Handler.extend({
                        marker._index = i;
 
                        marker
-                               .off('click', onClick)
-                               .on('click', this._onMarkerClick, this);
+                           .off('click', onClick)
+                           .on('click', this._onMarkerClick, this);
 
                        latlng.lat = marker.getLatLng().lat;
                        latlng.lng = marker.getLatLng().lng;
@@ -6545,9 +7111,9 @@ L.Handler.PolyEdit = L.Handler.extend({
                };
 
                marker
-                       .on('click', onClick, this)
-                       .on('dragstart', onDragStart, this)
-                       .on('dragend', onDragEnd, this);
+                   .on('click', onClick, this)
+                   .on('dragstart', onDragStart, this)
+                   .on('dragend', onDragEnd, this);
 
                this._markerGroup.addLayer(marker);
        },
@@ -6570,15 +7136,42 @@ L.Handler.PolyEdit = L.Handler.extend({
        }
 });
 
+L.Polyline.addInitHook(function () {
+
+       if (L.Handler.PolyEdit) {
+               this.editing = new L.Handler.PolyEdit(this);
+
+               if (this.options.editable) {
+                       this.editing.enable();
+               }
+       }
+
+       this.on('add', function () {
+               if (this.editing && this.editing.enabled()) {
+                       this.editing.addHooks();
+               }
+       });
+
+       this.on('remove', function () {
+               if (this.editing && this.editing.enabled()) {
+                       this.editing.removeHooks();
+               }
+       });
+});
 
 
+/*
+ * L.Control is a base class for implementing map controls. Handles positioning.
+ * All other controls extend from this class.
+ */
+
 L.Control = L.Class.extend({
        options: {
                position: 'topright'
        },
 
        initialize: function (options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
        },
 
        getPosition: function () {
@@ -6606,7 +7199,7 @@ L.Control = L.Class.extend({
 
                var container = this._container = this.onAdd(map),
                    pos = this.getPosition(),
-                       corner = map._controlCorners[pos];
+                   corner = map._controlCorners[pos];
 
                L.DomUtil.addClass(container, 'leaflet-control');
 
@@ -6621,7 +7214,7 @@ L.Control = L.Class.extend({
 
        removeFrom: function (map) {
                var pos = this.getPosition(),
-                       corner = map._controlCorners[pos];
+                   corner = map._controlCorners[pos];
 
                corner.removeChild(this._container);
                this._map = null;
@@ -6639,6 +7232,10 @@ L.control = function (options) {
 };
 
 
+/*
+ * Adds control-related methods to L.Map.
+ */
+
 L.Map.include({
        addControl: function (control) {
                control.addTo(this);
@@ -6654,13 +7251,12 @@ L.Map.include({
                var corners = this._controlCorners = {},
                    l = 'leaflet-',
                    container = this._controlContainer =
-                               L.DomUtil.create('div', l + 'control-container', this._container);
+                           L.DomUtil.create('div', l + 'control-container', this._container);
 
                function createCorner(vSide, hSide) {
                        var className = l + vSide + ' ' + l + hSide;
 
-                       corners[vSide + hSide] =
-                                       L.DomUtil.create('div', className, container);
+                       corners[vSide + hSide] = L.DomUtil.create('div', className, container);
                }
 
                createCorner('top', 'left');
@@ -6671,23 +7267,44 @@ L.Map.include({
 });
 
 
+/*
+ * L.Control.Zoom is used for the default zoom buttons on the map.
+ */
+
 L.Control.Zoom = L.Control.extend({
        options: {
                position: 'topleft'
        },
 
        onAdd: function (map) {
-               var className = 'leaflet-control-zoom',
-                   container = L.DomUtil.create('div', className);
+               var zoomName = 'leaflet-control-zoom',
+                   barName = 'leaflet-bar',
+                   partName = barName + '-part',
+                   container = L.DomUtil.create('div', zoomName + ' ' + barName);
 
                this._map = map;
 
-               this._createButton('Zoom in', className + '-in', container, this._zoomIn, this);
-               this._createButton('Zoom out', className + '-out', container, this._zoomOut, this);
+               this._zoomInButton = this._createButton('+', 'Zoom in',
+                       zoomName + '-in ' +
+                       partName + ' ' +
+                       partName + '-top',
+                       container, this._zoomIn,  this);
+
+               this._zoomOutButton = this._createButton('-', 'Zoom out',
+                       zoomName + '-out ' +
+                       partName + ' ' +
+                       partName + '-bottom',
+                       container, this._zoomOut, this);
+
+               map.on('zoomend', this._updateDisabled, this);
 
                return container;
        },
 
+       onRemove: function (map) {
+               map.off('zoomend', this._updateDisabled, this);
+       },
+
        _zoomIn: function (e) {
                this._map.zoomIn(e.shiftKey ? 3 : 1);
        },
@@ -6696,19 +7313,37 @@ L.Control.Zoom = L.Control.extend({
                this._map.zoomOut(e.shiftKey ? 3 : 1);
        },
 
-       _createButton: function (title, className, container, fn, context) {
+       _createButton: function (html, title, className, container, fn, context) {
                var link = L.DomUtil.create('a', className, container);
+               link.innerHTML = html;
                link.href = '#';
                link.title = title;
 
+               var stop = L.DomEvent.stopPropagation;
+
                L.DomEvent
-                       .on(link, 'click', L.DomEvent.stopPropagation)
-                       .on(link, 'mousedown', L.DomEvent.stopPropagation)
-                       .on(link, 'dblclick', L.DomEvent.stopPropagation)
-                       .on(link, 'click', L.DomEvent.preventDefault)
-                       .on(link, 'click', fn, context);
+                   .on(link, 'click', stop)
+                   .on(link, 'mousedown', stop)
+                   .on(link, 'dblclick', stop)
+                   .on(link, 'click', L.DomEvent.preventDefault)
+                   .on(link, 'click', fn, context);
 
                return link;
+       },
+
+       _updateDisabled: function () {
+               var map = this._map,
+                       className = 'leaflet-control-zoom-disabled';
+
+               L.DomUtil.removeClass(this._zoomInButton, className);
+               L.DomUtil.removeClass(this._zoomOutButton, className);
+
+               if (map._zoom === map.getMinZoom()) {
+                       L.DomUtil.addClass(this._zoomOutButton, className);
+               }
+               if (map._zoom === map.getMaxZoom()) {
+                       L.DomUtil.addClass(this._zoomInButton, className);
+               }
        }
 });
 
@@ -6729,14 +7364,18 @@ L.control.zoom = function (options) {
 
 
 
+/*
+ * L.Control.Attribution is used for displaying attribution on the map (added by default).
+ */
+
 L.Control.Attribution = L.Control.extend({
        options: {
                position: 'bottomright',
-               prefix: 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>'
+               prefix: 'Powered by <a href="http://leafletjs.com">Leaflet</a>'
        },
 
        initialize: function (options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
 
                this._attributions = {};
        },
@@ -6746,8 +7385,8 @@ L.Control.Attribution = L.Control.extend({
                L.DomEvent.disableClickPropagation(this._container);
 
                map
-                       .on('layeradd', this._onLayerAdd, this)
-                       .on('layerremove', this._onLayerRemove, this);
+                   .on('layeradd', this._onLayerAdd, this)
+                   .on('layerremove', this._onLayerRemove, this);
 
                this._update();
 
@@ -6756,8 +7395,8 @@ L.Control.Attribution = L.Control.extend({
 
        onRemove: function (map) {
                map
-                       .off('layeradd', this._onLayerAdd)
-                       .off('layerremove', this._onLayerRemove);
+                   .off('layeradd', this._onLayerAdd)
+                   .off('layerremove', this._onLayerRemove);
 
        },
 
@@ -6840,6 +7479,10 @@ L.control.attribution = function (options) {
 };
 
 
+/*
+ * L.Control.Scale is used for displaying metric/imperial scale on the map.
+ */
+
 L.Control.Scale = L.Control.extend({
        options: {
                position: 'bottomleft',
@@ -6913,8 +7556,8 @@ L.Control.Scale = L.Control.extend({
 
        _updateImperial: function (maxMeters) {
                var maxFeet = maxMeters * 3.2808399,
-                       scale = this._iScale,
-                       maxMiles, miles, feet;
+                   scale = this._iScale,
+                   maxMiles, miles, feet;
 
                if (maxFeet > 5280) {
                        maxMiles = maxFeet / 5280;
@@ -6950,6 +7593,10 @@ L.control.scale = function (options) {
 };
 
 
+/*
+ * L.Control.Layers is a control to allow users to switch between different layers on the map.
+ */
+
 L.Control.Layers = L.Control.extend({
        options: {
                collapsed: true,
@@ -6958,10 +7605,11 @@ L.Control.Layers = L.Control.extend({
        },
 
        initialize: function (baseLayers, overlays, options) {
-               L.Util.setOptions(this, options);
+               L.setOptions(this, options);
 
                this._layers = {};
                this._lastZIndex = 0;
+               this._handlingClick = false;
 
                for (var i in baseLayers) {
                        if (baseLayers.hasOwnProperty(i)) {
@@ -6980,9 +7628,19 @@ L.Control.Layers = L.Control.extend({
                this._initLayout();
                this._update();
 
+               map
+                   .on('layeradd', this._onLayerChange, this)
+                   .on('layerremove', this._onLayerChange, this);
+
                return this._container;
        },
 
+       onRemove: function (map) {
+               map
+                   .off('layeradd', this._onLayerChange)
+                   .off('layerremove', this._onLayerChange);
+       },
+
        addBaseLayer: function (layer, name) {
                this._addLayer(layer, name);
                this._update();
@@ -6996,7 +7654,7 @@ L.Control.Layers = L.Control.extend({
        },
 
        removeLayer: function (layer) {
-               var id = L.Util.stamp(layer);
+               var id = L.stamp(layer);
                delete this._layers[id];
                this._update();
                return this;
@@ -7008,6 +7666,7 @@ L.Control.Layers = L.Control.extend({
 
                if (!L.Browser.touch) {
                        L.DomEvent.disableClickPropagation(container);
+                       L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
                } else {
                        L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
                }
@@ -7016,8 +7675,8 @@ L.Control.Layers = L.Control.extend({
 
                if (this.options.collapsed) {
                        L.DomEvent
-                               .on(container, 'mouseover', this._expand, this)
-                               .on(container, 'mouseout', this._collapse, this);
+                           .on(container, 'mouseover', this._expand, this)
+                           .on(container, 'mouseout', this._collapse, this);
 
                        var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
                        link.href = '#';
@@ -7025,9 +7684,9 @@ L.Control.Layers = L.Control.extend({
 
                        if (L.Browser.touch) {
                                L.DomEvent
-                                       .on(link, 'click', L.DomEvent.stopPropagation)
-                                       .on(link, 'click', L.DomEvent.preventDefault)
-                                       .on(link, 'click', this._expand, this);
+                                   .on(link, 'click', L.DomEvent.stopPropagation)
+                                   .on(link, 'click', L.DomEvent.preventDefault)
+                                   .on(link, 'click', this._expand, this);
                        }
                        else {
                                L.DomEvent.on(link, 'focus', this._expand, this);
@@ -7047,7 +7706,7 @@ L.Control.Layers = L.Control.extend({
        },
 
        _addLayer: function (layer, name, overlay) {
-               var id = L.Util.stamp(layer);
+               var id = L.stamp(layer);
 
                this._layers[id] = {
                        layer: layer,
@@ -7070,7 +7729,7 @@ L.Control.Layers = L.Control.extend({
                this._overlaysList.innerHTML = '';
 
                var baseLayersPresent = false,
-                       overlaysPresent = false;
+                   overlaysPresent = false;
 
                for (var i in this._layers) {
                        if (this._layers.hasOwnProperty(i)) {
@@ -7084,6 +7743,14 @@ L.Control.Layers = L.Control.extend({
                this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
        },
 
+       _onLayerChange: function (e) {
+               var id = L.stamp(e.layer);
+
+               if (this._layers[id] && !this._handlingClick) {
+                       this._update();
+               }
+       },
+
        // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
        _createRadioElement: function (name, checked) {
 
@@ -7113,7 +7780,7 @@ L.Control.Layers = L.Control.extend({
                        input = this._createRadioElement('leaflet-base-layers', checked);
                }
 
-               input.layerId = L.Util.stamp(obj.layer);
+               input.layerId = L.stamp(obj.layer);
 
                L.DomEvent.on(input, 'click', this._onInputClick, this);
 
@@ -7125,13 +7792,17 @@ L.Control.Layers = L.Control.extend({
 
                var container = obj.overlay ? this._overlaysList : this._baseLayersList;
                container.appendChild(label);
+
+               return label;
        },
 
        _onInputClick: function () {
                var i, input, obj,
-                       inputs = this._form.getElementsByTagName('input'),
-                       inputsLen = inputs.length,
-                       baseLayer;
+                   inputs = this._form.getElementsByTagName('input'),
+                   inputsLen = inputs.length,
+                   baseLayer;
+
+               this._handlingClick = true;
 
                for (i = 0; i < inputsLen; i++) {
                        input = inputs[i];
@@ -7148,8 +7819,11 @@ L.Control.Layers = L.Control.extend({
                }
 
                if (baseLayer) {
+                       this._map.setZoom(this._map.getZoom());
                        this._map.fire('baselayerchange', {layer: baseLayer});
                }
+
+               this._handlingClick = false;
        },
 
        _expand: function () {
@@ -7173,7 +7847,7 @@ L.control.layers = function (baseLayers, overlays, options) {
 L.PosAnimation = L.Class.extend({
        includes: L.Mixin.Events,
 
-       run: function (el, newPos, duration, easing) { // (HTMLElement, Point[, Number, String])
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
                this.stop();
 
                this._el = el;
@@ -7181,7 +7855,8 @@ L.PosAnimation = L.Class.extend({
 
                this.fire('start');
 
-               el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) + 's ' + (easing || 'ease-out');
+               el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
+                       's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
 
                L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
                L.DomUtil.setPosition(el, newPos);
@@ -7189,8 +7864,8 @@ L.PosAnimation = L.Class.extend({
                // toggle reflow, Chrome flickers for some reason if you don't do this
                L.Util.falseFn(el.offsetWidth);
 
-               // there's no native way to track value updates of tranisitioned properties, so we imitate this
-               this._stepTimer = setInterval(L.Util.bind(this.fire, this, 'step'), 50);
+               // there's no native way to track value updates of transitioned properties, so we imitate this
+               this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50);
        },
 
        stop: function () {
@@ -7201,6 +7876,7 @@ L.PosAnimation = L.Class.extend({
 
                L.DomUtil.setPosition(this._el, this._getPos());
                this._onTransitionEnd();
+               L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
        },
 
        // you can't easily get intermediate values of properties animated with CSS3 Transitions,
@@ -7210,8 +7886,8 @@ L.PosAnimation = L.Class.extend({
 
        _getPos: function () {
                var left, top, matches,
-                       el = this._el,
-                       style = window.getComputedStyle(el);
+                   el = this._el,
+                   style = window.getComputedStyle(el);
 
                if (L.Browser.any3d) {
                        matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
@@ -7241,6 +7917,9 @@ L.PosAnimation = L.Class.extend({
 });
 
 
+/*
+ * Extends L.Map to handle panning animations.
+ */
 
 L.Map.include({
 
@@ -7256,8 +7935,8 @@ L.Map.include({
                        }
 
                        var done = (zoomChanged ?
-                                       this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
-                                       this._panByIfClose(center));
+                               this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
+                               this._panByIfClose(center));
 
                        // exit if animated pan or zoom started
                        if (done) {
@@ -7272,7 +7951,7 @@ L.Map.include({
                return this;
        },
 
-       panBy: function (offset, duration) {
+       panBy: function (offset, duration, easeLinearity) {
                offset = L.point(offset);
 
                if (!(offset.x || offset.y)) {
@@ -7292,8 +7971,8 @@ L.Map.include({
 
                L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
 
-               var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset);
-               this._panAnim.run(this._mapPane, newPos, duration || 0.25);
+               var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset)._round();
+               this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
 
                return this;
        },
@@ -7320,10 +7999,10 @@ L.Map.include({
 
        _offsetIsWithinView: function (offset, multiplyFactor) {
                var m = multiplyFactor || 1,
-                       size = this.getSize();
+                   size = this.getSize();
 
                return (Math.abs(offset.x) <= size.x * m) &&
-                               (Math.abs(offset.y) <= size.y * m);
+                      (Math.abs(offset.y) <= size.y * m);
        }
 });
 
@@ -7335,13 +8014,13 @@ L.Map.include({
 
 L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
 
-       run: function (el, newPos, duration, easing) { // (HTMLElement, Point[, Number, String])
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
                this.stop();
 
                this._el = el;
                this._inProgress = true;
                this._duration = duration || 0.25;
-               this._ease = this._easings[easing || 'ease-out'];
+               this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
 
                this._startPos = L.DomUtil.getPosition(el);
                this._offset = newPos.subtract(this._startPos);
@@ -7367,10 +8046,10 @@ L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
 
        _step: function () {
                var elapsed = (+new Date()) - this._startTime,
-                       duration = this._duration * 1000;
+                   duration = this._duration * 1000;
 
                if (elapsed < duration) {
-                       this._runFrame(this._ease(elapsed / duration));
+                       this._runFrame(this._easeOut(elapsed / duration));
                } else {
                        this._runFrame(1);
                        this._complete();
@@ -7391,15 +8070,16 @@ L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
                this.fire('end');
        },
 
-       // easing functions, they map time progress to movement progress
-       _easings: {
-               'ease-out': function (t) { return t * (2 - t); },
-               'linear':   function (t) { return t; }
+       _easeOut: function (t) {
+               return 1 - Math.pow(1 - t, this._easeOutPower);
        }
-
 });
 
 
+/*
+ * Extends L.Map to handle zoom animations.
+ */
+
 L.Map.mergeOptions({
        zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
 });
@@ -7419,7 +8099,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
                if (!this.options.zoomAnimation) { return false; }
 
                var scale = this.getZoomScale(zoom),
-                       offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
+                   offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
 
                // if offset does not exceed half of the view
                if (!this._offsetIsWithinView(offset, 1)) { return false; }
@@ -7427,8 +8107,8 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
                L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
 
                this
-                       .fire('movestart')
-                       .fire('zoomstart');
+                   .fire('movestart')
+                   .fire('zoomstart');
 
                this.fire('zoomanim', {
                        center: center,
@@ -7443,7 +8123,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
                return true;
        },
 
-       _catchTransitionEnd: function (e) {
+       _catchTransitionEnd: function () {
                if (this._animatingZoom) {
                        this._onZoomTransitionEnd();
                }
@@ -7459,28 +8139,27 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
                }
 
                var transform = L.DomUtil.TRANSFORM,
-                       tileBg = this._tileBg;
+                   tileBg = this._tileBg;
 
                clearTimeout(this._clearTileBgTimer);
 
                L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
 
                var scaleStr = L.DomUtil.getScaleString(scale, origin),
-                       oldTransform = tileBg.style[transform];
+                   oldTransform = tileBg.style[transform];
 
                tileBg.style[transform] = backwardsTransform ?
-                       oldTransform + ' ' + scaleStr :
-                       scaleStr + ' ' + oldTransform;
+                       oldTransform + ' ' + scaleStr :
+                       scaleStr + ' ' + oldTransform;
        },
 
        _prepareTileBg: function () {
                var tilePane = this._tilePane,
-                       tileBg = this._tileBg;
+                   tileBg = this._tileBg;
 
                // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
-               if (tileBg &&
-                               this._getLoadedTilesPercentage(tileBg) > 0.5 &&
-                               this._getLoadedTilesPercentage(tilePane) < 0.5) {
+               if (tileBg && this._getLoadedTilesPercentage(tileBg) > 0.5 &&
+                                 this._getLoadedTilesPercentage(tilePane) < 0.5) {
 
                        tilePane.style.visibility = 'hidden';
                        tilePane.empty = true;
@@ -7512,7 +8191,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
 
        _getLoadedTilesPercentage: function (container) {
                var tiles = container.getElementsByTagName('img'),
-                       i, len, count = 0;
+                   i, len, count = 0;
 
                for (i = 0, len = tiles.length; i < len; i++) {
                        if (tiles[i].complete) {
@@ -7525,7 +8204,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
        // stops loading all tiles in the background layer
        _stopLoadingImages: function (container) {
                var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
-                       i, len, tile;
+                   i, len, tile;
 
                for (i = 0, len = tiles.length; i < len; i++) {
                        tile = tiles[i];
@@ -7542,11 +8221,11 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
 
        _onZoomTransitionEnd: function () {
                this._restoreTileFront();
-               L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
-               this._resetView(this._animateToCenter, this._animateToZoom, true, true);
 
                L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+               L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
                this._animatingZoom = false;
+               this._resetView(this._animateToCenter, this._animateToZoom, true, true);
 
                if (L.Draggable) {
                        L.Draggable._disabled = false;
@@ -7569,7 +8248,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
 
 
 /*
- * Provides L.Map with convenient shortcuts for W3C geolocation.
+ * Provides L.Map with convenient shortcuts for using browser geolocation features.
  */
 
 L.Map.include({
@@ -7584,7 +8263,7 @@ L.Map.include({
 
        locate: function (/*Object*/ options) {
 
-               options = this._locationOptions = L.Util.extend(this._defaultLocateOptions, options);
+               options = this._locationOptions = L.extend(this._defaultLocateOptions, options);
 
                if (!navigator.geolocation) {
                        this._handleGeolocationError({
@@ -7594,11 +8273,12 @@ L.Map.include({
                        return this;
                }
 
-               var onResponse = L.Util.bind(this._handleGeolocationResponse, this),
-                       onError = L.Util.bind(this._handleGeolocationError, this);
+               var onResponse = L.bind(this._handleGeolocationResponse, this),
+                       onError = L.bind(this._handleGeolocationError, this);
 
                if (options.watch) {
-                       this._locationWatchId = navigator.geolocation.watchPosition(onResponse, onError, options);
+                       this._locationWatchId =
+                               navigator.geolocation.watchPosition(onResponse, onError, options);
                } else {
                        navigator.geolocation.getCurrentPosition(onResponse, onError, options);
                }
@@ -7614,9 +8294,9 @@ L.Map.include({
 
        _handleGeolocationError: function (error) {
                var c = error.code,
-                       message = error.message ||
-                               (c === 1 ? "permission denied" :
-                               (c === 2 ? "position unavailable" : "timeout"));
+                   message = error.message ||
+                           (c === 1 ? "permission denied" :
+                           (c === 2 ? "position unavailable" : "timeout"));
 
                if (this._locationOptions.setView && !this._loaded) {
                        this.fitWorld();
@@ -7630,17 +8310,17 @@ L.Map.include({
 
        _handleGeolocationResponse: function (pos) {
                var latAccuracy = 180 * pos.coords.accuracy / 4e7,
-                       lngAccuracy = latAccuracy * 2,
+                   lngAccuracy = latAccuracy * 2,
 
-                       lat = pos.coords.latitude,
-                       lng = pos.coords.longitude,
-                       latlng = new L.LatLng(lat, lng),
+                   lat = pos.coords.latitude,
+                   lng = pos.coords.longitude,
+                   latlng = new L.LatLng(lat, lng),
 
-                       sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
-                       ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
-                       bounds = new L.LatLngBounds(sw, ne),
+                   sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
+                   ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
+                   bounds = new L.LatLngBounds(sw, ne),
 
-                       options = this._locationOptions;
+                   options = this._locationOptions;
 
                if (options.setView) {
                        var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
@@ -7656,6 +8336,4 @@ L.Map.include({
 });
 
 
-
-
-}(this));
\ No newline at end of file
+}(this, document));
\ No newline at end of file