]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/leaflet/leaflet.js
Fix edit links on browse and changeset pages
[rails.git] / vendor / assets / leaflet / leaflet.js
index 5503592a0d2ec2bce818ab281060a2ac5bf79603..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://leafletjs.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,11 +23,11 @@ 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 = {
@@ -103,14 +105,14 @@ L.Util = {
                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) {
@@ -123,6 +125,10 @@ L.Util = {
                });
        },
 
+       isArray: function (obj) {
+               return (Object.prototype.toString.call(obj) === '[object Array]');
+       },
+
        emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
 };
 
@@ -186,18 +192,26 @@ 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
@@ -236,6 +250,25 @@ L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
        // mix given properties into the prototype
        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;
 };
 
@@ -245,13 +278,26 @@ L.Class.include = function (props) {
        L.extend(this.prototype, props);
 };
 
+// merge new default options to the Class
 L.Class.mergeOptions = function (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';
@@ -352,26 +398,29 @@ 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 ie = !!window.ActiveXObject,
-           // http://tanalin.com/en/articles/ie-version-js/
            ie6 = ie && !window.XMLHttpRequest,
            ie7 = ie && !document.querySelector,
 
            // terrible browser detection to work around Safari / iOS / Android browser bugs
-           // see TileLayer._addTile and debug/hacks/jitter.html
-
            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,
+           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)").matches)),
+           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),
@@ -411,6 +460,7 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
 
 
        L.Browser = {
+               ie: ie,
                ie6: ie6,
                ie7: ie7,
                webkit: webkit,
@@ -526,6 +576,11 @@ L.Point.prototype = {
                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) + ', ' +
@@ -537,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)) {
@@ -551,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({
-
-       initialize: function (a, b) {   //(Point, Point) or Point[]
-               if (!a) { return; }
+L.Bounds = 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);
@@ -593,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;
 
@@ -631,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) {
@@ -645,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);
        }
-});
+};
 
 
 /*
@@ -711,6 +769,11 @@ L.DomUtil = {
                do {
                        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; }
@@ -776,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;
                }
        },
@@ -897,8 +960,11 @@ L.DomUtil = {
 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
         ['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' ?
@@ -906,10 +972,10 @@ L.DomUtil.TRANSITION_END =
 
 
 /*
      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);
 
@@ -917,11 +983,6 @@ L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
                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;
 };
@@ -938,17 +999,21 @@ 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) + ')';
        },
 
        // 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);
 
@@ -964,20 +1029,31 @@ L.LatLng.prototype = {
                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);
 };
 
 
@@ -986,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);
@@ -1007,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);
@@ -1050,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
@@ -1112,7 +1188,7 @@ L.LatLngBounds = L.Class.extend({
        isValid: function () {
                return !!(this._southWest && this._northEast);
        }
-});
+};
 
 //TODO International date line?
 
@@ -1131,6 +1207,9 @@ 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,
@@ -1152,12 +1231,14 @@ L.Projection.SphericalMercator = {
                    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) {
@@ -1165,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
@@ -1196,13 +1280,24 @@ 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)
+       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.extend({}, L.CRS, {
        code: 'EPSG:3857',
@@ -1222,6 +1317,9 @@ L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
 });
 
 
+/*
+ * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
+ */
 
 L.CRS.EPSG4326 = L.extend({}, L.CRS, {
        code: 'EPSG:4326',
@@ -1258,7 +1356,7 @@ L.Map = L.Class.extend({
 
                this._initContainer(id);
                this._initLayout();
-               this._initHooks();
+               this.callInitHooks();
                this._initEvents();
 
                if (options.maxBounds) {
@@ -1381,11 +1479,9 @@ L.Map = L.Class.extend({
                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!!!
@@ -1411,6 +1507,10 @@ L.Map = L.Class.extend({
                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)) {
@@ -1648,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) {
@@ -1697,19 +1796,11 @@ L.Map = L.Class.extend({
                return L.DomUtil.create('div', className, container || this._panes.objectsPane);
        },
 
-       _initializers: [],
-
-       _initHooks: function () {
-               var i, len;
-               for (i = 0, len = this._initializers.length; i < len; i++) {
-                       this._initializers[i].call(this);
-               }
-       },
-
        _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;
@@ -1768,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
 
@@ -1888,21 +2003,15 @@ L.Map = L.Class.extend({
        }
 });
 
-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,
@@ -1952,7 +2061,7 @@ L.Projection.Mercator = {
                        phi += dphi;
                }
 
-               return new L.LatLng(phi * d, lng, true);
+               return new L.LatLng(phi * d, lng);
        }
 };
 
@@ -2056,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,
@@ -2141,8 +2250,8 @@ 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
+               var layers = pane.children,
+                   edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
                    zIndex, i, len;
 
                for (i = 0, len = layers.length; i < len; i++) {
@@ -2219,7 +2328,7 @@ L.TileLayer = L.Class.extend({
                this._initContainer();
        },
 
-       _update: function (e) {
+       _update: function () {
 
                if (!this._map) { return; }
 
@@ -2440,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);
@@ -2465,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
@@ -2481,7 +2589,7 @@ L.TileLayer = L.Class.extend({
                layer._tileLoaded();
        },
 
-       _tileOnError: function (e) {
+       _tileOnError: function () {
                var layer = this._layer;
 
                layer.fire('tileerror', {
@@ -2503,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: {
@@ -2549,6 +2661,8 @@ 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,
@@ -2563,7 +2677,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
 
                    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) {
@@ -2583,6 +2697,11 @@ 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
@@ -2628,7 +2747,7 @@ L.TileLayer.Canvas = L.TileLayer.extend({
                }
        },
 
-       drawTile: function (tile, tilePoint) {
+       drawTile: function (/*tile, tilePoint*/) {
                // override with rendering code
        },
 
@@ -2643,6 +2762,10 @@ L.tileLayer.canvas = function (options) {
 };
 
 
+/*
+ * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
+ */
+
 L.ImageOverlay = L.Class.extend({
        includes: L.Mixin.Events,
 
@@ -2742,8 +2865,7 @@ L.ImageOverlay = L.Class.extend({
 
                    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 + ') ';
@@ -2752,7 +2874,7 @@ L.ImageOverlay = L.Class.extend({
        _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);
 
@@ -2774,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, 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)
                */
@@ -2859,6 +2987,9 @@ L.Icon = L.Class.extend({
        },
 
        _getIconUrl: function (name) {
+               if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
+                       return this.options[name + 'RetinaUrl'];
+               }
                return this.options[name + 'Url'];
        }
 });
@@ -2868,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({
 
@@ -2886,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) {
@@ -2977,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) {
@@ -2996,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 () {
@@ -3094,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);
@@ -3119,20 +3264,31 @@ L.Marker = L.Class.extend({
 
        _onMouseClick: function (e) {
                var wasDragged = this.dragging && this.dragging.moved();
+
                if (this.hasEventListeners(e.type) || wasDragged) {
                        L.DomEvent.stopPropagation(e);
                }
+
                if (wasDragged) { return; }
-               if (this._map.dragging && this._map.dragging.moved()) { 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);
                }
@@ -3166,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
@@ -3205,6 +3366,9 @@ L.divIcon = function (options) {
 };
 
 
+/*
+ * L.Popup is used for displaying popups on the map.
+ */
 
 L.Map.mergeOptions({
        closePopupOnClick: true
@@ -3221,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.setOptions(this, options);
 
                this._source = source;
+               this._animated = L.Browser.any3d && this.options.zoomAnimation;
        },
 
        onAdd: function (map) {
@@ -3247,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);
                }
 
@@ -3316,7 +3482,8 @@ L.Popup = L.Class.extend({
 
        _initLayout: function () {
                var prefix = 'leaflet-popup',
-                       containerClass = 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;
 
@@ -3402,15 +3569,15 @@ L.Popup = L.Class.extend({
                if (!this._map) { return; }
 
                var pos = this._map.latLngToLayerPoint(this._latlng),
-                   is3d = L.Browser.any3d,
+                   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';
@@ -3432,7 +3599,7 @@ L.Popup = L.Class.extend({
 
                    layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
 
-               if (L.Browser.any3d) {
+               if (this._animated) {
                        layerPos._add(L.DomUtil.getPosition(this._container));
                }
 
@@ -3472,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({
@@ -3533,6 +3700,9 @@ L.Marker.include({
 });
 
 
+/*
+ * Adds popup-related methods to L.Map.
+ */
 
 L.Map.include({
        openPopup: function (popup) {
@@ -3555,7 +3725,8 @@ 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({
@@ -3638,6 +3809,10 @@ L.LayerGroup = L.Class.extend({
                                method.call(context, this._layers[i]);
                        }
                }
+       },
+
+       setZIndex: function (zIndex) {
+               return this.invoke('setZIndex', zIndex);
        }
 });
 
@@ -3647,7 +3822,8 @@ 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({
@@ -3667,7 +3843,7 @@ L.FeatureGroup = L.LayerGroup.extend({
                L.LayerGroup.prototype.addLayer.call(this, layer);
 
                if (this._popupContent && layer.bindPopup) {
-                       layer.bindPopup(this._popupContent);
+                       layer.bindPopup(this._popupContent, this._popupOptions);
                }
 
                return this.fire('layeradd', {layer: layer});
@@ -3686,9 +3862,10 @@ L.FeatureGroup = L.LayerGroup.extend({
                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) {
@@ -3775,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
@@ -3789,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) {
@@ -3797,8 +3978,6 @@ L.Path = L.Class.extend({
                        this._fill = null;
                }
 
-               this.fire('remove');
-
                map.off({
                        'viewreset': this.projectLatlngs,
                        'moveend': this._updatePath
@@ -3841,6 +4020,10 @@ L.Map.include({
 });
 
 
+/*
+ * 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);
@@ -4002,13 +4185,12 @@ L.Map.include({
                }
        },
 
-       _animatePathZoom: function (opt) {
-               var scale = this.getZoomScale(opt.zoom),
-                   offset = this._getCenterOffset(opt.center),
-                   translate = offset.multiplyBy(-scale)._add(this._pathViewport.min);
+       _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(translate) + ' scale(' + scale + ') ';
+                       L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
 
                this._pathZooming = true;
        },
@@ -4054,7 +4236,7 @@ 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({
@@ -4082,7 +4264,7 @@ L.Path.include({
                if (this._popup) {
                        this._popup = null;
                        this
-                           .off('click', this.openPopup)
+                           .off('click', this._openPopup)
                            .off('remove', this.closePopup);
 
                        this._popupHandlersAdded = false;
@@ -4193,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;
@@ -4210,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;
@@ -4277,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;
@@ -4344,16 +4535,12 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
                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();
                }
 
@@ -4372,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
+                       });
                }
        }
 });
@@ -4426,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.
@@ -4517,16 +4711,11 @@ 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,
-
-                   codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
+               var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
                    codeB = this._getBitCode(b, bounds),
 
                    codeOut, p, newCode;
@@ -4592,8 +4781,6 @@ 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,
@@ -4630,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: {
@@ -4682,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();
@@ -4722,27 +4904,10 @@ L.Polyline = L.Path.extend({
                return bounds;
        },
 
-       // 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();
-               }
-       },
-
-       onRemove: function (map) {
-               if (this.editing && this.editing.enabled()) {
-                       this.editing.removeHooks();
-               }
-
-               L.Path.prototype.onRemove.call(this, map);
-       },
-
        _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]);
@@ -4826,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 = {};
 
@@ -4838,9 +5003,7 @@ 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,
+       var clippedPoints,
            edges = [1, 4, 2, 8],
            i, j, k,
            a, b,
@@ -4883,8 +5046,6 @@ L.PolyUtil.clipPolygon = function (points, bounds) {
        return points;
 };
 
-/*jshint bitwise:true */
-
 
 /*
  * L.Polygon is used to display polygons on a map.
@@ -4898,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);
                }
@@ -4913,7 +5074,7 @@ L.Polygon = L.Polyline.extend({
 
                if (!this._holes) { return; }
 
-               var i, j, len, len2, hole;
+               var i, j, len, len2;
 
                for (i = 0, len = this._holes.length; i < len; i++) {
                        this._holePoints[i] = [];
@@ -5003,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({
@@ -5021,8 +5182,7 @@ L.Rectangle = L.Polygon.extend({
                        latLngBounds.getSouthWest(),
                        latLngBounds.getNorthWest(),
                        latLngBounds.getNorthEast(),
-                       latLngBounds.getSouthEast(),
-                       latLngBounds.getSouthWest()
+                       latLngBounds.getSouthEast()
                ];
        }
 });
@@ -5060,7 +5220,7 @@ L.Circle = L.Path.extend({
 
        projectLatlngs: function () {
                var lngRadius = this._getLngRadius(),
-                   latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
+                   latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius),
                    point2 = this._map.latLngToLayerPoint(latlng2);
 
                this._point = this._map.latLngToLayerPoint(this._latlng);
@@ -5150,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;
@@ -5162,6 +5327,9 @@ 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) {
@@ -5191,6 +5359,9 @@ 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) {
@@ -5228,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 ? {} : {
@@ -5247,7 +5418,12 @@ L.Circle.include(!L.Path.CANVAS ? {} : {
 });
 
 
+/*
+ * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
+ */
+
 L.GeoJSON = L.FeatureGroup.extend({
+
        initialize: function (geojson, options) {
                L.setOptions(this, options);
 
@@ -5259,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;
                }
@@ -5276,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) {
@@ -5288,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);
                }
        },
@@ -5340,13 +5523,17 @@ L.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);
@@ -5360,7 +5547,7 @@ L.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
@@ -5390,7 +5577,7 @@ 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.stamp(fn),
@@ -5405,10 +5592,12 @@ L.DomEvent = {
 
                if (L.Browser.msTouch && type.indexOf('touch') === 0) {
                        return this.addMsTouchListener(obj, type, handler, id);
-               } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
-                       return this.addDoubleTapListener(obj, handler, id);
+               }
+               if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
+                       this.addDoubleTapListener(obj, handler, id);
+               }
 
-               } else if ('addEventListener' in obj) {
+               if ('addEventListener' in obj) {
 
                        if (type === 'mousewheel') {
                                obj.addEventListener('DOMMouseScroll', handler, false);
@@ -5486,8 +5675,11 @@ L.DomEvent = {
 
                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);
        },
@@ -5547,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;
@@ -5563,7 +5754,6 @@ L.DomEvent = {
                }
                return e;
        }
-       /*jshint noarg:false */
 };
 
 L.DomEvent.on = L.DomEvent.addListener;
@@ -5578,9 +5768,17 @@ 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
        },
 
@@ -5593,14 +5791,18 @@ L.Draggable = L.Class.extend({
        enable: function () {
                if (this._enabled) { return; }
 
-               L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+                       L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+               }
                this._enabled = true;
        },
 
        disable: function () {
                if (!this._enabled) { return; }
 
-               L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+                       L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+               }
                this._enabled = false;
                this._moved = false;
        },
@@ -5648,8 +5850,8 @@ L.Draggable = L.Class.extend({
                        }, this), 1000);
                }
 
-               L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
-               L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
+               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) {
@@ -5710,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
@@ -5750,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) {
@@ -5779,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({
@@ -5794,7 +6001,7 @@ L.Map.mergeOptions({
        longPress: true,
 
        // TODO refactor, move to CRS
-       worldCopyJump: true
+       worldCopyJump: false
 });
 
 L.Map.Drag = L.Handler.extend({
@@ -5863,6 +6070,7 @@ L.Map.Drag = L.Handler.extend({
        },
 
        _onViewReset: function () {
+               // TODO fix hardcoded Earth values
                var pxCenter = this._map.getSize()._divideBy(2),
                    pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
 
@@ -5872,8 +6080,7 @@ 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,
+               var worldWidth = this._worldWidth,
                    halfWidth = Math.round(worldWidth / 2),
                    dx = this._initialWorldOffset,
                    x = this._draggable._newPos.x,
@@ -5889,9 +6096,7 @@ L.Map.Drag = L.Handler.extend({
                    options = map.options,
                    delay = +new Date() - this._lastTime,
 
-                   noInertia = !options.inertia ||
-                           delay > options.inertiaThreshold ||
-                           !this._positions[0];
+                   noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
 
                if (noInertia) {
                        map.fire('moveend');
@@ -5900,18 +6105,19 @@ L.Map.Drag = L.Handler.extend({
 
                        var direction = this._lastPos.subtract(this._positions[0]),
                            duration = (this._lastTime + delay - this._times[0]) / 1000,
+                           ease = options.easeLinearity,
 
-                           speedVector = direction.multiplyBy(options.easeLinearity / duration),
+                           speedVector = direction.multiplyBy(ease / duration),
                            speed = speedVector.distanceTo(new L.Point(0, 0)),
 
                            limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
                            limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
 
-                           decelerationDuration = limitedSpeed / (options.inertiaDeceleration * options.easeLinearity),
+                           decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
                            offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
 
                        L.Util.requestAnimFrame(function () {
-                               map.panBy(offset, decelerationDuration, options.easeLinearity);
+                               map.panBy(offset, decelerationDuration, ease);
                        });
                }
 
@@ -5932,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({
@@ -5955,12 +6161,13 @@ L.Map.DoubleClickZoom = L.Handler.extend({
 
 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
 
+
 /*
  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
  */
 
 L.Map.mergeOptions({
-       scrollWheelZoom: !L.Browser.touch || L.Browser.msTouch
+       scrollWheelZoom: true
 });
 
 L.Map.ScrollWheelZoom = L.Handler.extend({
@@ -5994,9 +6201,10 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
 
        _performZoom: function () {
                var map = this._map,
-                   delta = Math.round(this._delta),
+                   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;
 
@@ -6026,6 +6234,10 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
 
 
+/*
+ * Extends the event handling code with double tap support for mobile browsers.
+ */
+
 L.extend(L.DomEvent, {
 
        _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
@@ -6061,7 +6273,9 @@ L.extend(L.DomEvent, {
                        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) {
@@ -6075,14 +6289,13 @@ L.extend(L.DomEvent, {
                                        //Work around .type being readonly with MSPointer* events
                                        var newTouch = { },
                                                prop;
+
                                        for (var i in touch) {
-                                               if (true) { //Make JSHint happy, we want to copy all properties
-                                                       prop = touch[i];
-                                                       if (typeof prop === 'function') {
-                                                               newTouch[i] = prop.bind(touch);
-                                                       } else {
-                                                               newTouch[i] = prop;
-                                                       }
+                                               prop = touch[i];
+                                               if (typeof prop === 'function') {
+                                                       newTouch[i] = prop.bind(touch);
+                                               } else {
+                                                       newTouch[i] = prop;
                                                }
                                        }
                                        touch = newTouch;
@@ -6119,6 +6332,10 @@ L.extend(L.DomEvent, {
 });
 
 
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
 L.extend(L.DomEvent, {
 
        _msTouches: [],
@@ -6357,7 +6574,7 @@ L.Map.TouchZoom = L.Handler.extend({
                        L.DomUtil.getScaleString(this._scale, this._startCenter);
        },
 
-       _onTouchEnd: function (e) {
+       _onTouchEnd: function () {
                if (!this._moved || !this._zooming) { return; }
 
                var map = this._map;
@@ -6397,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({
@@ -6469,9 +6687,11 @@ L.Map.BoxZoom = L.Handler.extend({
                    .off(document, 'mouseup', this._onMouseUp);
 
                var map = this._map,
-                   layerPoint = map.mouseEventToLayerPoint(e),
+                   layerPoint = map.mouseEventToLayerPoint(e);
+
+               if (this._startLayerPoint.equals(layerPoint)) { return; }
 
-                   bounds = new L.LatLngBounds(
+               var bounds = new L.LatLngBounds(
                        map.layerPointToLatLng(this._startLayerPoint),
                        map.layerPointToLatLng(layerPoint));
 
@@ -6486,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,
@@ -6494,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) {
@@ -6520,9 +6743,9 @@ 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)
@@ -6535,9 +6758,9 @@ L.Map.Keyboard = L.Handler.extend({
                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)
@@ -6593,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;
@@ -6648,14 +6876,14 @@ L.Handler.MarkerDrag = L.Handler.extend({
                return this._draggable && this._draggable._moved;
        },
 
-       _onDragStart: function (e) {
+       _onDragStart: function () {
                this._marker
                    .closePopup()
                    .fire('movestart')
                    .fire('dragstart');
        },
 
-       _onDrag: function (e) {
+       _onDrag: function () {
                var marker = this._marker,
                    shadow = marker._shadow,
                    iconPos = L.DomUtil.getPosition(marker._icon),
@@ -6681,6 +6909,10 @@ L.Handler.MarkerDrag = L.Handler.extend({
 });
 
 
+/*
+ * L.Handler.PolyEdit is an editing handler for polylines and polygons.
+ */
+
 L.Handler.PolyEdit = L.Handler.extend({
        options: {
                icon: new L.DivIcon({
@@ -6904,7 +7136,34 @@ 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: {
@@ -6973,6 +7232,10 @@ L.control = function (options) {
 };
 
 
+/*
+ * Adds control-related methods to L.Map.
+ */
+
 L.Map.include({
        addControl: function (control) {
                control.addTo(this);
@@ -7004,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);
        },
@@ -7029,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', 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);
+               }
        }
 });
 
@@ -7062,6 +7364,10 @@ 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',
@@ -7173,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',
@@ -7283,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,
@@ -7315,16 +7629,16 @@ L.Control.Layers = L.Control.extend({
                this._update();
 
                map
-                   .on('layeradd', this._update, this)
-                   .on('layerremove', this._update, this);
+                   .on('layeradd', this._onLayerChange, this)
+                   .on('layerremove', this._onLayerChange, this);
 
                return this._container;
        },
 
        onRemove: function (map) {
                map
-                   .off('layeradd', this._update)
-                   .off('layerremove', this._update);
+                   .off('layeradd', this._onLayerChange)
+                   .off('layerremove', this._onLayerChange);
        },
 
        addBaseLayer: function (layer, name) {
@@ -7352,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);
                }
@@ -7406,7 +7721,7 @@ L.Control.Layers = L.Control.extend({
        },
 
        _update: function () {
-               if (!this._container || this._handlingClick) {
+               if (!this._container) {
                        return;
                }
 
@@ -7428,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) {
 
@@ -7469,6 +7792,8 @@ L.Control.Layers = L.Control.extend({
 
                var container = obj.overlay ? this._overlaysList : this._baseLayersList;
                container.appendChild(label);
+
+               return label;
        },
 
        _onInputClick: function () {
@@ -7494,6 +7819,7 @@ L.Control.Layers = L.Control.extend({
                }
 
                if (baseLayer) {
+                       this._map.setZoom(this._map.getZoom());
                        this._map.fire('baselayerchange', {layer: baseLayer});
                }
 
@@ -7538,7 +7864,7 @@ 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
+               // 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);
        },
 
@@ -7591,6 +7917,9 @@ L.PosAnimation = L.Class.extend({
 });
 
 
+/*
+ * Extends L.Map to handle panning animations.
+ */
 
 L.Map.include({
 
@@ -7642,7 +7971,7 @@ L.Map.include({
 
                L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
 
-               var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset);
+               var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset)._round();
                this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
 
                return this;
@@ -7747,6 +8076,10 @@ L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
 });
 
 
+/*
+ * Extends L.Map to handle zoom animations.
+ */
+
 L.Map.mergeOptions({
        zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
 });
@@ -7790,7 +8123,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
                return true;
        },
 
-       _catchTransitionEnd: function (e) {
+       _catchTransitionEnd: function () {
                if (this._animatingZoom) {
                        this._onZoomTransitionEnd();
                }
@@ -7888,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;
@@ -7915,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({
@@ -8003,6 +8336,4 @@ L.Map.include({
 });
 
 
-
-
-}(this));
+}(this, document));
\ No newline at end of file