Update to leaflet 1.1.0
authorTom Hughes <tom@compton.nu>
Tue, 27 Jun 2017 18:22:03 +0000 (19:22 +0100)
committerTom Hughes <tom@compton.nu>
Tue, 27 Jun 2017 18:22:23 +0000 (19:22 +0100)
Vendorfile
vendor/assets/leaflet/leaflet.css
vendor/assets/leaflet/leaflet.js

index 2a17ef8..502f8b5 100644 (file)
@@ -11,13 +11,13 @@ folder 'vendor/assets' do
   end
 
   folder 'leaflet' do
-    file 'leaflet.js', 'https://unpkg.com/leaflet@1.0.3/dist/leaflet-src.js'
-    file 'leaflet.css', 'https://unpkg.com/leaflet@1.0.3/dist/leaflet.css'
+    file 'leaflet.js', 'https://unpkg.com/leaflet@1.1.0/dist/leaflet-src.js'
+    file 'leaflet.css', 'https://unpkg.com/leaflet@1.1.0/dist/leaflet.css'
 
     [ 'layers.png', 'layers-2x.png',
       'marker-icon.png', 'marker-icon-2x.png',
       'marker-shadow.png' ].each do |image|
-      file "images/#{image}", "https://unpkg.com/leaflet@1.0.3/dist/images/#{image}"
+      file "images/#{image}", "https://unpkg.com/leaflet@1.1.0/dist/images/#{image}"
     end
 
     from 'git://github.com/aratcliffe/Leaflet.contextmenu.git', :tag => 'v1.2.1' do
index 72998d0..41126ab 100644 (file)
        -ms-touch-action: none;
        touch-action: none;
 }
+.leaflet-container {
+       -webkit-tap-highlight-color: transparent;
+}
+.leaflet-container a {
+       -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
+}
 .leaflet-tile {
        filter: inherit;
        visibility: hidden;
        height: 30px;
        line-height: 30px;
        }
-
+.leaflet-touch .leaflet-bar a:first-child {
+       border-top-left-radius: 2px;
+       border-top-right-radius: 2px;
+       }
+.leaflet-touch .leaflet-bar a:last-child {
+       border-bottom-left-radius: 2px;
+       border-bottom-right-radius: 2px;
+       }
 
 /* zoom control */
 
        font: bold 18px 'Lucida Console', Monaco, monospace;
        text-indent: 1px;
        }
-.leaflet-control-zoom-out {
-       font-size: 20px;
-       }
 
-.leaflet-touch .leaflet-control-zoom-in {
+.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out  {
        font-size: 22px;
        }
-.leaflet-touch .leaflet-control-zoom-out {
-       font-size: 24px;
-       }
 
 
 /* layers control */
index e366062..5e6ab2b 100644 (file)
@@ -1,38 +1,14 @@
 /*
- Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com
- (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
-*/
-(function (window, document, undefined) {
-var L = {
-       version: "1.0.3"
-};
-
-function expose() {
-       var oldL = window.L;
-
-       L.noConflict = function () {
-               window.L = oldL;
-               return this;
-       };
-
-       window.L = L;
-}
-
-// define Leaflet for Node module pattern loaders, including Browserify
-if (typeof module === 'object' && typeof module.exports === 'object') {
-       module.exports = L;
-
-// define Leaflet as an AMD module
-} else if (typeof define === 'function' && define.amd) {
-       define(L);
-}
-
-// define Leaflet as a global L variable, saving the original L to restore later if needed
-if (typeof window !== 'undefined') {
-       expose();
-}
-
+ * Leaflet 1.1.0, a JS library for interactive maps. http://leafletjs.com
+ * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+ */
+(function (global, factory) {
+       typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+       typeof define === 'function' && define.amd ? define(['exports'], factory) :
+       (factory((global.L = global.L || {})));
+}(this, (function (exports) { 'use strict';
 
+var version = "1.1.0";
 
 /*
  * @namespace Util
@@ -40,253 +16,263 @@ if (typeof window !== 'undefined') {
  * Various utility functions, used by Leaflet internally.
  */
 
-L.Util = {
-
-       // @function extend(dest: Object, src?: Object): Object
-       // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
-       extend: function (dest) {
-               var i, j, len, src;
+// @function extend(dest: Object, src?: Object): Object
+// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
+function extend(dest) {
+       var i, j, len, src;
 
-               for (j = 1, len = arguments.length; j < len; j++) {
-                       src = arguments[j];
-                       for (i in src) {
-                               dest[i] = src[i];
-                       }
+       for (j = 1, len = arguments.length; j < len; j++) {
+               src = arguments[j];
+               for (i in src) {
+                       dest[i] = src[i];
                }
-               return dest;
-       },
-
-       // @function create(proto: Object, properties?: Object): Object
-       // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
-       create: Object.create || (function () {
-               function F() {}
-               return function (proto) {
-                       F.prototype = proto;
-                       return new F();
-               };
-       })(),
+       }
+       return dest;
+}
 
-       // @function bind(fn: Function, …): Function
-       // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
-       // Has a `L.bind()` shortcut.
-       bind: function (fn, obj) {
-               var slice = Array.prototype.slice;
+// @function create(proto: Object, properties?: Object): Object
+// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
+var create = Object.create || (function () {
+       function F() {}
+       return function (proto) {
+               F.prototype = proto;
+               return new F();
+       };
+})();
 
-               if (fn.bind) {
-                       return fn.bind.apply(fn, slice.call(arguments, 1));
-               }
+// @function bind(fn: Function, …): Function
+// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
+// Has a `L.bind()` shortcut.
+function bind(fn, obj) {
+       var slice = Array.prototype.slice;
 
-               var args = slice.call(arguments, 2);
+       if (fn.bind) {
+               return fn.bind.apply(fn, slice.call(arguments, 1));
+       }
 
-               return function () {
-                       return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
-               };
-       },
+       var args = slice.call(arguments, 2);
 
-       // @function stamp(obj: Object): Number
-       // Returns the unique ID of an object, assiging it one if it doesn't have it.
-       stamp: function (obj) {
-               /*eslint-disable */
-               obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
-               return obj._leaflet_id;
-               /*eslint-enable */
-       },
-
-       // @property lastId: Number
-       // Last unique ID used by [`stamp()`](#util-stamp)
-       lastId: 0,
-
-       // @function throttle(fn: Function, time: Number, context: Object): Function
-       // Returns a function which executes function `fn` with the given scope `context`
-       // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
-       // `fn` will be called no more than one time per given amount of `time`. The arguments
-       // received by the bound function will be any arguments passed when binding the
-       // function, followed by any arguments passed when invoking the bound function.
-       // Has an `L.bind` shortcut.
-       throttle: function (fn, time, context) {
-               var lock, args, wrapperFn, later;
-
-               later = function () {
-                       // reset lock and call if queued
-                       lock = false;
-                       if (args) {
-                               wrapperFn.apply(context, args);
-                               args = false;
-                       }
-               };
+       return function () {
+               return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
+       };
+}
 
-               wrapperFn = function () {
-                       if (lock) {
-                               // called too soon, queue to call later
-                               args = arguments;
+// @property lastId: Number
+// Last unique ID used by [`stamp()`](#util-stamp)
+var lastId = 0;
+
+// @function stamp(obj: Object): Number
+// Returns the unique ID of an object, assiging it one if it doesn't have it.
+function stamp(obj) {
+       /*eslint-disable */
+       obj._leaflet_id = obj._leaflet_id || ++lastId;
+       return obj._leaflet_id;
+       /*eslint-enable */
+}
 
-                       } else {
-                               // call and lock until later
-                               fn.apply(context, arguments);
-                               setTimeout(later, time);
-                               lock = true;
-                       }
-               };
+// @function throttle(fn: Function, time: Number, context: Object): Function
+// Returns a function which executes function `fn` with the given scope `context`
+// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
+// `fn` will be called no more than one time per given amount of `time`. The arguments
+// received by the bound function will be any arguments passed when binding the
+// function, followed by any arguments passed when invoking the bound function.
+// Has an `L.throttle` shortcut.
+function throttle(fn, time, context) {
+       var lock, args, wrapperFn, later;
+
+       later = function () {
+               // reset lock and call if queued
+               lock = false;
+               if (args) {
+                       wrapperFn.apply(context, args);
+                       args = false;
+               }
+       };
 
-               return wrapperFn;
-       },
+       wrapperFn = function () {
+               if (lock) {
+                       // called too soon, queue to call later
+                       args = arguments;
 
-       // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
-       // Returns the number `num` modulo `range` in such a way so it lies within
-       // `range[0]` and `range[1]`. The returned value will be always smaller than
-       // `range[1]` unless `includeMax` is set to `true`.
-       wrapNum: function (x, range, includeMax) {
-               var max = range[1],
-                   min = range[0],
-                   d = max - min;
-               return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
-       },
+               } else {
+                       // call and lock until later
+                       fn.apply(context, arguments);
+                       setTimeout(later, time);
+                       lock = true;
+               }
+       };
 
-       // @function falseFn(): Function
-       // Returns a function which always returns `false`.
-       falseFn: function () { return false; },
+       return wrapperFn;
+}
 
-       // @function formatNum(num: Number, digits?: Number): Number
-       // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
-       formatNum: function (num, digits) {
-               var pow = Math.pow(10, digits || 5);
-               return Math.round(num * pow) / pow;
-       },
+// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
+// Returns the number `num` modulo `range` in such a way so it lies within
+// `range[0]` and `range[1]`. The returned value will be always smaller than
+// `range[1]` unless `includeMax` is set to `true`.
+function wrapNum(x, range, includeMax) {
+       var max = range[1],
+           min = range[0],
+           d = max - min;
+       return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
+}
 
-       // @function trim(str: String): String
-       // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
-       trim: function (str) {
-               return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
-       },
+// @function falseFn(): Function
+// Returns a function which always returns `false`.
+function falseFn() { return false; }
 
-       // @function splitWords(str: String): String[]
-       // Trims and splits the string on whitespace and returns the array of parts.
-       splitWords: function (str) {
-               return L.Util.trim(str).split(/\s+/);
-       },
+// @function formatNum(num: Number, digits?: Number): Number
+// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
+function formatNum(num, digits) {
+       var pow = Math.pow(10, digits || 5);
+       return Math.round(num * pow) / pow;
+}
 
-       // @function setOptions(obj: Object, options: Object): Object
-       // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
-       setOptions: function (obj, options) {
-               if (!obj.hasOwnProperty('options')) {
-                       obj.options = obj.options ? L.Util.create(obj.options) : {};
-               }
-               for (var i in options) {
-                       obj.options[i] = options[i];
-               }
-               return obj.options;
-       },
+// @function trim(str: String): String
+// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
+function trim(str) {
+       return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
+}
 
-       // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
-       // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
-       // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
-       // be appended at the end. If `uppercase` is `true`, the parameter names will
-       // be uppercased (e.g. `'?A=foo&B=bar'`)
-       getParamString: function (obj, existingUrl, uppercase) {
-               var params = [];
-               for (var i in obj) {
-                       params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
-               }
-               return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
-       },
+// @function splitWords(str: String): String[]
+// Trims and splits the string on whitespace and returns the array of parts.
+function splitWords(str) {
+       return trim(str).split(/\s+/);
+}
 
-       // @function template(str: String, data: Object): String
-       // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
-       // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
-       // `('Hello foo, bar')`. You can also specify functions instead of strings for
-       // data values — they will be evaluated passing `data` as an argument.
-       template: function (str, data) {
-               return str.replace(L.Util.templateRe, function (str, key) {
-                       var value = data[key];
+// @function setOptions(obj: Object, options: Object): Object
+// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
+function setOptions(obj, options) {
+       if (!obj.hasOwnProperty('options')) {
+               obj.options = obj.options ? create(obj.options) : {};
+       }
+       for (var i in options) {
+               obj.options[i] = options[i];
+       }
+       return obj.options;
+}
 
-                       if (value === undefined) {
-                               throw new Error('No value provided for variable ' + str);
+// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
+// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
+// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
+// be appended at the end. If `uppercase` is `true`, the parameter names will
+// be uppercased (e.g. `'?A=foo&B=bar'`)
+function getParamString(obj, existingUrl, uppercase) {
+       var params = [];
+       for (var i in obj) {
+               params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
+       }
+       return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
+}
 
-                       } else if (typeof value === 'function') {
-                               value = value(data);
-                       }
-                       return value;
-               });
-       },
+var templateRe = /\{ *([\w_\-]+) *\}/g;
 
-       templateRe: /\{ *([\w_\-]+) *\}/g,
+// @function template(str: String, data: Object): String
+// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
+// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
+// `('Hello foo, bar')`. You can also specify functions instead of strings for
+// data values — they will be evaluated passing `data` as an argument.
+function template(str, data) {
+       return str.replace(templateRe, function (str, key) {
+               var value = data[key];
 
-       // @function isArray(obj): Boolean
-       // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
-       isArray: Array.isArray || function (obj) {
-               return (Object.prototype.toString.call(obj) === '[object Array]');
-       },
+               if (value === undefined) {
+                       throw new Error('No value provided for variable ' + str);
 
-       // @function indexOf(array: Array, el: Object): Number
-       // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
-       indexOf: function (array, el) {
-               for (var i = 0; i < array.length; i++) {
-                       if (array[i] === el) { return i; }
+               } else if (typeof value === 'function') {
+                       value = value(data);
                }
-               return -1;
-       },
+               return value;
+       });
+}
 
-       // @property emptyImageUrl: String
-       // Data URI string containing a base64-encoded empty GIF image.
-       // Used as a hack to free memory from unused images on WebKit-powered
-       // mobile devices (by setting image `src` to this string).
-       emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
+// @function isArray(obj): Boolean
+// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
+var isArray = Array.isArray || function (obj) {
+       return (Object.prototype.toString.call(obj) === '[object Array]');
 };
 
-(function () {
-       // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
-
-       function getPrefixed(name) {
-               return window['webkit' + name] || window['moz' + name] || window['ms' + name];
+// @function indexOf(array: Array, el: Object): Number
+// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
+function indexOf(array, el) {
+       for (var i = 0; i < array.length; i++) {
+               if (array[i] === el) { return i; }
        }
+       return -1;
+}
 
-       var lastTime = 0;
-
-       // fallback for IE 7-8
-       function timeoutDefer(fn) {
-               var time = +new Date(),
-                   timeToCall = Math.max(0, 16 - (time - lastTime));
+// @property emptyImageUrl: String
+// Data URI string containing a base64-encoded empty GIF image.
+// Used as a hack to free memory from unused images on WebKit-powered
+// mobile devices (by setting image `src` to this string).
+var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
 
-               lastTime = time + timeToCall;
-               return window.setTimeout(fn, timeToCall);
-       }
+// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
 
-       var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
-           cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
-                      getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
+function getPrefixed(name) {
+       return window['webkit' + name] || window['moz' + name] || window['ms' + name];
+}
 
+var lastTime = 0;
 
-       // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
-       // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
-       // `context` if given. When `immediate` is set, `fn` is called immediately if
-       // the browser doesn't have native support for
-       // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
-       // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
-       L.Util.requestAnimFrame = function (fn, context, immediate) {
-               if (immediate && requestFn === timeoutDefer) {
-                       fn.call(context);
-               } else {
-                       return requestFn.call(window, L.bind(fn, context));
-               }
-       };
+// fallback for IE 7-8
+function timeoutDefer(fn) {
+       var time = +new Date(),
+           timeToCall = Math.max(0, 16 - (time - lastTime));
 
-       // @function cancelAnimFrame(id: Number): undefined
-       // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
-       L.Util.cancelAnimFrame = function (id) {
-               if (id) {
-                       cancelFn.call(window, id);
-               }
-       };
-})();
+       lastTime = time + timeToCall;
+       return window.setTimeout(fn, timeToCall);
+}
 
-// 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;
+var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
+var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
+               getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
+
+// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
+// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
+// `context` if given. When `immediate` is set, `fn` is called immediately if
+// the browser doesn't have native support for
+// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
+// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
+function requestAnimFrame(fn, context, immediate) {
+       if (immediate && requestFn === timeoutDefer) {
+               fn.call(context);
+       } else {
+               return requestFn.call(window, bind(fn, context));
+       }
+}
 
+// @function cancelAnimFrame(id: Number): undefined
+// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
+function cancelAnimFrame(id) {
+       if (id) {
+               cancelFn.call(window, id);
+       }
+}
 
 
+var Util = (Object.freeze || Object)({
+       extend: extend,
+       create: create,
+       bind: bind,
+       lastId: lastId,
+       stamp: stamp,
+       throttle: throttle,
+       wrapNum: wrapNum,
+       falseFn: falseFn,
+       formatNum: formatNum,
+       trim: trim,
+       splitWords: splitWords,
+       setOptions: setOptions,
+       getParamString: getParamString,
+       template: template,
+       isArray: isArray,
+       indexOf: indexOf,
+       emptyImageUrl: emptyImageUrl,
+       requestFn: requestFn,
+       cancelFn: cancelFn,
+       requestAnimFrame: requestAnimFrame,
+       cancelAnimFrame: cancelAnimFrame
+});
 
 // @class Class
 // @aka L.Class
@@ -296,9 +282,9 @@ L.setOptions = L.Util.setOptions;
 
 // Thanks to John Resig and Dean Edwards for inspiration!
 
-L.Class = function () {};
+function Class() {}
 
-L.Class.extend = function (props) {
+Class.extend = function (props) {
 
        // @function extend(props: Object): Function
        // [Extends the current class](#class-inheritance) given the properties to be included.
@@ -316,37 +302,38 @@ L.Class.extend = function (props) {
 
        var parentProto = NewClass.__super__ = this.prototype;
 
-       var proto = L.Util.create(parentProto);
+       var proto = create(parentProto);
        proto.constructor = NewClass;
 
        NewClass.prototype = proto;
 
        // inherit parent's statics
        for (var i in this) {
-               if (this.hasOwnProperty(i) && i !== 'prototype') {
+               if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
                        NewClass[i] = this[i];
                }
        }
 
        // mix static properties into the class
        if (props.statics) {
-               L.extend(NewClass, props.statics);
+               extend(NewClass, props.statics);
                delete props.statics;
        }
 
        // mix includes into the prototype
        if (props.includes) {
-               L.Util.extend.apply(null, [proto].concat(props.includes));
+               checkDeprecatedMixinEvents(props.includes);
+               extend.apply(null, [proto].concat(props.includes));
                delete props.includes;
        }
 
        // merge options
        if (proto.options) {
-               props.options = L.Util.extend(L.Util.create(proto.options), props.options);
+               props.options = extend(create(proto.options), props.options);
        }
 
        // mix given properties into the prototype
-       L.extend(proto, props);
+       extend(proto, props);
 
        proto._initHooks = [];
 
@@ -372,21 +359,21 @@ L.Class.extend = function (props) {
 
 // @function include(properties: Object): this
 // [Includes a mixin](#class-includes) into the current class.
-L.Class.include = function (props) {
-       L.extend(this.prototype, props);
+Class.include = function (props) {
+       extend(this.prototype, props);
        return this;
 };
 
 // @function mergeOptions(options: Object): this
 // [Merges `options`](#class-options) into the defaults of the class.
-L.Class.mergeOptions = function (options) {
-       L.extend(this.prototype.options, options);
+Class.mergeOptions = function (options) {
+       extend(this.prototype.options, options);
        return this;
 };
 
 // @function addInitHook(fn: Function): this
 // Adds a [constructor hook](#class-constructor-hooks) to the class.
-L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
+Class.addInitHook = function (fn) { // (Function) || (String, args...)
        var args = Array.prototype.slice.call(arguments, 1);
 
        var init = typeof fn === 'function' ? fn : function () {
@@ -398,7 +385,19 @@ L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
        return this;
 };
 
+function checkDeprecatedMixinEvents(includes) {
+       if (!L || !L.Mixin) { return; }
+
+       includes = isArray(includes) ? includes : [includes];
 
+       for (var i = 0; i < includes.length; i++) {
+               if (includes[i] === L.Mixin.Events) {
+                       console.warn('Deprecated include of L.Mixin.Events: ' +
+                               'this property will be removed in future releases, ' +
+                               'please inherit from L.Evented instead.', new Error().stack);
+               }
+       }
+}
 
 /*
  * @class Evented
@@ -425,9 +424,7 @@ L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
  * ```
  */
 
-
-L.Evented = L.Class.extend({
-
+var Events = {
        /* @method on(type: String, fn: Function, context?: Object): this
         * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
         *
@@ -447,7 +444,7 @@ L.Evented = L.Class.extend({
 
                } else {
                        // types can be a string of space-separated words
-                       types = L.Util.splitWords(types);
+                       types = splitWords(types);
 
                        for (var i = 0, len = types.length; i < len; i++) {
                                this._on(types[i], fn, context);
@@ -480,7 +477,7 @@ L.Evented = L.Class.extend({
                        }
 
                } else {
-                       types = L.Util.splitWords(types);
+                       types = splitWords(types);
 
                        for (var i = 0, len = types.length; i < len; i++) {
                                this._off(types[i], fn, context);
@@ -534,7 +531,7 @@ L.Evented = L.Class.extend({
                if (!fn) {
                        // Set all removed listeners to noop so they are not called if remove happens in fire
                        for (i = 0, len = listeners.length; i < len; i++) {
-                               listeners[i].fn = L.Util.falseFn;
+                               listeners[i].fn = falseFn;
                        }
                        // clear all listeners for a type if function isn't specified
                        delete this._events[type];
@@ -554,7 +551,7 @@ L.Evented = L.Class.extend({
                                if (l.fn === fn) {
 
                                        // set the removed listener to noop so that's not called if remove happens in fire
-                                       l.fn = L.Util.falseFn;
+                                       l.fn = falseFn;
 
                                        if (this._firingCount) {
                                                /* copy array in case events are being fired */
@@ -575,7 +572,7 @@ L.Evented = L.Class.extend({
        fire: function (type, data, propagate) {
                if (!this.listens(type, propagate)) { return this; }
 
-               var event = L.Util.extend({}, data, {type: type, target: this});
+               var event = extend({}, data, {type: type, target: this});
 
                if (this._events) {
                        var listeners = this._events[type];
@@ -625,7 +622,7 @@ L.Evented = L.Class.extend({
                        return this;
                }
 
-               var handler = L.bind(function () {
+               var handler = bind(function () {
                        this
                            .off(types, fn, context)
                            .off(types, handler, context);
@@ -641,7 +638,7 @@ L.Evented = L.Class.extend({
        // Adds an event parent - an `Evented` that will receive propagated events
        addEventParent: function (obj) {
                this._eventParents = this._eventParents || {};
-               this._eventParents[L.stamp(obj)] = obj;
+               this._eventParents[stamp(obj)] = obj;
                return this;
        },
 
@@ -649,202 +646,44 @@ L.Evented = L.Class.extend({
        // Removes an event parent, so it will stop receiving propagated events
        removeEventParent: function (obj) {
                if (this._eventParents) {
-                       delete this._eventParents[L.stamp(obj)];
+                       delete this._eventParents[stamp(obj)];
                }
                return this;
        },
 
        _propagateEvent: function (e) {
                for (var id in this._eventParents) {
-                       this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
+                       this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
                }
        }
-});
-
-var proto = L.Evented.prototype;
+};
 
 // aliases; we should ditch those eventually
 
 // @method addEventListener(…): this
 // Alias to [`on(…)`](#evented-on)
-proto.addEventListener = proto.on;
+Events.addEventListener = Events.on;
 
 // @method removeEventListener(…): this
 // Alias to [`off(…)`](#evented-off)
 
 // @method clearAllEventListeners(…): this
 // Alias to [`off()`](#evented-off)
-proto.removeEventListener = proto.clearAllEventListeners = proto.off;
+Events.removeEventListener = Events.clearAllEventListeners = Events.off;
 
 // @method addOneTimeEventListener(…): this
 // Alias to [`once(…)`](#evented-once)
-proto.addOneTimeEventListener = proto.once;
+Events.addOneTimeEventListener = Events.once;
 
 // @method fireEvent(…): this
 // Alias to [`fire(…)`](#evented-fire)
-proto.fireEvent = proto.fire;
+Events.fireEvent = Events.fire;
 
 // @method hasEventListeners(…): Boolean
 // Alias to [`listens(…)`](#evented-listens)
-proto.hasEventListeners = proto.listens;
-
-L.Mixin = {Events: proto};
-
-
-
-/*
- * @namespace Browser
- * @aka L.Browser
- *
- * A namespace with static properties for browser/feature detection used by Leaflet internally.
- *
- * @example
- *
- * ```js
- * if (L.Browser.ielt9) {
- *   alert('Upgrade your browser, dude!');
- * }
- * ```
- */
-
-(function () {
-
-       var ua = navigator.userAgent.toLowerCase(),
-           doc = document.documentElement,
-
-           ie = 'ActiveXObject' in window,
-
-           webkit    = ua.indexOf('webkit') !== -1,
-           phantomjs = ua.indexOf('phantom') !== -1,
-           android23 = ua.search('android [23]') !== -1,
-           chrome    = ua.indexOf('chrome') !== -1,
-           gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
-
-           win = navigator.platform.indexOf('Win') === 0,
-
-           mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
-           msPointer = !window.PointerEvent && window.MSPointerEvent,
-           pointer = window.PointerEvent || msPointer,
-
-           ie3d = ie && ('transition' in doc.style),
-           webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
-           gecko3d = 'MozPerspective' in doc.style,
-           opera12 = 'OTransition' in doc.style;
-
-
-       var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
-                       (window.DocumentTouch && document instanceof window.DocumentTouch));
-
-       L.Browser = {
-
-               // @property ie: Boolean
-               // `true` for all Internet Explorer versions (not Edge).
-               ie: ie,
-
-               // @property ielt9: Boolean
-               // `true` for Internet Explorer versions less than 9.
-               ielt9: ie && !document.addEventListener,
-
-               // @property edge: Boolean
-               // `true` for the Edge web browser.
-               edge: 'msLaunchUri' in navigator && !('documentMode' in document),
-
-               // @property webkit: Boolean
-               // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
-               webkit: webkit,
-
-               // @property gecko: Boolean
-               // `true` for gecko-based browsers like Firefox.
-               gecko: gecko,
-
-               // @property android: Boolean
-               // `true` for any browser running on an Android platform.
-               android: ua.indexOf('android') !== -1,
-
-               // @property android23: Boolean
-               // `true` for browsers running on Android 2 or Android 3.
-               android23: android23,
-
-               // @property chrome: Boolean
-               // `true` for the Chrome browser.
-               chrome: chrome,
-
-               // @property safari: Boolean
-               // `true` for the Safari browser.
-               safari: !chrome && ua.indexOf('safari') !== -1,
-
-
-               // @property win: Boolean
-               // `true` when the browser is running in a Windows platform
-               win: win,
-
-
-               // @property ie3d: Boolean
-               // `true` for all Internet Explorer versions supporting CSS transforms.
-               ie3d: ie3d,
-
-               // @property webkit3d: Boolean
-               // `true` for webkit-based browsers supporting CSS transforms.
-               webkit3d: webkit3d,
-
-               // @property gecko3d: Boolean
-               // `true` for gecko-based browsers supporting CSS transforms.
-               gecko3d: gecko3d,
-
-               // @property opera12: Boolean
-               // `true` for the Opera browser supporting CSS transforms (version 12 or later).
-               opera12: opera12,
-
-               // @property any3d: Boolean
-               // `true` for all browsers supporting CSS transforms.
-               any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
-
-
-               // @property mobile: Boolean
-               // `true` for all browsers running in a mobile device.
-               mobile: mobile,
-
-               // @property mobileWebkit: Boolean
-               // `true` for all webkit-based browsers in a mobile device.
-               mobileWebkit: mobile && webkit,
-
-               // @property mobileWebkit3d: Boolean
-               // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
-               mobileWebkit3d: mobile && webkit3d,
-
-               // @property mobileOpera: Boolean
-               // `true` for the Opera browser in a mobile device.
-               mobileOpera: mobile && window.opera,
-
-               // @property mobileGecko: Boolean
-               // `true` for gecko-based browsers running in a mobile device.
-               mobileGecko: mobile && gecko,
-
-
-               // @property touch: Boolean
-               // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
-               // This does not necessarily mean that the browser is running in a computer with
-               // a touchscreen, it only means that the browser is capable of understanding
-               // touch events.
-               touch: !!touch,
-
-               // @property msPointer: Boolean
-               // `true` for browsers implementing the Microsoft touch events model (notably IE10).
-               msPointer: !!msPointer,
-
-               // @property pointer: Boolean
-               // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
-               pointer: !!pointer,
-
-
-               // @property retina: Boolean
-               // `true` for browsers on a high-resolution "retina" screen.
-               retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
-       };
-
-}());
-
+Events.hasEventListeners = Events.listens;
 
+var Evented = Class.extend(Events);
 
 /*
  * @class Point
@@ -866,26 +705,26 @@ L.Mixin = {Events: proto};
  * ```
  */
 
-L.Point = function (x, y, round) {
+function Point(x, y, round) {
        // @property x: Number; The `x` coordinate of the point
        this.x = (round ? Math.round(x) : x);
        // @property y: Number; The `y` coordinate of the point
        this.y = (round ? Math.round(y) : y);
-};
+}
 
-L.Point.prototype = {
+Point.prototype = {
 
        // @method clone(): Point
        // Returns a copy of the current point.
        clone: function () {
-               return new L.Point(this.x, this.y);
+               return new Point(this.x, this.y);
        },
 
        // @method add(otherPoint: Point): Point
        // Returns the result of addition of the current and the given points.
        add: function (point) {
                // non-destructive, returns a new point
-               return this.clone()._add(L.point(point));
+               return this.clone()._add(toPoint(point));
        },
 
        _add: function (point) {
@@ -898,7 +737,7 @@ L.Point.prototype = {
        // @method subtract(otherPoint: Point): Point
        // Returns the result of subtraction of the given point from the current.
        subtract: function (point) {
-               return this.clone()._subtract(L.point(point));
+               return this.clone()._subtract(toPoint(point));
        },
 
        _subtract: function (point) {
@@ -937,14 +776,14 @@ L.Point.prototype = {
        // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
        // defined by `scale`.
        scaleBy: function (point) {
-               return new L.Point(this.x * point.x, this.y * point.y);
+               return new Point(this.x * point.x, this.y * point.y);
        },
 
        // @method unscaleBy(scale: Point): Point
        // Inverse of `scaleBy`. Divide each coordinate of the current point by
        // each coordinate of `scale`.
        unscaleBy: function (point) {
-               return new L.Point(this.x / point.x, this.y / point.y);
+               return new Point(this.x / point.x, this.y / point.y);
        },
 
        // @method round(): Point
@@ -986,7 +825,7 @@ L.Point.prototype = {
        // @method distanceTo(otherPoint: Point): Number
        // Returns the cartesian distance between the current and the given points.
        distanceTo: function (point) {
-               point = L.point(point);
+               point = toPoint(point);
 
                var x = point.x - this.x,
                    y = point.y - this.y;
@@ -997,7 +836,7 @@ L.Point.prototype = {
        // @method equals(otherPoint: Point): Boolean
        // Returns `true` if the given point has the same coordinates.
        equals: function (point) {
-               point = L.point(point);
+               point = toPoint(point);
 
                return point.x === this.x &&
                       point.y === this.y;
@@ -1006,7 +845,7 @@ L.Point.prototype = {
        // @method contains(otherPoint: Point): Boolean
        // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
        contains: function (point) {
-               point = L.point(point);
+               point = toPoint(point);
 
                return Math.abs(point.x) <= Math.abs(this.x) &&
                       Math.abs(point.y) <= Math.abs(this.y);
@@ -1016,8 +855,8 @@ L.Point.prototype = {
        // Returns a string representation of the point for debugging purposes.
        toString: function () {
                return 'Point(' +
-                       L.Util.formatNum(this.x) + ', ' +
-                       L.Util.formatNum(this.y) + ')';
+                       formatNum(this.x) + ', ' +
+                       formatNum(this.y) + ')';
        }
 };
 
@@ -1031,23 +870,21 @@ L.Point.prototype = {
 // @alternative
 // @factory L.point(coords: Object)
 // Expects a plain object of the form `{x: Number, y: Number}` instead.
-L.point = function (x, y, round) {
-       if (x instanceof L.Point) {
+function toPoint(x, y, round) {
+       if (x instanceof Point) {
                return x;
        }
-       if (L.Util.isArray(x)) {
-               return new L.Point(x[0], x[1]);
+       if (isArray(x)) {
+               return new Point(x[0], x[1]);
        }
        if (x === undefined || x === null) {
                return x;
        }
        if (typeof x === 'object' && 'x' in x && 'y' in x) {
-               return new L.Point(x.x, x.y);
+               return new Point(x.x, x.y);
        }
-       return new L.Point(x, y, round);
-};
-
-
+       return new Point(x, y, round);
+}
 
 /*
  * @class Bounds
@@ -1070,7 +907,7 @@ L.point = function (x, y, round) {
  * ```
  */
 
-L.Bounds = function (a, b) {
+function Bounds(a, b) {
        if (!a) { return; }
 
        var points = b ? [a, b] : a;
@@ -1078,13 +915,13 @@ L.Bounds = function (a, b) {
        for (var i = 0, len = points.length; i < len; i++) {
                this.extend(points[i]);
        }
-};
+}
 
-L.Bounds.prototype = {
+Bounds.prototype = {
        // @method extend(point: Point): this
        // Extends the bounds to contain the given point.
        extend: function (point) { // (Point)
-               point = L.point(point);
+               point = toPoint(point);
 
                // @property min: Point
                // The top left corner of the rectangle.
@@ -1105,7 +942,7 @@ L.Bounds.prototype = {
        // @method getCenter(round?: Boolean): Point
        // Returns the center point of the bounds.
        getCenter: function (round) {
-               return new L.Point(
+               return new Point(
                        (this.min.x + this.max.x) / 2,
                        (this.min.y + this.max.y) / 2, round);
        },
@@ -1113,13 +950,25 @@ L.Bounds.prototype = {
        // @method getBottomLeft(): Point
        // Returns the bottom-left point of the bounds.
        getBottomLeft: function () {
-               return new L.Point(this.min.x, this.max.y);
+               return new Point(this.min.x, this.max.y);
        },
 
        // @method getTopRight(): Point
        // Returns the top-right point of the bounds.
        getTopRight: function () { // -> Point
-               return new L.Point(this.max.x, this.min.y);
+               return new Point(this.max.x, this.min.y);
+       },
+
+       // @method getTopLeft(): Point
+       // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
+       getTopLeft: function () {
+               return this.min; // left, top
+       },
+
+       // @method getBottomRight(): Point
+       // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
+       getBottomRight: function () {
+               return this.max; // right, bottom
        },
 
        // @method getSize(): Point
@@ -1136,13 +985,13 @@ L.Bounds.prototype = {
        contains: function (obj) {
                var min, max;
 
-               if (typeof obj[0] === 'number' || obj instanceof L.Point) {
-                       obj = L.point(obj);
+               if (typeof obj[0] === 'number' || obj instanceof Point) {
+                       obj = toPoint(obj);
                } else {
-                       obj = L.bounds(obj);
+                       obj = toBounds(obj);
                }
 
-               if (obj instanceof L.Bounds) {
+               if (obj instanceof Bounds) {
                        min = obj.min;
                        max = obj.max;
                } else {
@@ -1159,7 +1008,7 @@ L.Bounds.prototype = {
        // Returns `true` if the rectangle intersects the given bounds. Two bounds
        // intersect if they have at least one point in common.
        intersects: function (bounds) { // (Bounds) -> Boolean
-               bounds = L.bounds(bounds);
+               bounds = toBounds(bounds);
 
                var min = this.min,
                    max = this.max,
@@ -1175,7 +1024,7 @@ L.Bounds.prototype = {
        // Returns `true` if the rectangle overlaps the given bounds. Two bounds
        // overlap if their intersection is an area.
        overlaps: function (bounds) { // (Bounds) -> Boolean
-               bounds = L.bounds(bounds);
+               bounds = toBounds(bounds);
 
                var min = this.min,
                    max = this.max,
@@ -1193,399 +1042,261 @@ L.Bounds.prototype = {
 };
 
 
-// @factory L.bounds(topLeft: Point, bottomRight: Point)
-// Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
+// @factory L.bounds(corner1: Point, corner2: Point)
+// Creates a Bounds object from two corners coordinate pairs.
 // @alternative
 // @factory L.bounds(points: Point[])
-// Creates a Bounds object from the points it contains
-L.bounds = function (a, b) {
-       if (!a || a instanceof L.Bounds) {
+// Creates a Bounds object from the given array of points.
+function toBounds(a, b) {
+       if (!a || a instanceof Bounds) {
                return a;
        }
-       return new L.Bounds(a, b);
-};
-
-
+       return new Bounds(a, b);
+}
 
 /*
- * @class Transformation
- * @aka L.Transformation
+ * @class LatLngBounds
+ * @aka L.LatLngBounds
  *
- * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
- * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
- * the reverse. Used by Leaflet in its projections code.
+ * Represents a rectangular geographical area on a map.
  *
  * @example
  *
  * ```js
- * var transformation = new L.Transformation(2, 5, -1, 10),
- *     p = L.point(1, 2),
- *     p2 = transformation.transform(p), //  L.point(7, 8)
- *     p3 = transformation.untransform(p2); //  L.point(1, 2)
+ * var corner1 = L.latLng(40.712, -74.227),
+ * corner2 = L.latLng(40.774, -74.125),
+ * bounds = L.latLngBounds(corner1, corner2);
+ * ```
+ *
+ * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
+ *
+ * ```js
+ * map.fitBounds([
+ *     [40.712, -74.227],
+ *     [40.774, -74.125]
+ * ]);
  * ```
+ *
+ * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
  */
 
+function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
+       if (!corner1) { return; }
 
-// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
-// Creates a `Transformation` object with the given coefficients.
-L.Transformation = function (a, b, c, d) {
-       this._a = a;
-       this._b = b;
-       this._c = c;
-       this._d = d;
-};
-
-L.Transformation.prototype = {
-       // @method transform(point: Point, scale?: Number): Point
-       // Returns a transformed point, optionally multiplied by the given scale.
-       // Only accepts actual `L.Point` instances, not arrays.
-       transform: function (point, scale) { // (Point, Number) -> Point
-               return this._transform(point.clone(), scale);
-       },
-
-       // destructive transform (faster)
-       _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;
-       },
+       var latlngs = corner2 ? [corner1, corner2] : corner1;
 
-       // @method untransform(point: Point, scale?: Number): Point
-       // Returns the reverse transformation of the given point, optionally divided
-       // by the given scale. Only accepts actual `L.Point` instances, not arrays.
-       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);
+       for (var i = 0, len = latlngs.length; i < len; i++) {
+               this.extend(latlngs[i]);
        }
-};
-
+}
 
+LatLngBounds.prototype = {
 
-/*
- * @namespace DomUtil
- *
- * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
- * tree, used by Leaflet internally.
- *
- * Most functions expecting or returning a `HTMLElement` also work for
- * SVG elements. The only difference is that classes refer to CSS classes
- * in HTML and SVG classes in SVG.
- */
+       // @method extend(latlng: LatLng): this
+       // Extend the bounds to contain the given point
 
-L.DomUtil = {
+       // @alternative
+       // @method extend(otherBounds: LatLngBounds): this
+       // Extend the bounds to contain the given bounds
+       extend: function (obj) {
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   sw2, ne2;
 
-       // @function get(id: String|HTMLElement): HTMLElement
-       // Returns an element given its DOM id, or returns the element itself
-       // if it was passed directly.
-       get: function (id) {
-               return typeof id === 'string' ? document.getElementById(id) : id;
-       },
+               if (obj instanceof LatLng) {
+                       sw2 = obj;
+                       ne2 = obj;
 
-       // @function getStyle(el: HTMLElement, styleAttrib: String): String
-       // Returns the value for a certain style attribute on an element,
-       // including computed values or values set through CSS.
-       getStyle: function (el, style) {
+               } else if (obj instanceof LatLngBounds) {
+                       sw2 = obj._southWest;
+                       ne2 = obj._northEast;
 
-               var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
+                       if (!sw2 || !ne2) { return this; }
 
-               if ((!value || value === 'auto') && document.defaultView) {
-                       var css = document.defaultView.getComputedStyle(el, null);
-                       value = css ? css[style] : null;
+               } else {
+                       return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
                }
 
-               return value === 'auto' ? null : value;
-       },
-
-       // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
-       // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
-       create: function (tagName, className, container) {
-
-               var el = document.createElement(tagName);
-               el.className = className || '';
-
-               if (container) {
-                       container.appendChild(el);
+               if (!sw && !ne) {
+                       this._southWest = new LatLng(sw2.lat, sw2.lng);
+                       this._northEast = new LatLng(ne2.lat, ne2.lng);
+               } else {
+                       sw.lat = Math.min(sw2.lat, sw.lat);
+                       sw.lng = Math.min(sw2.lng, sw.lng);
+                       ne.lat = Math.max(ne2.lat, ne.lat);
+                       ne.lng = Math.max(ne2.lng, ne.lng);
                }
 
-               return el;
+               return this;
        },
 
-       // @function remove(el: HTMLElement)
-       // Removes `el` from its parent element
-       remove: function (el) {
-               var parent = el.parentNode;
-               if (parent) {
-                       parent.removeChild(el);
-               }
-       },
+       // @method pad(bufferRatio: Number): LatLngBounds
+       // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
+       pad: function (bufferRatio) {
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
+                   widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
 
-       // @function empty(el: HTMLElement)
-       // Removes all of `el`'s children elements from `el`
-       empty: function (el) {
-               while (el.firstChild) {
-                       el.removeChild(el.firstChild);
-               }
+               return new LatLngBounds(
+                       new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
+                       new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
        },
 
-       // @function toFront(el: HTMLElement)
-       // Makes `el` the last children of its parent, so it renders in front of the other children.
-       toFront: function (el) {
-               el.parentNode.appendChild(el);
+       // @method getCenter(): LatLng
+       // Returns the center point of the bounds.
+       getCenter: function () {
+               return new LatLng(
+                       (this._southWest.lat + this._northEast.lat) / 2,
+                       (this._southWest.lng + this._northEast.lng) / 2);
        },
 
-       // @function toBack(el: HTMLElement)
-       // Makes `el` the first children of its parent, so it renders back from the other children.
-       toBack: function (el) {
-               var parent = el.parentNode;
-               parent.insertBefore(el, parent.firstChild);
+       // @method getSouthWest(): LatLng
+       // Returns the south-west point of the bounds.
+       getSouthWest: function () {
+               return this._southWest;
        },
 
-       // @function hasClass(el: HTMLElement, name: String): Boolean
-       // Returns `true` if the element's class attribute contains `name`.
-       hasClass: function (el, name) {
-               if (el.classList !== undefined) {
-                       return el.classList.contains(name);
-               }
-               var className = L.DomUtil.getClass(el);
-               return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+       // @method getNorthEast(): LatLng
+       // Returns the north-east point of the bounds.
+       getNorthEast: function () {
+               return this._northEast;
        },
 
-       // @function addClass(el: HTMLElement, name: String)
-       // Adds `name` to the element's class attribute.
-       addClass: function (el, name) {
-               if (el.classList !== undefined) {
-                       var classes = L.Util.splitWords(name);
-                       for (var i = 0, len = classes.length; i < len; i++) {
-                               el.classList.add(classes[i]);
-                       }
-               } else if (!L.DomUtil.hasClass(el, name)) {
-                       var className = L.DomUtil.getClass(el);
-                       L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
-               }
+       // @method getNorthWest(): LatLng
+       // Returns the north-west point of the bounds.
+       getNorthWest: function () {
+               return new LatLng(this.getNorth(), this.getWest());
        },
 
-       // @function removeClass(el: HTMLElement, name: String)
-       // Removes `name` from the element's class attribute.
-       removeClass: function (el, name) {
-               if (el.classList !== undefined) {
-                       el.classList.remove(name);
-               } else {
-                       L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
-               }
+       // @method getSouthEast(): LatLng
+       // Returns the south-east point of the bounds.
+       getSouthEast: function () {
+               return new LatLng(this.getSouth(), this.getEast());
        },
 
-       // @function setClass(el: HTMLElement, name: String)
-       // Sets the element's class.
-       setClass: function (el, name) {
-               if (el.className.baseVal === undefined) {
-                       el.className = name;
-               } else {
-                       // in case of SVG element
-                       el.className.baseVal = name;
-               }
+       // @method getWest(): Number
+       // Returns the west longitude of the bounds
+       getWest: function () {
+               return this._southWest.lng;
        },
 
-       // @function getClass(el: HTMLElement): String
-       // Returns the element's class.
-       getClass: function (el) {
-               return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+       // @method getSouth(): Number
+       // Returns the south latitude of the bounds
+       getSouth: function () {
+               return this._southWest.lat;
        },
 
-       // @function setOpacity(el: HTMLElement, opacity: Number)
-       // Set the opacity of an element (including old IE support).
-       // `opacity` must be a number from `0` to `1`.
-       setOpacity: function (el, value) {
-
-               if ('opacity' in el.style) {
-                       el.style.opacity = value;
+       // @method getEast(): Number
+       // Returns the east longitude of the bounds
+       getEast: function () {
+               return this._northEast.lng;
+       },
 
-               } else if ('filter' in el.style) {
-                       L.DomUtil._setOpacityIE(el, value);
-               }
+       // @method getNorth(): Number
+       // Returns the north latitude of the bounds
+       getNorth: function () {
+               return this._northEast.lat;
        },
 
-       _setOpacityIE: function (el, value) {
-               var filter = false,
-                   filterName = 'DXImageTransform.Microsoft.Alpha';
+       // @method contains(otherBounds: LatLngBounds): Boolean
+       // Returns `true` if the rectangle contains the given one.
 
-               // filters collection throws an error if we try to retrieve a filter that doesn't exist
-               try {
-                       filter = el.filters.item(filterName);
-               } catch (e) {
-                       // don't set opacity to 1 if we haven't already set an opacity,
-                       // it isn't needed and breaks transparent pngs.
-                       if (value === 1) { return; }
+       // @alternative
+       // @method contains (latlng: LatLng): Boolean
+       // Returns `true` if the rectangle contains the given point.
+       contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
+               if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
+                       obj = toLatLng(obj);
+               } else {
+                       obj = toLatLngBounds(obj);
                }
 
-               value = Math.round(value * 100);
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   sw2, ne2;
 
-               if (filter) {
-                       filter.Enabled = (value !== 100);
-                       filter.Opacity = value;
+               if (obj instanceof LatLngBounds) {
+                       sw2 = obj.getSouthWest();
+                       ne2 = obj.getNorthEast();
                } else {
-                       el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+                       sw2 = ne2 = obj;
                }
-       },
 
-       // @function testProp(props: String[]): String|false
-       // Goes through the array of style names and returns the first name
-       // that is a valid style name for an element. If no such name is found,
-       // it returns false. Useful for vendor-prefixed styles like `transform`.
-       testProp: function (props) {
-
-               var style = document.documentElement.style;
-
-               for (var i = 0; i < props.length; i++) {
-                       if (props[i] in style) {
-                               return props[i];
-                       }
-               }
-               return false;
+               return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
+                      (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
        },
 
-       // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
-       // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
-       // and optionally scaled by `scale`. Does not have an effect if the
-       // browser doesn't support 3D CSS transforms.
-       setTransform: function (el, offset, scale) {
-               var pos = offset || new L.Point(0, 0);
-
-               el.style[L.DomUtil.TRANSFORM] =
-                       (L.Browser.ie3d ?
-                               'translate(' + pos.x + 'px,' + pos.y + 'px)' :
-                               'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
-                       (scale ? ' scale(' + scale + ')' : '');
-       },
+       // @method intersects(otherBounds: LatLngBounds): Boolean
+       // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
+       intersects: function (bounds) {
+               bounds = toLatLngBounds(bounds);
 
-       // @function setPosition(el: HTMLElement, position: Point)
-       // Sets the position of `el` to coordinates specified by `position`,
-       // using CSS translate or top/left positioning depending on the browser
-       // (used by Leaflet internally to position its layers).
-       setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   sw2 = bounds.getSouthWest(),
+                   ne2 = bounds.getNorthEast(),
 
-               /*eslint-disable */
-               el._leaflet_pos = point;
-               /*eslint-enable */
+                   latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
+                   lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
 
-               if (L.Browser.any3d) {
-                       L.DomUtil.setTransform(el, point);
-               } else {
-                       el.style.left = point.x + 'px';
-                       el.style.top = point.y + 'px';
-               }
+               return latIntersects && lngIntersects;
        },
 
-       // @function getPosition(el: HTMLElement): Point
-       // Returns the coordinates of an element previously positioned with setPosition.
-       getPosition: function (el) {
-               // this method is only used for elements previously positioned using setPosition,
-               // so it's safe to cache the position for performance
-
-               return el._leaflet_pos || new L.Point(0, 0);
-       }
-};
-
-
-(function () {
-       // prefix style property names
+       // @method overlaps(otherBounds: Bounds): Boolean
+       // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
+       overlaps: function (bounds) {
+               bounds = toLatLngBounds(bounds);
 
-       // @property TRANSFORM: String
-       // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
-       L.DomUtil.TRANSFORM = L.DomUtil.testProp(
-                       ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+               var sw = this._southWest,
+                   ne = this._northEast,
+                   sw2 = bounds.getSouthWest(),
+                   ne2 = bounds.getNorthEast(),
 
+                   latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
+                   lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
 
-       // 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
+               return latOverlaps && lngOverlaps;
+       },
 
-       // @property TRANSITION: String
-       // Vendor-prefixed transform style name.
-       var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
-                       ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+       // @method toBBoxString(): String
+       // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
+       toBBoxString: function () {
+               return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
+       },
 
-       L.DomUtil.TRANSITION_END =
-                       transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
+       // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
+       // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.
+       equals: function (bounds, maxMargin) {
+               if (!bounds) { return false; }
 
-       // @function disableTextSelection()
-       // Prevents the user from generating `selectstart` DOM events, usually generated
-       // when the user drags the mouse through a page with text. Used internally
-       // by Leaflet to override the behaviour of any click-and-drag interaction on
-       // the map. Affects drag interactions on the whole document.
+               bounds = toLatLngBounds(bounds);
 
-       // @function enableTextSelection()
-       // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
-       if ('onselectstart' in document) {
-               L.DomUtil.disableTextSelection = function () {
-                       L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
-               };
-               L.DomUtil.enableTextSelection = function () {
-                       L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
-               };
+               return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
+                      this._northEast.equals(bounds.getNorthEast(), maxMargin);
+       },
 
-       } else {
-               var userSelectProperty = L.DomUtil.testProp(
-                       ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
-
-               L.DomUtil.disableTextSelection = function () {
-                       if (userSelectProperty) {
-                               var style = document.documentElement.style;
-                               this._userSelect = style[userSelectProperty];
-                               style[userSelectProperty] = 'none';
-                       }
-               };
-               L.DomUtil.enableTextSelection = function () {
-                       if (userSelectProperty) {
-                               document.documentElement.style[userSelectProperty] = this._userSelect;
-                               delete this._userSelect;
-                       }
-               };
+       // @method isValid(): Boolean
+       // Returns `true` if the bounds are properly initialized.
+       isValid: function () {
+               return !!(this._southWest && this._northEast);
        }
+};
 
-       // @function disableImageDrag()
-       // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
-       // for `dragstart` DOM events, usually generated when the user drags an image.
-       L.DomUtil.disableImageDrag = function () {
-               L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
-       };
-
-       // @function enableImageDrag()
-       // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
-       L.DomUtil.enableImageDrag = function () {
-               L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
-       };
-
-       // @function preventOutline(el: HTMLElement)
-       // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
-       // of the element `el` invisible. Used internally by Leaflet to prevent
-       // focusable elements from displaying an outline when the user performs a
-       // drag interaction on them.
-       L.DomUtil.preventOutline = function (element) {
-               while (element.tabIndex === -1) {
-                       element = element.parentNode;
-               }
-               if (!element || !element.style) { return; }
-               L.DomUtil.restoreOutline();
-               this._outlineElement = element;
-               this._outlineStyle = element.style.outline;
-               element.style.outline = 'none';
-               L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
-       };
-
-       // @function restoreOutline()
-       // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
-       L.DomUtil.restoreOutline = function () {
-               if (!this._outlineElement) { return; }
-               this._outlineElement.style.outline = this._outlineStyle;
-               delete this._outlineElement;
-               delete this._outlineStyle;
-               L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
-       };
-})();
+// TODO International date line?
 
+// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
+// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
 
+// @alternative
+// @factory L.latLngBounds(latlngs: LatLng[])
+// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
+function toLatLngBounds(a, b) {
+       if (a instanceof LatLngBounds) {
+               return a;
+       }
+       return new LatLngBounds(a, b);
+}
 
 /* @class LatLng
  * @aka L.LatLng
@@ -1608,7 +1319,7 @@ L.DomUtil = {
  * ```
  */
 
-L.LatLng = function (lat, lng, alt) {
+function LatLng(lat, lng, alt) {
        if (isNaN(lat) || isNaN(lng)) {
                throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
        }
@@ -1626,15 +1337,15 @@ L.LatLng = function (lat, lng, alt) {
        if (alt !== undefined) {
                this.alt = +alt;
        }
-};
+}
 
-L.LatLng.prototype = {
+LatLng.prototype = {
        // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
        // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
        equals: function (obj, maxMargin) {
                if (!obj) { return false; }
 
-               obj = L.latLng(obj);
+               obj = toLatLng(obj);
 
                var margin = Math.max(
                        Math.abs(this.lat - obj.lat),
@@ -1647,20 +1358,20 @@ L.LatLng.prototype = {
        // Returns a string representation of the point (for debugging purposes).
        toString: function (precision) {
                return 'LatLng(' +
-                       L.Util.formatNum(this.lat, precision) + ', ' +
-                       L.Util.formatNum(this.lng, precision) + ')';
+                       formatNum(this.lat, precision) + ', ' +
+                       formatNum(this.lng, precision) + ')';
        },
 
        // @method distanceTo(otherLatLng: LatLng): Number
        // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
        distanceTo: function (other) {
-               return L.CRS.Earth.distance(this, L.latLng(other));
+               return Earth.distance(this, toLatLng(other));
        },
 
        // @method wrap(): LatLng
        // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
        wrap: function () {
-               return L.CRS.Earth.wrapLatLng(this);
+               return Earth.wrapLatLng(this);
        },
 
        // @method toBounds(sizeInMeters: Number): LatLngBounds
@@ -1669,13 +1380,13 @@ L.LatLng.prototype = {
                var latAccuracy = 180 * sizeInMeters / 40075017,
                    lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
 
-               return L.latLngBounds(
+               return toLatLngBounds(
                        [this.lat - latAccuracy, this.lng - lngAccuracy],
                        [this.lat + latAccuracy, this.lng + lngAccuracy]);
        },
 
        clone: function () {
-               return new L.LatLng(this.lat, this.lng, this.alt);
+               return new LatLng(this.lat, this.lng, this.alt);
        }
 };
 
@@ -1692,16 +1403,16 @@ L.LatLng.prototype = {
 // @factory L.latLng(coords: Object): LatLng
 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
 
-L.latLng = function (a, b, c) {
-       if (a instanceof L.LatLng) {
+function toLatLng(a, b, c) {
+       if (a instanceof LatLng) {
                return a;
        }
-       if (L.Util.isArray(a) && typeof a[0] !== 'object') {
+       if (isArray(a) && typeof a[0] !== 'object') {
                if (a.length === 3) {
-                       return new L.LatLng(a[0], a[1], a[2]);
+                       return new LatLng(a[0], a[1], a[2]);
                }
                if (a.length === 2) {
-                       return new L.LatLng(a[0], a[1]);
+                       return new LatLng(a[0], a[1]);
                }
                return null;
        }
@@ -1709,290 +1420,173 @@ L.latLng = function (a, b, c) {
                return a;
        }
        if (typeof a === 'object' && 'lat' in a) {
-               return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
+               return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
        }
        if (b === undefined) {
                return null;
        }
-       return new L.LatLng(a, b, c);
-};
-
-
+       return new LatLng(a, b, c);
+}
 
 /*
- * @class LatLngBounds
- * @aka L.LatLngBounds
- *
- * Represents a rectangular geographical area on a map.
- *
- * @example
- *
- * ```js
- * var corner1 = L.latLng(40.712, -74.227),
- * corner2 = L.latLng(40.774, -74.125),
- * bounds = L.latLngBounds(corner1, corner2);
- * ```
- *
- * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
- *
- * ```js
- * map.fitBounds([
- *     [40.712, -74.227],
- *     [40.774, -74.125]
- * ]);
- * ```
+ * @namespace CRS
+ * @crs L.CRS.Base
+ * Object that defines coordinate reference systems for projecting
+ * geographical points into pixel (screen) coordinates and back (and to
+ * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
+ * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
  *
- * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
+ * Leaflet defines the most usual CRSs by default. If you want to use a
+ * CRS not defined by default, take a look at the
+ * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
  */
 
-L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
-       if (!corner1) { return; }
+var CRS = {
+       // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
+       // Projects geographical coordinates into pixel coordinates for a given zoom.
+       latLngToPoint: function (latlng, zoom) {
+               var projectedPoint = this.projection.project(latlng),
+                   scale = this.scale(zoom);
 
-       var latlngs = corner2 ? [corner1, corner2] : corner1;
+               return this.transformation._transform(projectedPoint, scale);
+       },
 
-       for (var i = 0, len = latlngs.length; i < len; i++) {
-               this.extend(latlngs[i]);
-       }
-};
+       // @method pointToLatLng(point: Point, zoom: Number): LatLng
+       // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
+       // zoom into geographical coordinates.
+       pointToLatLng: function (point, zoom) {
+               var scale = this.scale(zoom),
+                   untransformedPoint = this.transformation.untransform(point, scale);
 
-L.LatLngBounds.prototype = {
+               return this.projection.unproject(untransformedPoint);
+       },
 
-       // @method extend(latlng: LatLng): this
-       // Extend the bounds to contain the given point
+       // @method project(latlng: LatLng): Point
+       // Projects geographical coordinates into coordinates in units accepted for
+       // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
+       project: function (latlng) {
+               return this.projection.project(latlng);
+       },
 
-       // @alternative
-       // @method extend(otherBounds: LatLngBounds): this
-       // Extend the bounds to contain the given bounds
-       extend: function (obj) {
-               var sw = this._southWest,
-                   ne = this._northEast,
-                   sw2, ne2;
+       // @method unproject(point: Point): LatLng
+       // Given a projected coordinate returns the corresponding LatLng.
+       // The inverse of `project`.
+       unproject: function (point) {
+               return this.projection.unproject(point);
+       },
 
-               if (obj instanceof L.LatLng) {
-                       sw2 = obj;
-                       ne2 = obj;
+       // @method scale(zoom: Number): Number
+       // Returns the scale used when transforming projected coordinates into
+       // pixel coordinates for a particular zoom. For example, it returns
+       // `256 * 2^zoom` for Mercator-based CRS.
+       scale: function (zoom) {
+               return 256 * Math.pow(2, zoom);
+       },
 
-               } else if (obj instanceof L.LatLngBounds) {
-                       sw2 = obj._southWest;
-                       ne2 = obj._northEast;
+       // @method zoom(scale: Number): Number
+       // Inverse of `scale()`, returns the zoom level corresponding to a scale
+       // factor of `scale`.
+       zoom: function (scale) {
+               return Math.log(scale / 256) / Math.LN2;
+       },
 
-                       if (!sw2 || !ne2) { return this; }
-
-               } else {
-                       return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
-               }
-
-               if (!sw && !ne) {
-                       this._southWest = new L.LatLng(sw2.lat, sw2.lng);
-                       this._northEast = new L.LatLng(ne2.lat, ne2.lng);
-               } else {
-                       sw.lat = Math.min(sw2.lat, sw.lat);
-                       sw.lng = Math.min(sw2.lng, sw.lng);
-                       ne.lat = Math.max(ne2.lat, ne.lat);
-                       ne.lng = Math.max(ne2.lng, ne.lng);
-               }
-
-               return this;
-       },
-
-       // @method pad(bufferRatio: Number): LatLngBounds
-       // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
-       pad: function (bufferRatio) {
-               var sw = this._southWest,
-                   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));
-       },
-
-       // @method getCenter(): LatLng
-       // Returns the center point of the bounds.
-       getCenter: function () {
-               return new L.LatLng(
-                       (this._southWest.lat + this._northEast.lat) / 2,
-                       (this._southWest.lng + this._northEast.lng) / 2);
-       },
+       // @method getProjectedBounds(zoom: Number): Bounds
+       // Returns the projection's bounds scaled and transformed for the provided `zoom`.
+       getProjectedBounds: function (zoom) {
+               if (this.infinite) { return null; }
 
-       // @method getSouthWest(): LatLng
-       // Returns the south-west point of the bounds.
-       getSouthWest: function () {
-               return this._southWest;
-       },
+               var b = this.projection.bounds,
+                   s = this.scale(zoom),
+                   min = this.transformation.transform(b.min, s),
+                   max = this.transformation.transform(b.max, s);
 
-       // @method getNorthEast(): LatLng
-       // Returns the north-east point of the bounds.
-       getNorthEast: function () {
-               return this._northEast;
+               return new Bounds(min, max);
        },
 
-       // @method getNorthWest(): LatLng
-       // Returns the north-west point of the bounds.
-       getNorthWest: function () {
-               return new L.LatLng(this.getNorth(), this.getWest());
-       },
+       // @method distance(latlng1: LatLng, latlng2: LatLng): Number
+       // Returns the distance between two geographical coordinates.
 
-       // @method getSouthEast(): LatLng
-       // Returns the south-east point of the bounds.
-       getSouthEast: function () {
-               return new L.LatLng(this.getSouth(), this.getEast());
-       },
+       // @property code: String
+       // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
+       //
+       // @property wrapLng: Number[]
+       // An array of two numbers defining whether the longitude (horizontal) coordinate
+       // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
+       // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
+       //
+       // @property wrapLat: Number[]
+       // Like `wrapLng`, but for the latitude (vertical) axis.
 
-       // @method getWest(): Number
-       // Returns the west longitude of the bounds
-       getWest: function () {
-               return this._southWest.lng;
-       },
+       // wrapLng: [min, max],
+       // wrapLat: [min, max],
 
-       // @method getSouth(): Number
-       // Returns the south latitude of the bounds
-       getSouth: function () {
-               return this._southWest.lat;
-       },
+       // @property infinite: Boolean
+       // If true, the coordinate space will be unbounded (infinite in both axes)
+       infinite: false,
 
-       // @method getEast(): Number
-       // Returns the east longitude of the bounds
-       getEast: function () {
-               return this._northEast.lng;
-       },
+       // @method wrapLatLng(latlng: LatLng): LatLng
+       // Returns a `LatLng` where lat and lng has been wrapped according to the
+       // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
+       wrapLatLng: function (latlng) {
+               var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
+                   lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
+                   alt = latlng.alt;
 
-       // @method getNorth(): Number
-       // Returns the north latitude of the bounds
-       getNorth: function () {
-               return this._northEast.lat;
+               return new LatLng(lat, lng, alt);
        },
 
-       // @method contains(otherBounds: LatLngBounds): Boolean
-       // Returns `true` if the rectangle contains the given one.
-
-       // @alternative
-       // @method contains (latlng: LatLng): Boolean
-       // Returns `true` if the rectangle contains the given point.
-       contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
-               if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
-                       obj = L.latLng(obj);
-               } else {
-                       obj = L.latLngBounds(obj);
-               }
-
-               var sw = this._southWest,
-                   ne = this._northEast,
-                   sw2, ne2;
+       // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
+       // Returns a `LatLngBounds` with the same size as the given one, ensuring
+       // that its center is within the CRS's bounds.
+       // Only accepts actual `L.LatLngBounds` instances, not arrays.
+       wrapLatLngBounds: function (bounds) {
+               var center = bounds.getCenter(),
+                   newCenter = this.wrapLatLng(center),
+                   latShift = center.lat - newCenter.lat,
+                   lngShift = center.lng - newCenter.lng;
 
-               if (obj instanceof L.LatLngBounds) {
-                       sw2 = obj.getSouthWest();
-                       ne2 = obj.getNorthEast();
-               } else {
-                       sw2 = ne2 = obj;
+               if (latShift === 0 && lngShift === 0) {
+                       return bounds;
                }
 
-               return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
-                      (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
-       },
-
-       // @method intersects(otherBounds: LatLngBounds): Boolean
-       // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
-       intersects: function (bounds) {
-               bounds = L.latLngBounds(bounds);
-
-               var sw = this._southWest,
-                   ne = this._northEast,
-                   sw2 = bounds.getSouthWest(),
-                   ne2 = bounds.getNorthEast(),
-
-                   latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
-                   lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
-
-               return latIntersects && lngIntersects;
-       },
-
-       // @method overlaps(otherBounds: Bounds): Boolean
-       // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
-       overlaps: function (bounds) {
-               bounds = L.latLngBounds(bounds);
-
-               var sw = this._southWest,
-                   ne = this._northEast,
-                   sw2 = bounds.getSouthWest(),
-                   ne2 = bounds.getNorthEast(),
-
-                   latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
-                   lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
-
-               return latOverlaps && lngOverlaps;
-       },
-
-       // @method toBBoxString(): String
-       // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
-       toBBoxString: function () {
-               return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
-       },
-
-       // @method equals(otherBounds: LatLngBounds): Boolean
-       // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
-       equals: function (bounds) {
-               if (!bounds) { return false; }
-
-               bounds = L.latLngBounds(bounds);
-
-               return this._southWest.equals(bounds.getSouthWest()) &&
-                      this._northEast.equals(bounds.getNorthEast());
-       },
-
-       // @method isValid(): Boolean
-       // Returns `true` if the bounds are properly initialized.
-       isValid: function () {
-               return !!(this._southWest && this._northEast);
-       }
-};
-
-// TODO International date line?
-
-// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
-// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
+               var sw = bounds.getSouthWest(),
+                   ne = bounds.getNorthEast(),
+                   newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
+                   newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
 
-// @alternative
-// @factory L.latLngBounds(latlngs: LatLng[])
-// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
-L.latLngBounds = function (a, b) {
-       if (a instanceof L.LatLngBounds) {
-               return a;
+               return new LatLngBounds(newSw, newNe);
        }
-       return new L.LatLngBounds(a, b);
 };
 
-
-
 /*
- * @namespace Projection
- * @section
- * Leaflet comes with a set of already defined Projections out of the box:
- *
- * @projection L.Projection.LonLat
+ * @namespace CRS
+ * @crs L.CRS.Earth
  *
- * Equirectangular, or Plate Carree projection — the most simple projection,
- * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
- * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
- * `EPSG:3395` and `Simple` CRS.
+ * Serves as the base for CRS that are global such that they cover the earth.
+ * Can only be used as the base for other CRS and cannot be used directly,
+ * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
+ * meters.
  */
 
-L.Projection = {};
-
-L.Projection.LonLat = {
-       project: function (latlng) {
-               return new L.Point(latlng.lng, latlng.lat);
-       },
-
-       unproject: function (point) {
-               return new L.LatLng(point.y, point.x);
-       },
+var Earth = extend({}, CRS, {
+       wrapLng: [-180, 180],
 
-       bounds: L.bounds([-180, -90], [180, 90])
-};
+       // Mean Earth Radius, as recommended for use by
+       // the International Union of Geodesy and Geophysics,
+       // see http://rosettacode.org/wiki/Haversine_formula
+       R: 6371000,
 
+       // distance between two geographical points using spherical law of cosines approximation
+       distance: function (latlng1, latlng2) {
+               var rad = Math.PI / 180,
+                   lat1 = latlng1.lat * rad,
+                   lat2 = latlng2.lat * rad,
+                   a = Math.sin(lat1) * Math.sin(lat2) +
+                       Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
 
+               return this.R * Math.acos(Math.min(a, 1));
+       }
+});
 
 /*
  * @namespace Projection
@@ -2003,7 +1597,7 @@ L.Projection.LonLat = {
  * a sphere. Used by the `EPSG:3857` CRS.
  */
 
-L.Projection.SphericalMercator = {
+var SphericalMercator = {
 
        R: 6378137,
        MAX_LATITUDE: 85.0511287798,
@@ -2014,7 +1608,7 @@ L.Projection.SphericalMercator = {
                    lat = Math.max(Math.min(max, latlng.lat), -max),
                    sin = Math.sin(lat * d);
 
-               return new L.Point(
+               return new Point(
                                this.R * latlng.lng * d,
                                this.R * Math.log((1 + sin) / (1 - sin)) / 2);
        },
@@ -2022,11230 +1616,11968 @@ L.Projection.SphericalMercator = {
        unproject: function (point) {
                var d = 180 / Math.PI;
 
-               return new L.LatLng(
+               return new LatLng(
                        (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
                        point.x * d / this.R);
        },
 
        bounds: (function () {
                var d = 6378137 * Math.PI;
-               return L.bounds([-d, -d], [d, d]);
+               return new Bounds([-d, -d], [d, d]);
        })()
 };
 
-
-
 /*
- * @class CRS
- * @aka L.CRS
- * Abstract class that defines coordinate reference systems for projecting
- * geographical points into pixel (screen) coordinates and back (and to
- * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
- * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
+ * @class Transformation
+ * @aka L.Transformation
  *
- * Leaflet defines the most usual CRSs by default. If you want to use a
- * CRS not defined by default, take a look at the
- * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
+ * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
+ * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
+ * the reverse. Used by Leaflet in its projections code.
+ *
+ * @example
+ *
+ * ```js
+ * var transformation = L.transformation(2, 5, -1, 10),
+ *     p = L.point(1, 2),
+ *     p2 = transformation.transform(p), //  L.point(7, 8)
+ *     p3 = transformation.untransform(p2); //  L.point(1, 2)
+ * ```
  */
 
-L.CRS = {
-       // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
-       // Projects geographical coordinates into pixel coordinates for a given zoom.
-       latLngToPoint: function (latlng, zoom) {
-               var projectedPoint = this.projection.project(latlng),
-                   scale = this.scale(zoom);
-
-               return this.transformation._transform(projectedPoint, scale);
-       },
 
-       // @method pointToLatLng(point: Point, zoom: Number): LatLng
-       // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
-       // zoom into geographical coordinates.
-       pointToLatLng: function (point, zoom) {
-               var scale = this.scale(zoom),
-                   untransformedPoint = this.transformation.untransform(point, scale);
+// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
+// Creates a `Transformation` object with the given coefficients.
+function Transformation(a, b, c, d) {
+       if (isArray(a)) {
+               // use array properties
+               this._a = a[0];
+               this._b = a[1];
+               this._c = a[2];
+               this._d = a[3];
+               return;
+       }
+       this._a = a;
+       this._b = b;
+       this._c = c;
+       this._d = d;
+}
 
-               return this.projection.unproject(untransformedPoint);
+Transformation.prototype = {
+       // @method transform(point: Point, scale?: Number): Point
+       // Returns a transformed point, optionally multiplied by the given scale.
+       // Only accepts actual `L.Point` instances, not arrays.
+       transform: function (point, scale) { // (Point, Number) -> Point
+               return this._transform(point.clone(), scale);
        },
 
-       // @method project(latlng: LatLng): Point
-       // Projects geographical coordinates into coordinates in units accepted for
-       // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
-       project: function (latlng) {
-               return this.projection.project(latlng);
+       // destructive transform (faster)
+       _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;
        },
 
-       // @method unproject(point: Point): LatLng
-       // Given a projected coordinate returns the corresponding LatLng.
-       // The inverse of `project`.
-       unproject: function (point) {
-               return this.projection.unproject(point);
-       },
+       // @method untransform(point: Point, scale?: Number): Point
+       // Returns the reverse transformation of the given point, optionally divided
+       // by the given scale. Only accepts actual `L.Point` instances, not arrays.
+       untransform: function (point, scale) {
+               scale = scale || 1;
+               return new Point(
+                       (point.x / scale - this._b) / this._a,
+                       (point.y / scale - this._d) / this._c);
+       }
+};
 
-       // @method scale(zoom: Number): Number
-       // Returns the scale used when transforming projected coordinates into
-       // pixel coordinates for a particular zoom. For example, it returns
-       // `256 * 2^zoom` for Mercator-based CRS.
-       scale: function (zoom) {
-               return 256 * Math.pow(2, zoom);
-       },
+// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
 
-       // @method zoom(scale: Number): Number
-       // Inverse of `scale()`, returns the zoom level corresponding to a scale
-       // factor of `scale`.
-       zoom: function (scale) {
-               return Math.log(scale / 256) / Math.LN2;
-       },
+// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
+// Instantiates a Transformation object with the given coefficients.
 
-       // @method getProjectedBounds(zoom: Number): Bounds
-       // Returns the projection's bounds scaled and transformed for the provided `zoom`.
-       getProjectedBounds: function (zoom) {
-               if (this.infinite) { return null; }
+// @alternative
+// @factory L.transformation(coefficients: Array): Transformation
+// Expects an coeficients array of the form
+// `[a: Number, b: Number, c: Number, d: Number]`.
 
-               var b = this.projection.bounds,
-                   s = this.scale(zoom),
-                   min = this.transformation.transform(b.min, s),
-                   max = this.transformation.transform(b.max, s);
+function toTransformation(a, b, c, d) {
+       return new Transformation(a, b, c, d);
+}
 
-               return L.bounds(min, max);
-       },
+/*
+ * @namespace CRS
+ * @crs L.CRS.EPSG3857
+ *
+ * The most common CRS for online maps, used by almost all free and commercial
+ * tile providers. Uses Spherical Mercator projection. Set in by default in
+ * Map's `crs` option.
+ */
 
-       // @method distance(latlng1: LatLng, latlng2: LatLng): Number
-       // Returns the distance between two geographical coordinates.
+var EPSG3857 = extend({}, Earth, {
+       code: 'EPSG:3857',
+       projection: SphericalMercator,
 
-       // @property code: String
-       // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
-       //
-       // @property wrapLng: Number[]
-       // An array of two numbers defining whether the longitude (horizontal) coordinate
-       // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
-       // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
-       //
-       // @property wrapLat: Number[]
-       // Like `wrapLng`, but for the latitude (vertical) axis.
+       transformation: (function () {
+               var scale = 0.5 / (Math.PI * SphericalMercator.R);
+               return toTransformation(scale, 0.5, -scale, 0.5);
+       }())
+});
 
-       // wrapLng: [min, max],
-       // wrapLat: [min, max],
+var EPSG900913 = extend({}, EPSG3857, {
+       code: 'EPSG:900913'
+});
 
-       // @property infinite: Boolean
-       // If true, the coordinate space will be unbounded (infinite in both axes)
-       infinite: false,
+// @namespace SVG; @section
+// There are several static functions which can be called without instantiating L.SVG:
 
-       // @method wrapLatLng(latlng: LatLng): LatLng
-       // Returns a `LatLng` where lat and lng has been wrapped according to the
-       // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
-       // Only accepts actual `L.LatLng` instances, not arrays.
-       wrapLatLng: function (latlng) {
-               var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
-                   lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
-                   alt = latlng.alt;
+// @function create(name: String): SVGElement
+// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
+// corresponding to the class name passed. For example, using 'line' will return
+// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
+function svgCreate(name) {
+       return document.createElementNS('http://www.w3.org/2000/svg', name);
+}
 
-               return L.latLng(lat, lng, alt);
-       },
+// @function pointsToPath(rings: Point[], closed: Boolean): String
+// Generates a SVG path string for multiple rings, with each ring turning
+// into "M..L..L.." instructions
+function pointsToPath(rings, closed) {
+       var str = '',
+       i, j, len, len2, points, p;
 
-       // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
-       // Returns a `LatLngBounds` with the same size as the given one, ensuring
-       // that its center is within the CRS's bounds.
-       // Only accepts actual `L.LatLngBounds` instances, not arrays.
-       wrapLatLngBounds: function (bounds) {
-               var center = bounds.getCenter(),
-                   newCenter = this.wrapLatLng(center),
-                   latShift = center.lat - newCenter.lat,
-                   lngShift = center.lng - newCenter.lng;
+       for (i = 0, len = rings.length; i < len; i++) {
+               points = rings[i];
 
-               if (latShift === 0 && lngShift === 0) {
-                       return bounds;
+               for (j = 0, len2 = points.length; j < len2; j++) {
+                       p = points[j];
+                       str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
                }
 
-               var sw = bounds.getSouthWest(),
-                   ne = bounds.getNorthEast(),
-                   newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
-                   newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
-
-               return new L.LatLngBounds(newSw, newNe);
+               // closes the ring for polygons; "x" is VML syntax
+               str += closed ? (svg ? 'z' : 'x') : '';
        }
-};
-
 
+       // SVG complains about empty path strings
+       return str || 'M0 0';
+}
 
 /*
- * @namespace CRS
- * @crs L.CRS.Simple
+ * @namespace Browser
+ * @aka L.Browser
  *
- * A simple CRS that maps longitude and latitude into `x` and `y` directly.
- * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
- * axis should still be inverted (going from bottom to top). `distance()` returns
- * simple euclidean distance.
+ * A namespace with static properties for browser/feature detection used by Leaflet internally.
+ *
+ * @example
+ *
+ * ```js
+ * if (L.Browser.ielt9) {
+ *   alert('Upgrade your browser, dude!');
+ * }
+ * ```
  */
 
-L.CRS.Simple = L.extend({}, L.CRS, {
-       projection: L.Projection.LonLat,
-       transformation: new L.Transformation(1, 0, -1, 0),
+var style$1 = document.documentElement.style;
 
-       scale: function (zoom) {
-               return Math.pow(2, zoom);
-       },
+// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
+var ie = 'ActiveXObject' in window;
 
-       zoom: function (scale) {
-               return Math.log(scale) / Math.LN2;
-       },
+// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
+var ielt9 = ie && !document.addEventListener;
 
-       distance: function (latlng1, latlng2) {
-               var dx = latlng2.lng - latlng1.lng,
-                   dy = latlng2.lat - latlng1.lat;
+// @property edge: Boolean; `true` for the Edge web browser.
+var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
 
-               return Math.sqrt(dx * dx + dy * dy);
-       },
+// @property webkit: Boolean;
+// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
+var webkit = userAgentContains('webkit');
 
-       infinite: true
-});
+// @property android: Boolean
+// `true` for any browser running on an Android platform.
+var android = userAgentContains('android');
 
+// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
+var android23 = userAgentContains('android 2') || userAgentContains('android 3');
 
+// @property opera: Boolean; `true` for the Opera browser
+var opera = !!window.opera;
 
-/*
- * @namespace CRS
- * @crs L.CRS.Earth
- *
- * Serves as the base for CRS that are global such that they cover the earth.
- * Can only be used as the base for other CRS and cannot be used directly,
- * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
- * meters.
- */
+// @property chrome: Boolean; `true` for the Chrome browser.
+var chrome = userAgentContains('chrome');
 
-L.CRS.Earth = L.extend({}, L.CRS, {
-       wrapLng: [-180, 180],
+// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
+var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
 
-       // Mean Earth Radius, as recommended for use by
-       // the International Union of Geodesy and Geophysics,
-       // see http://rosettacode.org/wiki/Haversine_formula
-       R: 6371000,
+// @property safari: Boolean; `true` for the Safari browser.
+var safari = !chrome && userAgentContains('safari');
 
-       // distance between two geographical points using spherical law of cosines approximation
-       distance: function (latlng1, latlng2) {
-               var rad = Math.PI / 180,
-                   lat1 = latlng1.lat * rad,
-                   lat2 = latlng2.lat * rad,
-                   a = Math.sin(lat1) * Math.sin(lat2) +
-                       Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
+var phantom = userAgentContains('phantom');
 
-               return this.R * Math.acos(Math.min(a, 1));
-       }
-});
+// @property opera12: Boolean
+// `true` for the Opera browser supporting CSS transforms (version 12 or later).
+var opera12 = 'OTransition' in style$1;
 
+// @property win: Boolean; `true` when the browser is running in a Windows platform
+var win = navigator.platform.indexOf('Win') === 0;
 
+// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
+var ie3d = ie && ('transition' in style$1);
 
-/*
- * @namespace CRS
- * @crs L.CRS.EPSG3857
- *
- * The most common CRS for online maps, used by almost all free and commercial
- * tile providers. Uses Spherical Mercator projection. Set in by default in
- * Map's `crs` option.
- */
+// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
+var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
 
-L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
-       code: 'EPSG:3857',
-       projection: L.Projection.SphericalMercator,
+// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
+var gecko3d = 'MozPerspective' in style$1;
 
-       transformation: (function () {
-               var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
-               return new L.Transformation(scale, 0.5, -scale, 0.5);
-       }())
-});
+// @property any3d: Boolean
+// `true` for all browsers supporting CSS transforms.
+var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
 
-L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
-       code: 'EPSG:900913'
-});
+// @property mobile: Boolean; `true` for all browsers running in a mobile device.
+var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
 
+// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
+var mobileWebkit = mobile && webkit;
 
+// @property mobileWebkit3d: Boolean
+// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
+var mobileWebkit3d = mobile && webkit3d;
 
-/*
- * @namespace CRS
- * @crs L.CRS.EPSG4326
- *
- * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
- *
- * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
- * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
- * with this CRS, ensure that there are two 256x256 pixel tiles covering the
- * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
- * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
- */
+// @property msPointer: Boolean
+// `true` for browsers implementing the Microsoft touch events model (notably IE10).
+var msPointer = !window.PointerEvent && window.MSPointerEvent;
 
-L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
-       code: 'EPSG:4326',
-       projection: L.Projection.LonLat,
-       transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
-});
+// @property pointer: Boolean
+// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
+var pointer = !!(window.PointerEvent || msPointer);
 
+// @property touch: Boolean
+// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
+// This does not necessarily mean that the browser is running in a computer with
+// a touchscreen, it only means that the browser is capable of understanding
+// touch events.
+var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
+               (window.DocumentTouch && document instanceof window.DocumentTouch));
 
+// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
+var mobileOpera = mobile && opera;
 
-/*
- * @class Map
- * @aka L.Map
- * @inherits Evented
- *
- * The central class of the API — it is used to create a map on a page and manipulate it.
- *
- * @example
- *
- * ```js
- * // initialize the map on the "map" div with a given center and zoom
- * var map = L.map('map', {
- *     center: [51.505, -0.09],
- *     zoom: 13
- * });
- * ```
- *
- */
+// @property mobileGecko: Boolean
+// `true` for gecko-based browsers running in a mobile device.
+var mobileGecko = mobile && gecko;
 
-L.Map = L.Evented.extend({
+// @property retina: Boolean
+// `true` for browsers on a high-resolution "retina" screen.
+var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
 
-       options: {
-               // @section Map State Options
-               // @option crs: CRS = L.CRS.EPSG3857
-               // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
-               // sure what it means.
-               crs: L.CRS.EPSG3857,
 
-               // @option center: LatLng = undefined
-               // Initial geographic center of the map
-               center: undefined,
+// @property canvas: Boolean
+// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
+var canvas = (function () {
+       return !!document.createElement('canvas').getContext;
+}());
 
-               // @option zoom: Number = undefined
-               // Initial map zoom level
-               zoom: undefined,
+// @property svg: Boolean
+// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
+var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
 
-               // @option minZoom: Number = undefined
-               // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
-               minZoom: undefined,
+// @property vml: Boolean
+// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
+var vml = !svg && (function () {
+       try {
+               var div = document.createElement('div');
+               div.innerHTML = '<v:shape adj="1"/>';
 
-               // @option maxZoom: Number = undefined
-               // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
-               maxZoom: undefined,
+               var shape = div.firstChild;
+               shape.style.behavior = 'url(#default#VML)';
 
-               // @option layers: Layer[] = []
-               // Array of layers that will be added to the map initially
-               layers: [],
+               return shape && (typeof shape.adj === 'object');
 
-               // @option maxBounds: LatLngBounds = null
-               // When this option is set, the map restricts the view to the given
-               // geographical bounds, bouncing the user back if the user tries to pan
-               // outside the view. To set the restriction dynamically, use
-               // [`setMaxBounds`](#map-setmaxbounds) method.
-               maxBounds: undefined,
+       } catch (e) {
+               return false;
+       }
+}());
 
-               // @option renderer: Renderer = *
-               // The default method for drawing vector layers on the map. `L.SVG`
-               // or `L.Canvas` by default depending on browser support.
-               renderer: undefined,
 
+function userAgentContains(str) {
+       return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
+}
 
-               // @section Animation Options
-               // @option zoomAnimation: Boolean = true
-               // Whether the map zoom animation is enabled. By default it's enabled
-               // in all browsers that support CSS3 Transitions except Android.
-               zoomAnimation: true,
 
-               // @option zoomAnimationThreshold: Number = 4
-               // Won't animate zoom if the zoom difference exceeds this value.
-               zoomAnimationThreshold: 4,
+var Browser = (Object.freeze || Object)({
+       ie: ie,
+       ielt9: ielt9,
+       edge: edge,
+       webkit: webkit,
+       android: android,
+       android23: android23,
+       opera: opera,
+       chrome: chrome,
+       gecko: gecko,
+       safari: safari,
+       phantom: phantom,
+       opera12: opera12,
+       win: win,
+       ie3d: ie3d,
+       webkit3d: webkit3d,
+       gecko3d: gecko3d,
+       any3d: any3d,
+       mobile: mobile,
+       mobileWebkit: mobileWebkit,
+       mobileWebkit3d: mobileWebkit3d,
+       msPointer: msPointer,
+       pointer: pointer,
+       touch: touch,
+       mobileOpera: mobileOpera,
+       mobileGecko: mobileGecko,
+       retina: retina,
+       canvas: canvas,
+       svg: svg,
+       vml: vml
+});
 
-               // @option fadeAnimation: Boolean = true
-               // Whether the tile fade animation is enabled. By default it's enabled
-               // in all browsers that support CSS3 Transitions except Android.
-               fadeAnimation: true,
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
 
-               // @option markerZoomAnimation: Boolean = true
-               // Whether markers animate their zoom with the zoom animation, if disabled
-               // they will disappear for the length of the animation. By default it's
-               // enabled in all browsers that support CSS3 Transitions except Android.
-               markerZoomAnimation: true,
 
-               // @option transform3DLimit: Number = 2^23
-               // Defines the maximum size of a CSS translation transform. The default
-               // value should not be changed unless a web browser positions layers in
-               // the wrong place after doing a large `panBy`.
-               transform3DLimit: 8388608, // Precision limit of a 32-bit float
+var POINTER_DOWN =   msPointer ? 'MSPointerDown'   : 'pointerdown';
+var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
+var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
+var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
+var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
+var _pointers = {};
+var _pointerDocListener = false;
 
-               // @section Interaction Options
-               // @option zoomSnap: Number = 1
-               // Forces the map's zoom level to always be a multiple of this, particularly
-               // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
-               // By default, the zoom level snaps to the nearest integer; lower values
-               // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
-               // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
-               zoomSnap: 1,
+// DomEvent.DoubleTap needs to know about this
+var _pointersCount = 0;
 
-               // @option zoomDelta: Number = 1
-               // Controls how much the map's zoom level will change after a
-               // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
-               // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
-               // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
-               zoomDelta: 1,
+// Provides a touch events wrapper for (ms)pointer events.
+// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
 
-               // @option trackResize: Boolean = true
-               // Whether the map automatically handles browser window resize to update itself.
-               trackResize: true
-       },
+function addPointerListener(obj, type, handler, id) {
+       if (type === 'touchstart') {
+               _addPointerStart(obj, handler, id);
 
-       initialize: function (id, options) { // (HTMLElement or String, Object)
-               options = L.setOptions(this, options);
+       } else if (type === 'touchmove') {
+               _addPointerMove(obj, handler, id);
 
-               this._initContainer(id);
-               this._initLayout();
+       } else if (type === 'touchend') {
+               _addPointerEnd(obj, handler, id);
+       }
 
-               // hack for https://github.com/Leaflet/Leaflet/issues/1980
-               this._onResize = L.bind(this._onResize, this);
+       return this;
+}
 
-               this._initEvents();
+function removePointerListener(obj, type, id) {
+       var handler = obj['_leaflet_' + type + id];
 
-               if (options.maxBounds) {
-                       this.setMaxBounds(options.maxBounds);
-               }
+       if (type === 'touchstart') {
+               obj.removeEventListener(POINTER_DOWN, handler, false);
 
-               if (options.zoom !== undefined) {
-                       this._zoom = this._limitZoom(options.zoom);
-               }
+       } else if (type === 'touchmove') {
+               obj.removeEventListener(POINTER_MOVE, handler, false);
 
-               if (options.center && options.zoom !== undefined) {
-                       this.setView(L.latLng(options.center), options.zoom, {reset: true});
+       } else if (type === 'touchend') {
+               obj.removeEventListener(POINTER_UP, handler, false);
+               obj.removeEventListener(POINTER_CANCEL, handler, false);
+       }
+
+       return this;
+}
+
+function _addPointerStart(obj, handler, id) {
+       var onDown = bind(function (e) {
+               if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+                       // In IE11, some touch events needs to fire for form controls, or
+                       // the controls will stop working. We keep a whitelist of tag names that
+                       // need these events. For other target tags, we prevent default on the event.
+                       if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
+                               preventDefault(e);
+                       } else {
+                               return;
+                       }
                }
 
-               this._handlers = [];
-               this._layers = {};
-               this._zoomBoundLayers = {};
-               this._sizeChanged = true;
+               _handlePointer(e, handler);
+       });
 
-               this.callInitHooks();
+       obj['_leaflet_touchstart' + id] = onDown;
+       obj.addEventListener(POINTER_DOWN, onDown, false);
 
-               // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
-               this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
-                               this.options.zoomAnimation;
+       // need to keep track of what pointers and how many are active to provide e.touches emulation
+       if (!_pointerDocListener) {
+               // we listen documentElement as any drags that end by moving the touch off the screen get fired there
+               document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
+               document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
+               document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
+               document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
 
-               // zoom transitions run with the same duration for all layers, so if one of transitionend events
-               // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
-               if (this._zoomAnimated) {
-                       this._createAnimProxy();
-                       L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
-               }
+               _pointerDocListener = true;
+       }
+}
 
-               this._addLayers(this.options.layers);
-       },
+function _globalPointerDown(e) {
+       _pointers[e.pointerId] = e;
+       _pointersCount++;
+}
 
+function _globalPointerMove(e) {
+       if (_pointers[e.pointerId]) {
+               _pointers[e.pointerId] = e;
+       }
+}
 
-       // @section Methods for modifying map state
+function _globalPointerUp(e) {
+       delete _pointers[e.pointerId];
+       _pointersCount--;
+}
 
-       // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
-       // Sets the view of the map (geographical center and zoom) with the given
-       // animation options.
-       setView: function (center, zoom, options) {
+function _handlePointer(e, handler) {
+       e.touches = [];
+       for (var i in _pointers) {
+               e.touches.push(_pointers[i]);
+       }
+       e.changedTouches = [e];
 
-               zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
-               center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
-               options = options || {};
+       handler(e);
+}
 
-               this._stop();
+function _addPointerMove(obj, handler, id) {
+       var onMove = function (e) {
+               // don't fire touch moves when mouse isn't down
+               if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
 
-               if (this._loaded && !options.reset && options !== true) {
+               _handlePointer(e, handler);
+       };
 
-                       if (options.animate !== undefined) {
-                               options.zoom = L.extend({animate: options.animate}, options.zoom);
-                               options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
-                       }
+       obj['_leaflet_touchmove' + id] = onMove;
+       obj.addEventListener(POINTER_MOVE, onMove, false);
+}
 
-                       // try animating pan or zoom
-                       var moved = (this._zoom !== zoom) ?
-                               this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
-                               this._tryAnimatedPan(center, options.pan);
+function _addPointerEnd(obj, handler, id) {
+       var onUp = function (e) {
+               _handlePointer(e, handler);
+       };
 
-                       if (moved) {
-                               // prevent resize handler call, the view will refresh after animation anyway
-                               clearTimeout(this._sizeTimer);
-                               return this;
-                       }
-               }
+       obj['_leaflet_touchend' + id] = onUp;
+       obj.addEventListener(POINTER_UP, onUp, false);
+       obj.addEventListener(POINTER_CANCEL, onUp, false);
+}
 
-               // animation didn't start, just reset the map view
-               this._resetView(center, zoom);
+/*
+ * Extends the event handling code with double tap support for mobile browsers.
+ */
 
-               return this;
-       },
+var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
+var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
+var _pre = '_leaflet_';
 
-       // @method setZoom(zoom: Number, options: Zoom/pan options): this
-       // Sets the zoom of the map.
-       setZoom: function (zoom, options) {
-               if (!this._loaded) {
-                       this._zoom = zoom;
-                       return this;
+// inspired by Zepto touch code by Thomas Fuchs
+function addDoubleTapListener(obj, handler, id) {
+       var last, touch$$1,
+           doubleTap = false,
+           delay = 250;
+
+       function onTouchStart(e) {
+               var count;
+
+               if (pointer) {
+                       if ((!edge) || e.pointerType === 'mouse') { return; }
+                       count = _pointersCount;
+               } else {
+                       count = e.touches.length;
                }
-               return this.setView(this.getCenter(), zoom, {zoom: options});
-       },
 
-       // @method zoomIn(delta?: Number, options?: Zoom options): this
-       // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
-       zoomIn: function (delta, options) {
-               delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
-               return this.setZoom(this._zoom + delta, options);
-       },
+               if (count > 1) { return; }
 
-       // @method zoomOut(delta?: Number, options?: Zoom options): this
-       // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
-       zoomOut: function (delta, options) {
-               delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
-               return this.setZoom(this._zoom - delta, options);
-       },
+               var now = Date.now(),
+                   delta = now - (last || now);
 
-       // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
-       // Zooms the map while keeping a specified geographical point on the map
-       // stationary (e.g. used internally for scroll zoom and double-click zoom).
-       // @alternative
-       // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
-       // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
-       setZoomAround: function (latlng, zoom, options) {
-               var scale = this.getZoomScale(zoom),
-                   viewHalf = this.getSize().divideBy(2),
-                   containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
+               touch$$1 = e.touches ? e.touches[0] : e;
+               doubleTap = (delta > 0 && delta <= delay);
+               last = now;
+       }
 
-                   centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
-                   newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
+       function onTouchEnd(e) {
+               if (doubleTap && !touch$$1.cancelBubble) {
+                       if (pointer) {
+                               if ((!edge) || e.pointerType === 'mouse') { return; }
+                               // work around .type being readonly with MSPointer* events
+                               var newTouch = {},
+                                   prop, i;
+
+                               for (i in touch$$1) {
+                                       prop = touch$$1[i];
+                                       newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
+                               }
+                               touch$$1 = newTouch;
+                       }
+                       touch$$1.type = 'dblclick';
+                       handler(touch$$1);
+                       last = null;
+               }
+       }
 
-               return this.setView(newCenter, zoom, {zoom: options});
-       },
+       obj[_pre + _touchstart + id] = onTouchStart;
+       obj[_pre + _touchend + id] = onTouchEnd;
+       obj[_pre + 'dblclick' + id] = handler;
 
-       _getBoundsCenterZoom: function (bounds, options) {
+       obj.addEventListener(_touchstart, onTouchStart, false);
+       obj.addEventListener(_touchend, onTouchEnd, false);
 
-               options = options || {};
-               bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
+       // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
+       // the browser doesn't fire touchend/pointerup events but does fire
+       // native dblclicks. See #4127.
+       // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
+       obj.addEventListener('dblclick', handler, false);
 
-               var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
-                   paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
+       return this;
+}
 
-                   zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
+function removeDoubleTapListener(obj, id) {
+       var touchstart = obj[_pre + _touchstart + id],
+           touchend = obj[_pre + _touchend + id],
+           dblclick = obj[_pre + 'dblclick' + id];
 
-               zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
+       obj.removeEventListener(_touchstart, touchstart, false);
+       obj.removeEventListener(_touchend, touchend, false);
+       if (!edge) {
+               obj.removeEventListener('dblclick', dblclick, false);
+       }
 
-               var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
+       return this;
+}
 
-                   swPoint = this.project(bounds.getSouthWest(), zoom),
-                   nePoint = this.project(bounds.getNorthEast(), zoom),
-                   center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
+/*
+ * @namespace DomEvent
+ * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
+ */
 
-               return {
-                       center: center,
-                       zoom: zoom
-               };
-       },
+// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
 
-       // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
-       // Sets a map view that contains the given geographical bounds with the
-       // maximum zoom level possible.
-       fitBounds: function (bounds, options) {
+// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Adds a listener function (`fn`) to a particular DOM event type of the
+// element `el`. You can optionally specify the context of the listener
+// (object the `this` keyword will point to). You can also pass several
+// space-separated types (e.g. `'click dblclick'`).
 
-               bounds = L.latLngBounds(bounds);
+// @alternative
+// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
+// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function on(obj, types, fn, context) {
 
-               if (!bounds.isValid()) {
-                       throw new Error('Bounds are not valid.');
+       if (typeof types === 'object') {
+               for (var type in types) {
+                       addOne(obj, type, types[type], fn);
                }
+       } else {
+               types = splitWords(types);
 
-               var target = this._getBoundsCenterZoom(bounds, options);
-               return this.setView(target.center, target.zoom, options);
-       },
+               for (var i = 0, len = types.length; i < len; i++) {
+                       addOne(obj, types[i], fn, context);
+               }
+       }
 
-       // @method fitWorld(options?: fitBounds options): this
-       // Sets a map view that mostly contains the whole world with the maximum
-       // zoom level possible.
-       fitWorld: function (options) {
-               return this.fitBounds([[-90, -180], [90, 180]], options);
-       },
+       return this;
+}
 
-       // @method panTo(latlng: LatLng, options?: Pan options): this
-       // Pans the map to a given center.
-       panTo: function (center, options) { // (LatLng)
-               return this.setView(center, this._zoom, {pan: options});
-       },
+var eventsKey = '_leaflet_events';
 
-       // @method panBy(offset: Point): this
-       // Pans the map by a given number of pixels (animated).
-       panBy: function (offset, options) {
-               offset = L.point(offset).round();
-               options = options || {};
+// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Removes a previously added listener function. If no function is specified,
+// it will remove all the listeners of that particular DOM event from the element.
+// Note that if you passed a custom context to on, you must pass the same
+// context to `off` in order to remove the listener.
 
-               if (!offset.x && !offset.y) {
-                       return this.fire('moveend');
-               }
-               // If we pan too far, Chrome gets issues with tiles
-               // and makes them disappear or appear in the wrong place (slightly offset) #2602
-               if (options.animate !== true && !this.getSize().contains(offset)) {
-                       this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
-                       return this;
-               }
+// @alternative
+// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
+// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
 
-               if (!this._panAnim) {
-                       this._panAnim = new L.PosAnimation();
+// @alternative
+// @function off(el: HTMLElement): this
+// Removes all known event listeners
+function off(obj, types, fn, context) {
 
-                       this._panAnim.on({
-                               'step': this._onPanTransitionStep,
-                               'end': this._onPanTransitionEnd
-                       }, this);
+       if (typeof types === 'object') {
+               for (var type in types) {
+                       removeOne(obj, type, types[type], fn);
                }
+       } else if (types) {
+               types = splitWords(types);
 
-               // don't fire movestart if animating inertia
-               if (!options.noMoveStart) {
-                       this.fire('movestart');
+               for (var i = 0, len = types.length; i < len; i++) {
+                       removeOne(obj, types[i], fn, context);
                }
+       } else {
+               for (var j in obj[eventsKey]) {
+                       removeOne(obj, j, obj[eventsKey][j]);
+               }
+               delete obj[eventsKey];
+       }
+}
 
-               // animate pan unless animate: false specified
-               if (options.animate !== false) {
-                       L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
+function addOne(obj, type, fn, context) {
+       var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
 
-                       var newPos = this._getMapPanePos().subtract(offset).round();
-                       this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
-               } else {
-                       this._rawPanBy(offset);
-                       this.fire('move').fire('moveend');
-               }
+       if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
 
-               return this;
-       },
+       var handler = function (e) {
+               return fn.call(context || obj, e || window.event);
+       };
 
-       // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
-       // Sets the view of the map (geographical center and zoom) performing a smooth
-       // pan-zoom animation.
-       flyTo: function (targetCenter, targetZoom, options) {
+       var originalHandler = handler;
 
-               options = options || {};
-               if (options.animate === false || !L.Browser.any3d) {
-                       return this.setView(targetCenter, targetZoom, options);
+       if (pointer && type.indexOf('touch') === 0) {
+               // Needs DomEvent.Pointer.js
+               addPointerListener(obj, type, handler, id);
+
+       } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
+                  !(pointer && chrome)) {
+               // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
+               // See #5180
+               addDoubleTapListener(obj, handler, id);
+
+       } else if ('addEventListener' in obj) {
+
+               if (type === 'mousewheel') {
+                       obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+
+               } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+                       handler = function (e) {
+                               e = e || window.event;
+                               if (isExternalTarget(obj, e)) {
+                                       originalHandler(e);
+                               }
+                       };
+                       obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
+
+               } else {
+                       if (type === 'click' && android) {
+                               handler = function (e) {
+                                       filterClick(e, originalHandler);
+                               };
+                       }
+                       obj.addEventListener(type, handler, false);
                }
 
-               this._stop();
+       } else if ('attachEvent' in obj) {
+               obj.attachEvent('on' + type, handler);
+       }
 
-               var from = this.project(this.getCenter()),
-                   to = this.project(targetCenter),
-                   size = this.getSize(),
-                   startZoom = this._zoom;
+       obj[eventsKey] = obj[eventsKey] || {};
+       obj[eventsKey][id] = handler;
+}
 
-               targetCenter = L.latLng(targetCenter);
-               targetZoom = targetZoom === undefined ? startZoom : targetZoom;
+function removeOne(obj, type, fn, context) {
 
-               var w0 = Math.max(size.x, size.y),
-                   w1 = w0 * this.getZoomScale(startZoom, targetZoom),
-                   u1 = (to.distanceTo(from)) || 1,
-                   rho = 1.42,
-                   rho2 = rho * rho;
+       var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
+           handler = obj[eventsKey] && obj[eventsKey][id];
 
-               function r(i) {
-                       var s1 = i ? -1 : 1,
-                           s2 = i ? w1 : w0,
-                           t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
-                           b1 = 2 * s2 * rho2 * u1,
-                           b = t1 / b1,
-                           sq = Math.sqrt(b * b + 1) - b;
+       if (!handler) { return this; }
 
-                           // workaround for floating point precision bug when sq = 0, log = -Infinite,
-                           // thus triggering an infinite loop in flyTo
-                           var log = sq < 0.000000001 ? -18 : Math.log(sq);
+       if (pointer && type.indexOf('touch') === 0) {
+               removePointerListener(obj, type, id);
 
-                       return log;
+       } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
+               removeDoubleTapListener(obj, id);
+
+       } else if ('removeEventListener' in obj) {
+
+               if (type === 'mousewheel') {
+                       obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+
+               } else {
+                       obj.removeEventListener(
+                               type === 'mouseenter' ? 'mouseover' :
+                               type === 'mouseleave' ? 'mouseout' : type, handler, false);
                }
 
-               function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
-               function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
-               function tanh(n) { return sinh(n) / cosh(n); }
+       } else if ('detachEvent' in obj) {
+               obj.detachEvent('on' + type, handler);
+       }
 
-               var r0 = r(0);
+       obj[eventsKey][id] = null;
+}
 
-               function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
-               function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
+// @function stopPropagation(ev: DOMEvent): this
+// Stop the given event from propagation to parent elements. Used inside the listener functions:
+// ```js
+// L.DomEvent.on(div, 'click', function (ev) {
+//     L.DomEvent.stopPropagation(ev);
+// });
+// ```
+function stopPropagation(e) {
 
-               function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
+       if (e.stopPropagation) {
+               e.stopPropagation();
+       } else if (e.originalEvent) {  // In case of Leaflet event.
+               e.originalEvent._stopped = true;
+       } else {
+               e.cancelBubble = true;
+       }
+       skipped(e);
 
-               var start = Date.now(),
-                   S = (r(1) - r0) / rho,
-                   duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
+       return this;
+}
 
-               function frame() {
-                       var t = (Date.now() - start) / duration,
-                           s = easeOut(t) * S;
+// @function disableScrollPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
+function disableScrollPropagation(el) {
+       return addOne(el, 'mousewheel', stopPropagation);
+}
 
-                       if (t <= 1) {
-                               this._flyToFrame = L.Util.requestAnimFrame(frame, this);
+// @function disableClickPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
+// `'mousedown'` and `'touchstart'` events (plus browser variants).
+function disableClickPropagation(el) {
+       on(el, 'mousedown touchstart dblclick', stopPropagation);
+       addOne(el, 'click', fakeStop);
+       return this;
+}
 
-                               this._move(
-                                       this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
-                                       this.getScaleZoom(w0 / w(s), startZoom),
-                                       {flyTo: true});
+// @function preventDefault(ev: DOMEvent): this
+// Prevents the default action of the DOM Event `ev` from happening (such as
+// following a link in the href of the a element, or doing a POST request
+// with page reload when a `<form>` is submitted).
+// Use it inside listener functions.
+function preventDefault(e) {
+       if (e.preventDefault) {
+               e.preventDefault();
+       } else {
+               e.returnValue = false;
+       }
+       return this;
+}
 
-                       } else {
-                               this
-                                       ._move(targetCenter, targetZoom)
-                                       ._moveEnd(true);
-                       }
-               }
+// @function stop(ev): this
+// Does `stopPropagation` and `preventDefault` at the same time.
+function stop(e) {
+       preventDefault(e);
+       stopPropagation(e);
+       return this;
+}
 
-               this._moveStart(true);
+// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
+// Gets normalized mouse position from a DOM event relative to the
+// `container` or to the whole page if not specified.
+function getMousePosition(e, container) {
+       if (!container) {
+               return new Point(e.clientX, e.clientY);
+       }
 
-               frame.call(this);
-               return this;
-       },
+       var rect = container.getBoundingClientRect();
 
-       // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
-       // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
-       // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
-       flyToBounds: function (bounds, options) {
-               var target = this._getBoundsCenterZoom(bounds, options);
-               return this.flyTo(target.center, target.zoom, options);
-       },
+       return new Point(
+               e.clientX - rect.left - container.clientLeft,
+               e.clientY - rect.top - container.clientTop);
+}
 
-       // @method setMaxBounds(bounds: Bounds): this
-       // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
-       setMaxBounds: function (bounds) {
-               bounds = L.latLngBounds(bounds);
+// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
+// and Firefox scrolls device pixels, not CSS pixels
+var wheelPxFactor =
+       (win && chrome) ? 2 * window.devicePixelRatio :
+       gecko ? window.devicePixelRatio : 1;
+
+// @function getWheelDelta(ev: DOMEvent): Number
+// Gets normalized wheel delta from a mousewheel DOM event, in vertical
+// pixels scrolled (negative if scrolling down).
+// Events from pointing devices without precise scrolling are mapped to
+// a best guess of 60 pixels.
+function getWheelDelta(e) {
+       return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
+              (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
+              (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
+              (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
+              (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
+              e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
+              (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
+              e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
+              0;
+}
 
-               if (!bounds.isValid()) {
-                       this.options.maxBounds = null;
-                       return this.off('moveend', this._panInsideMaxBounds);
-               } else if (this.options.maxBounds) {
-                       this.off('moveend', this._panInsideMaxBounds);
-               }
+var skipEvents = {};
 
-               this.options.maxBounds = bounds;
+function fakeStop(e) {
+       // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
+       skipEvents[e.type] = true;
+}
 
-               if (this._loaded) {
-                       this._panInsideMaxBounds();
-               }
+function skipped(e) {
+       var events = skipEvents[e.type];
+       // reset when checking, as it's only used in map container and propagates outside of the map
+       skipEvents[e.type] = false;
+       return events;
+}
 
-               return this.on('moveend', this._panInsideMaxBounds);
-       },
+// check if element really left/entered the event target (for mouseenter/mouseleave)
+function isExternalTarget(el, e) {
 
-       // @method setMinZoom(zoom: Number): this
-       // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
-       setMinZoom: function (zoom) {
-               this.options.minZoom = zoom;
+       var related = e.relatedTarget;
 
-               if (this._loaded && this.getZoom() < this.options.minZoom) {
-                       return this.setZoom(zoom);
+       if (!related) { return true; }
+
+       try {
+               while (related && (related !== el)) {
+                       related = related.parentNode;
                }
+       } catch (err) {
+               return false;
+       }
+       return (related !== el);
+}
 
-               return this;
-       },
+var lastClick;
 
-       // @method setMaxZoom(zoom: Number): this
-       // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
-       setMaxZoom: function (zoom) {
-               this.options.maxZoom = zoom;
+// this is a horrible workaround for a bug in Android where a single touch triggers two click events
+function filterClick(e, handler) {
+       var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
+           elapsed = lastClick && (timeStamp - lastClick);
 
-               if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
-                       return this.setZoom(zoom);
-               }
+       // are they closer together than 500ms yet more than 100ms?
+       // Android typically triggers them ~300ms apart while multiple listeners
+       // on the same event should be triggered far faster;
+       // or check if click is simulated on the element, and if it is, reject any non-simulated events
 
-               return this;
-       },
+       if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
+               stop(e);
+               return;
+       }
+       lastClick = timeStamp;
 
-       // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
-       // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
-       panInsideBounds: function (bounds, options) {
-               this._enforcingBounds = true;
-               var center = this.getCenter(),
-                   newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
+       handler(e);
+}
 
-               if (!center.equals(newCenter)) {
-                       this.panTo(newCenter, options);
-               }
 
-               this._enforcingBounds = false;
-               return this;
-       },
 
-       // @method invalidateSize(options: Zoom/Pan options): this
-       // Checks if the map container size changed and updates the map if so —
-       // call it after you've changed the map size dynamically, also animating
-       // pan by default. If `options.pan` is `false`, panning will not occur.
-       // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
-       // that it doesn't happen often even if the method is called many
-       // times in a row.
 
-       // @alternative
-       // @method invalidateSize(animate: Boolean): this
-       // Checks if the map container size changed and updates the map if so —
-       // call it after you've changed the map size dynamically, also animating
-       // pan by default.
-       invalidateSize: function (options) {
-               if (!this._loaded) { return this; }
+var DomEvent = (Object.freeze || Object)({
+       on: on,
+       off: off,
+       stopPropagation: stopPropagation,
+       disableScrollPropagation: disableScrollPropagation,
+       disableClickPropagation: disableClickPropagation,
+       preventDefault: preventDefault,
+       stop: stop,
+       getMousePosition: getMousePosition,
+       getWheelDelta: getWheelDelta,
+       fakeStop: fakeStop,
+       skipped: skipped,
+       isExternalTarget: isExternalTarget,
+       addListener: on,
+       removeListener: off
+});
 
-               options = L.extend({
-                       animate: false,
-                       pan: true
-               }, options === true ? {animate: true} : options);
+/*
+ * @namespace DomUtil
+ *
+ * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
+ * tree, used by Leaflet internally.
+ *
+ * Most functions expecting or returning a `HTMLElement` also work for
+ * SVG elements. The only difference is that classes refer to CSS classes
+ * in HTML and SVG classes in SVG.
+ */
 
-               var oldSize = this.getSize();
-               this._sizeChanged = true;
-               this._lastCenter = null;
 
-               var newSize = this.getSize(),
-                   oldCenter = oldSize.divideBy(2).round(),
-                   newCenter = newSize.divideBy(2).round(),
-                   offset = oldCenter.subtract(newCenter);
+// @property TRANSFORM: String
+// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
+var TRANSFORM = testProp(
+       ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
 
-               if (!offset.x && !offset.y) { return this; }
+// 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
 
-               if (options.animate && options.pan) {
-                       this.panBy(offset);
+// @property TRANSITION: String
+// Vendor-prefixed transition style name.
+var TRANSITION = testProp(
+       ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
 
-               } else {
-                       if (options.pan) {
-                               this._rawPanBy(offset);
-                       }
+// @property TRANSITION_END: String
+// Vendor-prefixed transitionend event name.
+var TRANSITION_END =
+       TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
 
-                       this.fire('move');
 
-                       if (options.debounceMoveend) {
-                               clearTimeout(this._sizeTimer);
-                               this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
-                       } else {
-                               this.fire('moveend');
-                       }
-               }
+// @function get(id: String|HTMLElement): HTMLElement
+// Returns an element given its DOM id, or returns the element itself
+// if it was passed directly.
+function get(id) {
+       return typeof id === 'string' ? document.getElementById(id) : id;
+}
 
-               // @section Map state change events
-               // @event resize: ResizeEvent
-               // Fired when the map is resized.
-               return this.fire('resize', {
-                       oldSize: oldSize,
-                       newSize: newSize
-               });
-       },
+// @function getStyle(el: HTMLElement, styleAttrib: String): String
+// Returns the value for a certain style attribute on an element,
+// including computed values or values set through CSS.
+function getStyle(el, style) {
+       var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
 
-       // @section Methods for modifying map state
-       // @method stop(): this
-       // Stops the currently running `panTo` or `flyTo` animation, if any.
-       stop: function () {
-               this.setZoom(this._limitZoom(this._zoom));
-               if (!this.options.zoomSnap) {
-                       this.fire('viewreset');
-               }
-               return this._stop();
-       },
+       if ((!value || value === 'auto') && document.defaultView) {
+               var css = document.defaultView.getComputedStyle(el, null);
+               value = css ? css[style] : null;
+       }
+       return value === 'auto' ? null : value;
+}
 
-       // @section Geolocation methods
-       // @method locate(options?: Locate options): this
-       // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
-       // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
-       // and optionally sets the map view to the user's location with respect to
-       // detection accuracy (or to the world view if geolocation failed).
-       // Note that, if your page doesn't use HTTPS, this method will fail in
-       // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
-       // See `Locate options` for more details.
-       locate: function (options) {
+// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
+// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
+function create$1(tagName, className, container) {
+       var el = document.createElement(tagName);
+       el.className = className || '';
 
-               options = this._locateOptions = L.extend({
-                       timeout: 10000,
-                       watch: false
-                       // setView: false
-                       // maxZoom: <Number>
-                       // maximumAge: 0
-                       // enableHighAccuracy: false
-               }, options);
+       if (container) {
+               container.appendChild(el);
+       }
+       return el;
+}
 
-               if (!('geolocation' in navigator)) {
-                       this._handleGeolocationError({
-                               code: 0,
-                               message: 'Geolocation not supported.'
-                       });
-                       return this;
-               }
+// @function remove(el: HTMLElement)
+// Removes `el` from its parent element
+function remove(el) {
+       var parent = el.parentNode;
+       if (parent) {
+               parent.removeChild(el);
+       }
+}
 
-               var onResponse = L.bind(this._handleGeolocationResponse, this),
-                   onError = L.bind(this._handleGeolocationError, this);
+// @function empty(el: HTMLElement)
+// Removes all of `el`'s children elements from `el`
+function empty(el) {
+       while (el.firstChild) {
+               el.removeChild(el.firstChild);
+       }
+}
 
-               if (options.watch) {
-                       this._locationWatchId =
-                               navigator.geolocation.watchPosition(onResponse, onError, options);
-               } else {
-                       navigator.geolocation.getCurrentPosition(onResponse, onError, options);
-               }
-               return this;
-       },
+// @function toFront(el: HTMLElement)
+// Makes `el` the last child of its parent, so it renders in front of the other children.
+function toFront(el) {
+       var parent = el.parentNode;
+       if (parent.lastChild !== el) {
+               parent.appendChild(el);
+       }
+}
 
-       // @method stopLocate(): this
-       // Stops watching location previously initiated by `map.locate({watch: true})`
-       // and aborts resetting the map view if map.locate was called with
-       // `{setView: true}`.
-       stopLocate: function () {
-               if (navigator.geolocation && navigator.geolocation.clearWatch) {
-                       navigator.geolocation.clearWatch(this._locationWatchId);
-               }
-               if (this._locateOptions) {
-                       this._locateOptions.setView = false;
-               }
-               return this;
-       },
+// @function toBack(el: HTMLElement)
+// Makes `el` the first child of its parent, so it renders behind the other children.
+function toBack(el) {
+       var parent = el.parentNode;
+       if (parent.firstChild !== el) {
+               parent.insertBefore(el, parent.firstChild);
+       }
+}
 
-       _handleGeolocationError: function (error) {
-               var c = error.code,
-                   message = error.message ||
-                           (c === 1 ? 'permission denied' :
-                           (c === 2 ? 'position unavailable' : 'timeout'));
+// @function hasClass(el: HTMLElement, name: String): Boolean
+// Returns `true` if the element's class attribute contains `name`.
+function hasClass(el, name) {
+       if (el.classList !== undefined) {
+               return el.classList.contains(name);
+       }
+       var className = getClass(el);
+       return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+}
 
-               if (this._locateOptions.setView && !this._loaded) {
-                       this.fitWorld();
-               }
+// @function addClass(el: HTMLElement, name: String)
+// Adds `name` to the element's class attribute.
+function addClass(el, name) {
+       if (el.classList !== undefined) {
+               var classes = splitWords(name);
+               for (var i = 0, len = classes.length; i < len; i++) {
+                       el.classList.add(classes[i]);
+               }
+       } else if (!hasClass(el, name)) {
+               var className = getClass(el);
+               setClass(el, (className ? className + ' ' : '') + name);
+       }
+}
 
-               // @section Location events
-               // @event locationerror: ErrorEvent
-               // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
-               this.fire('locationerror', {
-                       code: c,
-                       message: 'Geolocation error: ' + message + '.'
-               });
-       },
+// @function removeClass(el: HTMLElement, name: String)
+// Removes `name` from the element's class attribute.
+function removeClass(el, name) {
+       if (el.classList !== undefined) {
+               el.classList.remove(name);
+       } else {
+               setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+       }
+}
 
-       _handleGeolocationResponse: function (pos) {
-               var lat = pos.coords.latitude,
-                   lng = pos.coords.longitude,
-                   latlng = new L.LatLng(lat, lng),
-                   bounds = latlng.toBounds(pos.coords.accuracy),
-                   options = this._locateOptions;
+// @function setClass(el: HTMLElement, name: String)
+// Sets the element's class.
+function setClass(el, name) {
+       if (el.className.baseVal === undefined) {
+               el.className = name;
+       } else {
+               // in case of SVG element
+               el.className.baseVal = name;
+       }
+}
 
-               if (options.setView) {
-                       var zoom = this.getBoundsZoom(bounds);
-                       this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
-               }
+// @function getClass(el: HTMLElement): String
+// Returns the element's class.
+function getClass(el) {
+       return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+}
 
-               var data = {
-                       latlng: latlng,
-                       bounds: bounds,
-                       timestamp: pos.timestamp
-               };
+// @function setOpacity(el: HTMLElement, opacity: Number)
+// Set the opacity of an element (including old IE support).
+// `opacity` must be a number from `0` to `1`.
+function setOpacity(el, value) {
+       if ('opacity' in el.style) {
+               el.style.opacity = value;
+       } else if ('filter' in el.style) {
+               _setOpacityIE(el, value);
+       }
+}
 
-               for (var i in pos.coords) {
-                       if (typeof pos.coords[i] === 'number') {
-                               data[i] = pos.coords[i];
-                       }
-               }
+function _setOpacityIE(el, value) {
+       var filter = false,
+           filterName = 'DXImageTransform.Microsoft.Alpha';
 
-               // @event locationfound: LocationEvent
-               // Fired when geolocation (using the [`locate`](#map-locate) method)
-               // went successfully.
-               this.fire('locationfound', data);
-       },
+       // filters collection throws an error if we try to retrieve a filter that doesn't exist
+       try {
+               filter = el.filters.item(filterName);
+       } catch (e) {
+               // don't set opacity to 1 if we haven't already set an opacity,
+               // it isn't needed and breaks transparent pngs.
+               if (value === 1) { return; }
+       }
 
-       // TODO handler.addTo
-       // TODO Appropiate docs section?
-       // @section Other Methods
-       // @method addHandler(name: String, HandlerClass: Function): this
-       // Adds a new `Handler` to the map, given its name and constructor function.
-       addHandler: function (name, HandlerClass) {
-               if (!HandlerClass) { return this; }
+       value = Math.round(value * 100);
 
-               var handler = this[name] = new HandlerClass(this);
+       if (filter) {
+               filter.Enabled = (value !== 100);
+               filter.Opacity = value;
+       } else {
+               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+       }
+}
 
-               this._handlers.push(handler);
+// @function testProp(props: String[]): String|false
+// Goes through the array of style names and returns the first name
+// that is a valid style name for an element. If no such name is found,
+// it returns false. Useful for vendor-prefixed styles like `transform`.
+function testProp(props) {
+       var style = document.documentElement.style;
 
-               if (this.options[name]) {
-                       handler.enable();
+       for (var i = 0; i < props.length; i++) {
+               if (props[i] in style) {
+                       return props[i];
                }
+       }
+       return false;
+}
 
-               return this;
-       },
-
-       // @method remove(): this
-       // Destroys the map and clears all related event listeners.
-       remove: function () {
+// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
+// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
+// and optionally scaled by `scale`. Does not have an effect if the
+// browser doesn't support 3D CSS transforms.
+function setTransform(el, offset, scale) {
+       var pos = offset || new Point(0, 0);
+
+       el.style[TRANSFORM] =
+               (ie3d ?
+                       'translate(' + pos.x + 'px,' + pos.y + 'px)' :
+                       'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
+               (scale ? ' scale(' + scale + ')' : '');
+}
 
-               this._initEvents(true);
+// @function setPosition(el: HTMLElement, position: Point)
+// Sets the position of `el` to coordinates specified by `position`,
+// using CSS translate or top/left positioning depending on the browser
+// (used by Leaflet internally to position its layers).
+function setPosition(el, point) {
 
-               if (this._containerId !== this._container._leaflet_id) {
-                       throw new Error('Map container is being reused by another instance');
-               }
+       /*eslint-disable */
+       el._leaflet_pos = point;
+       /*eslint-enable */
 
-               try {
-                       // throws error in IE6-8
-                       delete this._container._leaflet_id;
-                       delete this._containerId;
-               } catch (e) {
-                       /*eslint-disable */
-                       this._container._leaflet_id = undefined;
-                       /*eslint-enable */
-                       this._containerId = undefined;
-               }
+       if (any3d) {
+               setTransform(el, point);
+       } else {
+               el.style.left = point.x + 'px';
+               el.style.top = point.y + 'px';
+       }
+}
 
-               L.DomUtil.remove(this._mapPane);
+// @function getPosition(el: HTMLElement): Point
+// Returns the coordinates of an element previously positioned with setPosition.
+function getPosition(el) {
+       // this method is only used for elements previously positioned using setPosition,
+       // so it's safe to cache the position for performance
 
-               if (this._clearControlPos) {
-                       this._clearControlPos();
-               }
+       return el._leaflet_pos || new Point(0, 0);
+}
 
-               this._clearHandlers();
+// @function disableTextSelection()
+// Prevents the user from generating `selectstart` DOM events, usually generated
+// when the user drags the mouse through a page with text. Used internally
+// by Leaflet to override the behaviour of any click-and-drag interaction on
+// the map. Affects drag interactions on the whole document.
+
+// @function enableTextSelection()
+// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
+var disableTextSelection;
+var enableTextSelection;
+var _userSelect;
+if ('onselectstart' in document) {
+       disableTextSelection = function () {
+               on(window, 'selectstart', preventDefault);
+       };
+       enableTextSelection = function () {
+               off(window, 'selectstart', preventDefault);
+       };
+} else {
+       var userSelectProperty = testProp(
+               ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
 
-               if (this._loaded) {
-                       // @section Map state change events
-                       // @event unload: Event
-                       // Fired when the map is destroyed with [remove](#map-remove) method.
-                       this.fire('unload');
+       disableTextSelection = function () {
+               if (userSelectProperty) {
+                       var style = document.documentElement.style;
+                       _userSelect = style[userSelectProperty];
+                       style[userSelectProperty] = 'none';
                }
-
-               for (var i in this._layers) {
-                       this._layers[i].remove();
+       };
+       enableTextSelection = function () {
+               if (userSelectProperty) {
+                       document.documentElement.style[userSelectProperty] = _userSelect;
+                       _userSelect = undefined;
                }
+       };
+}
 
-               return this;
-       },
+// @function disableImageDrag()
+// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
+// for `dragstart` DOM events, usually generated when the user drags an image.
+function disableImageDrag() {
+       on(window, 'dragstart', preventDefault);
+}
 
-       // @section Other Methods
-       // @method createPane(name: String, container?: HTMLElement): HTMLElement
-       // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
-       // then returns it. The pane is created as a children of `container`, or
-       // as a children of the main map pane if not set.
-       createPane: function (name, container) {
-               var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
-                   pane = L.DomUtil.create('div', className, container || this._mapPane);
+// @function enableImageDrag()
+// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
+function enableImageDrag() {
+       off(window, 'dragstart', preventDefault);
+}
 
-               if (name) {
-                       this._panes[name] = pane;
-               }
-               return pane;
-       },
+var _outlineElement;
+var _outlineStyle;
+// @function preventOutline(el: HTMLElement)
+// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
+// of the element `el` invisible. Used internally by Leaflet to prevent
+// focusable elements from displaying an outline when the user performs a
+// drag interaction on them.
+function preventOutline(element) {
+       while (element.tabIndex === -1) {
+               element = element.parentNode;
+       }
+       if (!element.style) { return; }
+       restoreOutline();
+       _outlineElement = element;
+       _outlineStyle = element.style.outline;
+       element.style.outline = 'none';
+       on(window, 'keydown', restoreOutline);
+}
 
-       // @section Methods for Getting Map State
+// @function restoreOutline()
+// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
+function restoreOutline() {
+       if (!_outlineElement) { return; }
+       _outlineElement.style.outline = _outlineStyle;
+       _outlineElement = undefined;
+       _outlineStyle = undefined;
+       off(window, 'keydown', restoreOutline);
+}
 
-       // @method getCenter(): LatLng
-       // Returns the geographical center of the map view
-       getCenter: function () {
-               this._checkIfLoaded();
 
-               if (this._lastCenter && !this._moved()) {
-                       return this._lastCenter;
-               }
-               return this.layerPointToLatLng(this._getCenterLayerPoint());
-       },
+var DomUtil = (Object.freeze || Object)({
+       TRANSFORM: TRANSFORM,
+       TRANSITION: TRANSITION,
+       TRANSITION_END: TRANSITION_END,
+       get: get,
+       getStyle: getStyle,
+       create: create$1,
+       remove: remove,
+       empty: empty,
+       toFront: toFront,
+       toBack: toBack,
+       hasClass: hasClass,
+       addClass: addClass,
+       removeClass: removeClass,
+       setClass: setClass,
+       getClass: getClass,
+       setOpacity: setOpacity,
+       testProp: testProp,
+       setTransform: setTransform,
+       setPosition: setPosition,
+       getPosition: getPosition,
+       disableTextSelection: disableTextSelection,
+       enableTextSelection: enableTextSelection,
+       disableImageDrag: disableImageDrag,
+       enableImageDrag: enableImageDrag,
+       preventOutline: preventOutline,
+       restoreOutline: restoreOutline
+});
 
-       // @method getZoom(): Number
-       // Returns the current zoom level of the map view
-       getZoom: function () {
-               return this._zoom;
-       },
+/*
+ * @class PosAnimation
+ * @aka L.PosAnimation
+ * @inherits Evented
+ * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
+ *
+ * @example
+ * ```js
+ * var fx = new L.PosAnimation();
+ * fx.run(el, [300, 500], 0.5);
+ * ```
+ *
+ * @constructor L.PosAnimation()
+ * Creates a `PosAnimation` object.
+ *
+ */
 
-       // @method getBounds(): LatLngBounds
-       // Returns the geographical bounds visible in the current map view
-       getBounds: function () {
-               var bounds = this.getPixelBounds(),
-                   sw = this.unproject(bounds.getBottomLeft()),
-                   ne = this.unproject(bounds.getTopRight());
+var PosAnimation = Evented.extend({
 
-               return new L.LatLngBounds(sw, ne);
-       },
+       // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
+       // Run an animation of a given element to a new position, optionally setting
+       // duration in seconds (`0.25` by default) and easing linearity factor (3rd
+       // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
+       // `0.5` by default).
+       run: function (el, newPos, duration, easeLinearity) {
+               this.stop();
 
-       // @method getMinZoom(): Number
-       // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
-       getMinZoom: function () {
-               return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
-       },
+               this._el = el;
+               this._inProgress = true;
+               this._duration = duration || 0.25;
+               this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
 
-       // @method getMaxZoom(): Number
-       // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
-       getMaxZoom: function () {
-               return this.options.maxZoom === undefined ?
-                       (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
-                       this.options.maxZoom;
-       },
+               this._startPos = getPosition(el);
+               this._offset = newPos.subtract(this._startPos);
+               this._startTime = +new Date();
 
-       // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
-       // Returns the maximum zoom level on which the given bounds fit to the map
-       // view in its entirety. If `inside` (optional) is set to `true`, the method
-       // instead returns the minimum zoom level on which the map view fits into
-       // the given bounds in its entirety.
-       getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
-               bounds = L.latLngBounds(bounds);
-               padding = L.point(padding || [0, 0]);
+               // @event start: Event
+               // Fired when the animation starts
+               this.fire('start');
 
-               var zoom = this.getZoom() || 0,
-                   min = this.getMinZoom(),
-                   max = this.getMaxZoom(),
-                   nw = bounds.getNorthWest(),
-                   se = bounds.getSouthEast(),
-                   size = this.getSize().subtract(padding),
-                   boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
-                   snap = L.Browser.any3d ? this.options.zoomSnap : 1;
+               this._animate();
+       },
 
-               var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
-               zoom = this.getScaleZoom(scale, zoom);
+       // @method stop()
+       // Stops the animation (if currently running).
+       stop: function () {
+               if (!this._inProgress) { return; }
 
-               if (snap) {
-                       zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
-                       zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
-               }
+               this._step(true);
+               this._complete();
+       },
 
-               return Math.max(min, Math.min(max, zoom));
+       _animate: function () {
+               // animation loop
+               this._animId = requestAnimFrame(this._animate, this);
+               this._step();
        },
 
-       // @method getSize(): Point
-       // Returns the current size of the map container (in pixels).
-       getSize: function () {
-               if (!this._size || this._sizeChanged) {
-                       this._size = new L.Point(
-                               this._container.clientWidth || 0,
-                               this._container.clientHeight || 0);
+       _step: function (round) {
+               var elapsed = (+new Date()) - this._startTime,
+                   duration = this._duration * 1000;
 
-                       this._sizeChanged = false;
+               if (elapsed < duration) {
+                       this._runFrame(this._easeOut(elapsed / duration), round);
+               } else {
+                       this._runFrame(1);
+                       this._complete();
                }
-               return this._size.clone();
-       },
-
-       // @method getPixelBounds(): Bounds
-       // Returns the bounds of the current map view in projected pixel
-       // coordinates (sometimes useful in layer and overlay implementations).
-       getPixelBounds: function (center, zoom) {
-               var topLeftPoint = this._getTopLeftPoint(center, zoom);
-               return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
        },
 
-       // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
-       // the map pane? "left point of the map layer" can be confusing, specially
-       // since there can be negative offsets.
-       // @method getPixelOrigin(): Point
-       // Returns the projected pixel coordinates of the top left point of
-       // the map layer (useful in custom layer and overlay implementations).
-       getPixelOrigin: function () {
-               this._checkIfLoaded();
-               return this._pixelOrigin;
-       },
+       _runFrame: function (progress, round) {
+               var pos = this._startPos.add(this._offset.multiplyBy(progress));
+               if (round) {
+                       pos._round();
+               }
+               setPosition(this._el, pos);
 
-       // @method getPixelWorldBounds(zoom?: Number): Bounds
-       // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
-       // If `zoom` is omitted, the map's current zoom level is used.
-       getPixelWorldBounds: function (zoom) {
-               return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
+               // @event step: Event
+               // Fired continuously during the animation.
+               this.fire('step');
        },
 
-       // @section Other Methods
+       _complete: function () {
+               cancelAnimFrame(this._animId);
 
-       // @method getPane(pane: String|HTMLElement): HTMLElement
-       // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
-       getPane: function (pane) {
-               return typeof pane === 'string' ? this._panes[pane] : pane;
+               this._inProgress = false;
+               // @event end: Event
+               // Fired when the animation ends.
+               this.fire('end');
        },
 
-       // @method getPanes(): Object
-       // Returns a plain object containing the names of all [panes](#map-pane) as keys and
-       // the panes as values.
-       getPanes: function () {
-               return this._panes;
-       },
+       _easeOut: function (t) {
+               return 1 - Math.pow(1 - t, this._easeOutPower);
+       }
+});
 
-       // @method getContainer: HTMLElement
-       // Returns the HTML element that contains the map.
-       getContainer: function () {
-               return this._container;
-       },
+/*
+ * @class Map
+ * @aka L.Map
+ * @inherits Evented
+ *
+ * The central class of the API — it is used to create a map on a page and manipulate it.
+ *
+ * @example
+ *
+ * ```js
+ * // initialize the map on the "map" div with a given center and zoom
+ * var map = L.map('map', {
+ *     center: [51.505, -0.09],
+ *     zoom: 13
+ * });
+ * ```
+ *
+ */
 
+var Map = Evented.extend({
 
-       // @section Conversion Methods
+       options: {
+               // @section Map State Options
+               // @option crs: CRS = L.CRS.EPSG3857
+               // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
+               // sure what it means.
+               crs: EPSG3857,
 
-       // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
-       // Returns the scale factor to be applied to a map transition from zoom level
-       // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
-       getZoomScale: function (toZoom, fromZoom) {
-               // TODO replace with universal implementation after refactoring projections
-               var crs = this.options.crs;
-               fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
-               return crs.scale(toZoom) / crs.scale(fromZoom);
-       },
+               // @option center: LatLng = undefined
+               // Initial geographic center of the map
+               center: undefined,
 
-       // @method getScaleZoom(scale: Number, fromZoom: Number): Number
-       // Returns the zoom level that the map would end up at, if it is at `fromZoom`
-       // level and everything is scaled by a factor of `scale`. Inverse of
-       // [`getZoomScale`](#map-getZoomScale).
-       getScaleZoom: function (scale, fromZoom) {
-               var crs = this.options.crs;
-               fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
-               var zoom = crs.zoom(scale * crs.scale(fromZoom));
-               return isNaN(zoom) ? Infinity : zoom;
-       },
+               // @option zoom: Number = undefined
+               // Initial map zoom level
+               zoom: undefined,
 
-       // @method project(latlng: LatLng, zoom: Number): Point
-       // Projects a geographical coordinate `LatLng` according to the projection
-       // of the map's CRS, then scales it according to `zoom` and the CRS's
-       // `Transformation`. The result is pixel coordinate relative to
-       // the CRS origin.
-       project: function (latlng, zoom) {
-               zoom = zoom === undefined ? this._zoom : zoom;
-               return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
-       },
+               // @option minZoom: Number = *
+               // Minimum zoom level of the map.
+               // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
+               // the lowest of their `minZoom` options will be used instead.
+               minZoom: undefined,
 
-       // @method unproject(point: Point, zoom: Number): LatLng
-       // Inverse of [`project`](#map-project).
-       unproject: function (point, zoom) {
-               zoom = zoom === undefined ? this._zoom : zoom;
-               return this.options.crs.pointToLatLng(L.point(point), zoom);
-       },
+               // @option maxZoom: Number = *
+               // Maximum zoom level of the map.
+               // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
+               // the highest of their `maxZoom` options will be used instead.
+               maxZoom: undefined,
 
-       // @method layerPointToLatLng(point: Point): LatLng
-       // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
-       // returns the corresponding geographical coordinate (for the current zoom level).
-       layerPointToLatLng: function (point) {
-               var projectedPoint = L.point(point).add(this.getPixelOrigin());
-               return this.unproject(projectedPoint);
-       },
+               // @option layers: Layer[] = []
+               // Array of layers that will be added to the map initially
+               layers: [],
 
-       // @method latLngToLayerPoint(latlng: LatLng): Point
-       // Given a geographical coordinate, returns the corresponding pixel coordinate
-       // relative to the [origin pixel](#map-getpixelorigin).
-       latLngToLayerPoint: function (latlng) {
-               var projectedPoint = this.project(L.latLng(latlng))._round();
-               return projectedPoint._subtract(this.getPixelOrigin());
-       },
+               // @option maxBounds: LatLngBounds = null
+               // When this option is set, the map restricts the view to the given
+               // geographical bounds, bouncing the user back if the user tries to pan
+               // outside the view. To set the restriction dynamically, use
+               // [`setMaxBounds`](#map-setmaxbounds) method.
+               maxBounds: undefined,
 
-       // @method wrapLatLng(latlng: LatLng): LatLng
-       // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
-       // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
-       // CRS's bounds.
-       // By default this means longitude is wrapped around the dateline so its
-       // value is between -180 and +180 degrees.
-       wrapLatLng: function (latlng) {
-               return this.options.crs.wrapLatLng(L.latLng(latlng));
-       },
+               // @option renderer: Renderer = *
+               // The default method for drawing vector layers on the map. `L.SVG`
+               // or `L.Canvas` by default depending on browser support.
+               renderer: undefined,
 
-       // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
-       // Returns a `LatLngBounds` with the same size as the given one, ensuring that
-       // its center is within the CRS's bounds.
-       // By default this means the center longitude is wrapped around the dateline so its
-       // value is between -180 and +180 degrees, and the majority of the bounds
-       // overlaps the CRS's bounds.
-       wrapLatLngBounds: function (latlng) {
-               return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
-       },
 
-       // @method distance(latlng1: LatLng, latlng2: LatLng): Number
-       // Returns the distance between two geographical coordinates according to
-       // the map's CRS. By default this measures distance in meters.
-       distance: function (latlng1, latlng2) {
-               return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
-       },
+               // @section Animation Options
+               // @option zoomAnimation: Boolean = true
+               // Whether the map zoom animation is enabled. By default it's enabled
+               // in all browsers that support CSS3 Transitions except Android.
+               zoomAnimation: true,
 
-       // @method containerPointToLayerPoint(point: Point): Point
-       // Given a pixel coordinate relative to the map container, returns the corresponding
-       // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
-       containerPointToLayerPoint: function (point) { // (Point)
-               return L.point(point).subtract(this._getMapPanePos());
-       },
+               // @option zoomAnimationThreshold: Number = 4
+               // Won't animate zoom if the zoom difference exceeds this value.
+               zoomAnimationThreshold: 4,
 
-       // @method layerPointToContainerPoint(point: Point): Point
-       // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
-       // returns the corresponding pixel coordinate relative to the map container.
-       layerPointToContainerPoint: function (point) { // (Point)
-               return L.point(point).add(this._getMapPanePos());
-       },
+               // @option fadeAnimation: Boolean = true
+               // Whether the tile fade animation is enabled. By default it's enabled
+               // in all browsers that support CSS3 Transitions except Android.
+               fadeAnimation: true,
 
-       // @method containerPointToLatLng(point: Point): LatLng
-       // Given a pixel coordinate relative to the map container, returns
-       // the corresponding geographical coordinate (for the current zoom level).
-       containerPointToLatLng: function (point) {
-               var layerPoint = this.containerPointToLayerPoint(L.point(point));
-               return this.layerPointToLatLng(layerPoint);
-       },
+               // @option markerZoomAnimation: Boolean = true
+               // Whether markers animate their zoom with the zoom animation, if disabled
+               // they will disappear for the length of the animation. By default it's
+               // enabled in all browsers that support CSS3 Transitions except Android.
+               markerZoomAnimation: true,
 
-       // @method latLngToContainerPoint(latlng: LatLng): Point
-       // Given a geographical coordinate, returns the corresponding pixel coordinate
-       // relative to the map container.
-       latLngToContainerPoint: function (latlng) {
-               return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
-       },
+               // @option transform3DLimit: Number = 2^23
+               // Defines the maximum size of a CSS translation transform. The default
+               // value should not be changed unless a web browser positions layers in
+               // the wrong place after doing a large `panBy`.
+               transform3DLimit: 8388608, // Precision limit of a 32-bit float
 
-       // @method mouseEventToContainerPoint(ev: MouseEvent): Point
-       // Given a MouseEvent object, returns the pixel coordinate relative to the
-       // map container where the event took place.
-       mouseEventToContainerPoint: function (e) {
-               return L.DomEvent.getMousePosition(e, this._container);
-       },
+               // @section Interaction Options
+               // @option zoomSnap: Number = 1
+               // Forces the map's zoom level to always be a multiple of this, particularly
+               // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
+               // By default, the zoom level snaps to the nearest integer; lower values
+               // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
+               // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
+               zoomSnap: 1,
 
-       // @method mouseEventToLayerPoint(ev: MouseEvent): Point
-       // Given a MouseEvent object, returns the pixel coordinate relative to
-       // the [origin pixel](#map-getpixelorigin) where the event took place.
-       mouseEventToLayerPoint: function (e) {
-               return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
-       },
+               // @option zoomDelta: Number = 1
+               // Controls how much the map's zoom level will change after a
+               // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
+               // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
+               // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
+               zoomDelta: 1,
 
-       // @method mouseEventToLatLng(ev: MouseEvent): LatLng
-       // Given a MouseEvent object, returns geographical coordinate where the
-       // event took place.
-       mouseEventToLatLng: function (e) { // (MouseEvent)
-               return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
+               // @option trackResize: Boolean = true
+               // Whether the map automatically handles browser window resize to update itself.
+               trackResize: true
        },
 
+       initialize: function (id, options) { // (HTMLElement or String, Object)
+               options = setOptions(this, options);
+
+               this._initContainer(id);
+               this._initLayout();
 
-       // map initialization methods
+               // hack for https://github.com/Leaflet/Leaflet/issues/1980
+               this._onResize = bind(this._onResize, this);
 
-       _initContainer: function (id) {
-               var container = this._container = L.DomUtil.get(id);
+               this._initEvents();
 
-               if (!container) {
-                       throw new Error('Map container not found.');
-               } else if (container._leaflet_id) {
-                       throw new Error('Map container is already initialized.');
+               if (options.maxBounds) {
+                       this.setMaxBounds(options.maxBounds);
                }
 
-               L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
-               this._containerId = L.Util.stamp(container);
-       },
+               if (options.zoom !== undefined) {
+                       this._zoom = this._limitZoom(options.zoom);
+               }
 
-       _initLayout: function () {
-               var container = this._container;
+               if (options.center && options.zoom !== undefined) {
+                       this.setView(toLatLng(options.center), options.zoom, {reset: true});
+               }
 
-               this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
+               this._handlers = [];
+               this._layers = {};
+               this._zoomBoundLayers = {};
+               this._sizeChanged = true;
 
-               L.DomUtil.addClass(container, 'leaflet-container' +
-                       (L.Browser.touch ? ' leaflet-touch' : '') +
-                       (L.Browser.retina ? ' leaflet-retina' : '') +
-                       (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
-                       (L.Browser.safari ? ' leaflet-safari' : '') +
-                       (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
+               this.callInitHooks();
 
-               var position = L.DomUtil.getStyle(container, 'position');
+               // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
+               this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
+                               this.options.zoomAnimation;
 
-               if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
-                       container.style.position = 'relative';
+               // zoom transitions run with the same duration for all layers, so if one of transitionend events
+               // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
+               if (this._zoomAnimated) {
+                       this._createAnimProxy();
+                       on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
                }
 
-               this._initPanes();
-
-               if (this._initControlPos) {
-                       this._initControlPos();
-               }
+               this._addLayers(this.options.layers);
        },
 
-       _initPanes: function () {
-               var panes = this._panes = {};
-               this._paneRenderers = {};
-
-               // @section
-               //
-               // Panes are DOM elements used to control the ordering of layers on the map. You
-               // can access panes with [`map.getPane`](#map-getpane) or
-               // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
-               // [`map.createPane`](#map-createpane) method.
-               //
-               // Every map has the following default panes that differ only in zIndex.
-               //
-               // @pane mapPane: HTMLElement = 'auto'
-               // Pane that contains all other map panes
 
-               this._mapPane = this.createPane('mapPane', this._container);
-               L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
+       // @section Methods for modifying map state
 
-               // @pane tilePane: HTMLElement = 200
-               // Pane for `GridLayer`s and `TileLayer`s
-               this.createPane('tilePane');
-               // @pane overlayPane: HTMLElement = 400
-               // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
-               this.createPane('shadowPane');
-               // @pane shadowPane: HTMLElement = 500
-               // Pane for overlay shadows (e.g. `Marker` shadows)
-               this.createPane('overlayPane');
-               // @pane markerPane: HTMLElement = 600
-               // Pane for `Icon`s of `Marker`s
-               this.createPane('markerPane');
-               // @pane tooltipPane: HTMLElement = 650
-               // Pane for tooltip.
-               this.createPane('tooltipPane');
-               // @pane popupPane: HTMLElement = 700
-               // Pane for `Popup`s.
-               this.createPane('popupPane');
+       // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
+       // Sets the view of the map (geographical center and zoom) with the given
+       // animation options.
+       setView: function (center, zoom, options) {
 
-               if (!this.options.markerZoomAnimation) {
-                       L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
-                       L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
-               }
-       },
+               zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
+               center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
+               options = options || {};
 
+               this._stop();
 
-       // private methods that modify map state
+               if (this._loaded && !options.reset && options !== true) {
 
-       // @section Map state change events
-       _resetView: function (center, zoom) {
-               L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
+                       if (options.animate !== undefined) {
+                               options.zoom = extend({animate: options.animate}, options.zoom);
+                               options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
+                       }
 
-               var loading = !this._loaded;
-               this._loaded = true;
-               zoom = this._limitZoom(zoom);
+                       // try animating pan or zoom
+                       var moved = (this._zoom !== zoom) ?
+                               this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
+                               this._tryAnimatedPan(center, options.pan);
 
-               this.fire('viewprereset');
+                       if (moved) {
+                               // prevent resize handler call, the view will refresh after animation anyway
+                               clearTimeout(this._sizeTimer);
+                               return this;
+                       }
+               }
 
-               var zoomChanged = this._zoom !== zoom;
-               this
-                       ._moveStart(zoomChanged)
-                       ._move(center, zoom)
-                       ._moveEnd(zoomChanged);
+               // animation didn't start, just reset the map view
+               this._resetView(center, zoom);
 
-               // @event viewreset: Event
-               // Fired when the map needs to redraw its content (this usually happens
-               // on map zoom or load). Very useful for creating custom overlays.
-               this.fire('viewreset');
+               return this;
+       },
 
-               // @event load: Event
-               // Fired when the map is initialized (when its center and zoom are set
-               // for the first time).
-               if (loading) {
-                       this.fire('load');
+       // @method setZoom(zoom: Number, options?: Zoom/pan options): this
+       // Sets the zoom of the map.
+       setZoom: function (zoom, options) {
+               if (!this._loaded) {
+                       this._zoom = zoom;
+                       return this;
                }
+               return this.setView(this.getCenter(), zoom, {zoom: options});
        },
 
-       _moveStart: function (zoomChanged) {
-               // @event zoomstart: Event
-               // Fired when the map zoom is about to change (e.g. before zoom animation).
-               // @event movestart: Event
-               // Fired when the view of the map starts changing (e.g. user starts dragging the map).
-               if (zoomChanged) {
-                       this.fire('zoomstart');
-               }
-               return this.fire('movestart');
+       // @method zoomIn(delta?: Number, options?: Zoom options): this
+       // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
+       zoomIn: function (delta, options) {
+               delta = delta || (any3d ? this.options.zoomDelta : 1);
+               return this.setZoom(this._zoom + delta, options);
        },
 
-       _move: function (center, zoom, data) {
-               if (zoom === undefined) {
-                       zoom = this._zoom;
-               }
-               var zoomChanged = this._zoom !== zoom;
+       // @method zoomOut(delta?: Number, options?: Zoom options): this
+       // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
+       zoomOut: function (delta, options) {
+               delta = delta || (any3d ? this.options.zoomDelta : 1);
+               return this.setZoom(this._zoom - delta, options);
+       },
 
-               this._zoom = zoom;
-               this._lastCenter = center;
-               this._pixelOrigin = this._getNewPixelOrigin(center);
+       // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
+       // Zooms the map while keeping a specified geographical point on the map
+       // stationary (e.g. used internally for scroll zoom and double-click zoom).
+       // @alternative
+       // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
+       // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
+       setZoomAround: function (latlng, zoom, options) {
+               var scale = this.getZoomScale(zoom),
+                   viewHalf = this.getSize().divideBy(2),
+                   containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
 
-               // @event zoom: Event
-               // Fired repeatedly during any change in zoom level, including zoom
-               // and fly animations.
-               if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
-                       this.fire('zoom', data);
-               }
+                   centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
+                   newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
 
-               // @event move: Event
-               // Fired repeatedly during any movement of the map, including pan and
-               // fly animations.
-               return this.fire('move', data);
+               return this.setView(newCenter, zoom, {zoom: options});
        },
 
-       _moveEnd: function (zoomChanged) {
-               // @event zoomend: Event
-               // Fired when the map has changed, after any animations.
-               if (zoomChanged) {
-                       this.fire('zoomend');
-               }
-
-               // @event moveend: Event
-               // Fired when the center of the map stops changing (e.g. user stopped
-               // dragging the map).
-               return this.fire('moveend');
-       },
+       _getBoundsCenterZoom: function (bounds, options) {
 
-       _stop: function () {
-               L.Util.cancelAnimFrame(this._flyToFrame);
-               if (this._panAnim) {
-                       this._panAnim.stop();
-               }
-               return this;
-       },
+               options = options || {};
+               bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
 
-       _rawPanBy: function (offset) {
-               L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
-       },
+               var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
+                   paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
 
-       _getZoomSpan: function () {
-               return this.getMaxZoom() - this.getMinZoom();
-       },
+                   zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
 
-       _panInsideMaxBounds: function () {
-               if (!this._enforcingBounds) {
-                       this.panInsideBounds(this.options.maxBounds);
-               }
-       },
+               zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
 
-       _checkIfLoaded: function () {
-               if (!this._loaded) {
-                       throw new Error('Set map center and zoom first.');
+               if (zoom === Infinity) {
+                       return {
+                               center: bounds.getCenter(),
+                               zoom: zoom
+                       };
                }
-       },
 
-       // DOM event handling
+               var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
 
-       // @section Interaction events
-       _initEvents: function (remove) {
-               if (!L.DomEvent) { return; }
+                   swPoint = this.project(bounds.getSouthWest(), zoom),
+                   nePoint = this.project(bounds.getNorthEast(), zoom),
+                   center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
 
-               this._targets = {};
-               this._targets[L.stamp(this._container)] = this;
+               return {
+                       center: center,
+                       zoom: zoom
+               };
+       },
 
-               var onOff = remove ? 'off' : 'on';
+       // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
+       // Sets a map view that contains the given geographical bounds with the
+       // maximum zoom level possible.
+       fitBounds: function (bounds, options) {
 
-               // @event click: MouseEvent
-               // Fired when the user clicks (or taps) the map.
-               // @event dblclick: MouseEvent
-               // Fired when the user double-clicks (or double-taps) the map.
-               // @event mousedown: MouseEvent
-               // Fired when the user pushes the mouse button on the map.
-               // @event mouseup: MouseEvent
-               // Fired when the user releases the mouse button on the map.
-               // @event mouseover: MouseEvent
-               // Fired when the mouse enters the map.
-               // @event mouseout: MouseEvent
-               // Fired when the mouse leaves the map.
-               // @event mousemove: MouseEvent
-               // Fired while the mouse moves over the map.
-               // @event contextmenu: MouseEvent
-               // Fired when the user pushes the right mouse button on the map, prevents
-               // default browser context menu from showing if there are listeners on
-               // this event. Also fired on mobile when the user holds a single touch
-               // for a second (also called long press).
-               // @event keypress: KeyboardEvent
-               // Fired when the user presses a key from the keyboard while the map is focused.
-               L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
-                       'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
+               bounds = toLatLngBounds(bounds);
 
-               if (this.options.trackResize) {
-                       L.DomEvent[onOff](window, 'resize', this._onResize, this);
+               if (!bounds.isValid()) {
+                       throw new Error('Bounds are not valid.');
                }
 
-               if (L.Browser.any3d && this.options.transform3DLimit) {
-                       this[onOff]('moveend', this._onMoveEnd);
-               }
+               var target = this._getBoundsCenterZoom(bounds, options);
+               return this.setView(target.center, target.zoom, options);
        },
 
-       _onResize: function () {
-               L.Util.cancelAnimFrame(this._resizeRequest);
-               this._resizeRequest = L.Util.requestAnimFrame(
-                       function () { this.invalidateSize({debounceMoveend: true}); }, this);
+       // @method fitWorld(options?: fitBounds options): this
+       // Sets a map view that mostly contains the whole world with the maximum
+       // zoom level possible.
+       fitWorld: function (options) {
+               return this.fitBounds([[-90, -180], [90, 180]], options);
        },
 
-       _onScroll: function () {
-               this._container.scrollTop  = 0;
-               this._container.scrollLeft = 0;
+       // @method panTo(latlng: LatLng, options?: Pan options): this
+       // Pans the map to a given center.
+       panTo: function (center, options) { // (LatLng)
+               return this.setView(center, this._zoom, {pan: options});
        },
 
-       _onMoveEnd: function () {
-               var pos = this._getMapPanePos();
-               if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
-                       // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
-                       // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
-                       this._resetView(this.getCenter(), this.getZoom());
+       // @method panBy(offset: Point, options?: Pan options): this
+       // Pans the map by a given number of pixels (animated).
+       panBy: function (offset, options) {
+               offset = toPoint(offset).round();
+               options = options || {};
+
+               if (!offset.x && !offset.y) {
+                       return this.fire('moveend');
+               }
+               // If we pan too far, Chrome gets issues with tiles
+               // and makes them disappear or appear in the wrong place (slightly offset) #2602
+               if (options.animate !== true && !this.getSize().contains(offset)) {
+                       this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
+                       return this;
                }
-       },
 
-       _findEventTargets: function (e, type) {
-               var targets = [],
-                   target,
-                   isHover = type === 'mouseout' || type === 'mouseover',
-                   src = e.target || e.srcElement,
-                   dragging = false;
+               if (!this._panAnim) {
+                       this._panAnim = new PosAnimation();
 
-               while (src) {
-                       target = this._targets[L.stamp(src)];
-                       if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
-                               // Prevent firing click after you just dragged an object.
-                               dragging = true;
-                               break;
-                       }
-                       if (target && target.listens(type, true)) {
-                               if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
-                               targets.push(target);
-                               if (isHover) { break; }
-                       }
-                       if (src === this._container) { break; }
-                       src = src.parentNode;
-               }
-               if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
-                       targets = [this];
+                       this._panAnim.on({
+                               'step': this._onPanTransitionStep,
+                               'end': this._onPanTransitionEnd
+                       }, this);
                }
-               return targets;
-       },
 
-       _handleDOMEvent: function (e) {
-               if (!this._loaded || L.DomEvent._skipped(e)) { return; }
+               // don't fire movestart if animating inertia
+               if (!options.noMoveStart) {
+                       this.fire('movestart');
+               }
 
-               var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
+               // animate pan unless animate: false specified
+               if (options.animate !== false) {
+                       addClass(this._mapPane, 'leaflet-pan-anim');
 
-               if (type === 'mousedown') {
-                       // prevents outline when clicking on keyboard-focusable element
-                       L.DomUtil.preventOutline(e.target || e.srcElement);
+                       var newPos = this._getMapPanePos().subtract(offset).round();
+                       this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
+               } else {
+                       this._rawPanBy(offset);
+                       this.fire('move').fire('moveend');
                }
 
-               this._fireDOMEvent(e, type);
+               return this;
        },
 
-       _fireDOMEvent: function (e, type, targets) {
+       // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
+       // Sets the view of the map (geographical center and zoom) performing a smooth
+       // pan-zoom animation.
+       flyTo: function (targetCenter, targetZoom, options) {
 
-               if (e.type === 'click') {
-                       // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
-                       // @event preclick: MouseEvent
-                       // Fired before mouse click on the map (sometimes useful when you
-                       // want something to happen on click before any existing click
-                       // handlers start running).
-                       var synth = L.Util.extend({}, e);
-                       synth.type = 'preclick';
-                       this._fireDOMEvent(synth, synth.type, targets);
+               options = options || {};
+               if (options.animate === false || !any3d) {
+                       return this.setView(targetCenter, targetZoom, options);
                }
 
-               if (e._stopped) { return; }
-
-               // Find the layer the event is propagating from and its parents.
-               targets = (targets || []).concat(this._findEventTargets(e, type));
-
-               if (!targets.length) { return; }
+               this._stop();
 
-               var target = targets[0];
-               if (type === 'contextmenu' && target.listens(type, true)) {
-                       L.DomEvent.preventDefault(e);
-               }
+               var from = this.project(this.getCenter()),
+                   to = this.project(targetCenter),
+                   size = this.getSize(),
+                   startZoom = this._zoom;
 
-               var data = {
-                       originalEvent: e
-               };
+               targetCenter = toLatLng(targetCenter);
+               targetZoom = targetZoom === undefined ? startZoom : targetZoom;
 
-               if (e.type !== 'keypress') {
-                       var isMarker = target instanceof L.Marker;
-                       data.containerPoint = isMarker ?
-                                       this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
-                       data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
-                       data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
-               }
+               var w0 = Math.max(size.x, size.y),
+                   w1 = w0 * this.getZoomScale(startZoom, targetZoom),
+                   u1 = (to.distanceTo(from)) || 1,
+                   rho = 1.42,
+                   rho2 = rho * rho;
 
-               for (var i = 0; i < targets.length; i++) {
-                       targets[i].fire(type, data, true);
-                       if (data.originalEvent._stopped ||
-                               (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
-               }
-       },
+               function r(i) {
+                       var s1 = i ? -1 : 1,
+                           s2 = i ? w1 : w0,
+                           t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
+                           b1 = 2 * s2 * rho2 * u1,
+                           b = t1 / b1,
+                           sq = Math.sqrt(b * b + 1) - b;
 
-       _draggableMoved: function (obj) {
-               obj = obj.dragging && obj.dragging.enabled() ? obj : this;
-               return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
-       },
+                           // workaround for floating point precision bug when sq = 0, log = -Infinite,
+                           // thus triggering an infinite loop in flyTo
+                           var log = sq < 0.000000001 ? -18 : Math.log(sq);
 
-       _clearHandlers: function () {
-               for (var i = 0, len = this._handlers.length; i < len; i++) {
-                       this._handlers[i].disable();
+                       return log;
                }
-       },
 
-       // @section Other Methods
+               function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
+               function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
+               function tanh(n) { return sinh(n) / cosh(n); }
 
-       // @method whenReady(fn: Function, context?: Object): this
-       // Runs the given function `fn` when the map gets initialized with
-       // a view (center and zoom) and at least one layer, or immediately
-       // if it's already initialized, optionally passing a function context.
-       whenReady: function (callback, context) {
-               if (this._loaded) {
-                       callback.call(context || this, {target: this});
-               } else {
-                       this.on('load', callback, context);
-               }
-               return this;
-       },
+               var r0 = r(0);
 
+               function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
+               function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
 
-       // private methods for getting map state
+               function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
 
-       _getMapPanePos: function () {
-               return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
-       },
+               var start = Date.now(),
+                   S = (r(1) - r0) / rho,
+                   duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
 
-       _moved: function () {
-               var pos = this._getMapPanePos();
-               return pos && !pos.equals([0, 0]);
-       },
+               function frame() {
+                       var t = (Date.now() - start) / duration,
+                           s = easeOut(t) * S;
 
-       _getTopLeftPoint: function (center, zoom) {
-               var pixelOrigin = center && zoom !== undefined ?
-                       this._getNewPixelOrigin(center, zoom) :
-                       this.getPixelOrigin();
-               return pixelOrigin.subtract(this._getMapPanePos());
-       },
+                       if (t <= 1) {
+                               this._flyToFrame = requestAnimFrame(frame, this);
 
-       _getNewPixelOrigin: function (center, zoom) {
-               var viewHalf = this.getSize()._divideBy(2);
-               return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
-       },
+                               this._move(
+                                       this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
+                                       this.getScaleZoom(w0 / w(s), startZoom),
+                                       {flyTo: true});
 
-       _latLngToNewLayerPoint: function (latlng, zoom, center) {
-               var topLeft = this._getNewPixelOrigin(center, zoom);
-               return this.project(latlng, zoom)._subtract(topLeft);
-       },
+                       } else {
+                               this
+                                       ._move(targetCenter, targetZoom)
+                                       ._moveEnd(true);
+                       }
+               }
 
-       _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
-               var topLeft = this._getNewPixelOrigin(center, zoom);
-               return L.bounds([
-                       this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
-                       this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
-                       this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
-                       this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
-               ]);
-       },
+               this._moveStart(true);
 
-       // layer point of the current center
-       _getCenterLayerPoint: function () {
-               return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
+               frame.call(this);
+               return this;
        },
 
-       // offset of the specified place to the current center in pixels
-       _getCenterOffset: function (latlng) {
-               return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
+       // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
+       // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
+       // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
+       flyToBounds: function (bounds, options) {
+               var target = this._getBoundsCenterZoom(bounds, options);
+               return this.flyTo(target.center, target.zoom, options);
        },
 
-       // adjust center for view to get inside bounds
-       _limitCenter: function (center, zoom, bounds) {
+       // @method setMaxBounds(bounds: Bounds): this
+       // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
+       setMaxBounds: function (bounds) {
+               bounds = toLatLngBounds(bounds);
 
-               if (!bounds) { return center; }
+               if (!bounds.isValid()) {
+                       this.options.maxBounds = null;
+                       return this.off('moveend', this._panInsideMaxBounds);
+               } else if (this.options.maxBounds) {
+                       this.off('moveend', this._panInsideMaxBounds);
+               }
 
-               var centerPoint = this.project(center, zoom),
-                   viewHalf = this.getSize().divideBy(2),
-                   viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
-                   offset = this._getBoundsOffset(viewBounds, bounds, zoom);
+               this.options.maxBounds = bounds;
 
-               // If offset is less than a pixel, ignore.
-               // This prevents unstable projections from getting into
-               // an infinite loop of tiny offsets.
-               if (offset.round().equals([0, 0])) {
-                       return center;
+               if (this._loaded) {
+                       this._panInsideMaxBounds();
                }
 
-               return this.unproject(centerPoint.add(offset), zoom);
+               return this.on('moveend', this._panInsideMaxBounds);
        },
 
-       // adjust offset for view to get inside bounds
-       _limitOffset: function (offset, bounds) {
-               if (!bounds) { return offset; }
+       // @method setMinZoom(zoom: Number): this
+       // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
+       setMinZoom: function (zoom) {
+               this.options.minZoom = zoom;
 
-               var viewBounds = this.getPixelBounds(),
-                   newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
+               if (this._loaded && this.getZoom() < this.options.minZoom) {
+                       return this.setZoom(zoom);
+               }
 
-               return offset.add(this._getBoundsOffset(newBounds, bounds));
+               return this;
        },
 
-       // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
-       _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
-               var projectedMaxBounds = L.bounds(
-                       this.project(maxBounds.getNorthEast(), zoom),
-                       this.project(maxBounds.getSouthWest(), zoom)
-                   ),
-                   minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
-                   maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
+       // @method setMaxZoom(zoom: Number): this
+       // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
+       setMaxZoom: function (zoom) {
+               this.options.maxZoom = zoom;
 
-                   dx = this._rebound(minOffset.x, -maxOffset.x),
-                   dy = this._rebound(minOffset.y, -maxOffset.y);
+               if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
+                       return this.setZoom(zoom);
+               }
 
-               return new L.Point(dx, dy);
+               return this;
        },
 
-       _rebound: function (left, right) {
-               return left + right > 0 ?
-                       Math.round(left - right) / 2 :
-                       Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
-       },
+       // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
+       // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
+       panInsideBounds: function (bounds, options) {
+               this._enforcingBounds = true;
+               var center = this.getCenter(),
+                   newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
 
-       _limitZoom: function (zoom) {
-               var min = this.getMinZoom(),
-                   max = this.getMaxZoom(),
-                   snap = L.Browser.any3d ? this.options.zoomSnap : 1;
-               if (snap) {
-                       zoom = Math.round(zoom / snap) * snap;
+               if (!center.equals(newCenter)) {
+                       this.panTo(newCenter, options);
                }
-               return Math.max(min, Math.min(max, zoom));
-       },
 
-       _onPanTransitionStep: function () {
-               this.fire('move');
+               this._enforcingBounds = false;
+               return this;
        },
 
-       _onPanTransitionEnd: function () {
-               L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
-               this.fire('moveend');
-       },
+       // @method invalidateSize(options: Zoom/Pan options): this
+       // Checks if the map container size changed and updates the map if so —
+       // call it after you've changed the map size dynamically, also animating
+       // pan by default. If `options.pan` is `false`, panning will not occur.
+       // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
+       // that it doesn't happen often even if the method is called many
+       // times in a row.
 
-       _tryAnimatedPan: function (center, options) {
-               // difference between the new and current centers in pixels
-               var offset = this._getCenterOffset(center)._floor();
+       // @alternative
+       // @method invalidateSize(animate: Boolean): this
+       // Checks if the map container size changed and updates the map if so —
+       // call it after you've changed the map size dynamically, also animating
+       // pan by default.
+       invalidateSize: function (options) {
+               if (!this._loaded) { return this; }
 
-               // don't animate too far unless animate: true specified in options
-               if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
+               options = extend({
+                       animate: false,
+                       pan: true
+               }, options === true ? {animate: true} : options);
 
-               this.panBy(offset, options);
+               var oldSize = this.getSize();
+               this._sizeChanged = true;
+               this._lastCenter = null;
 
-               return true;
-       },
+               var newSize = this.getSize(),
+                   oldCenter = oldSize.divideBy(2).round(),
+                   newCenter = newSize.divideBy(2).round(),
+                   offset = oldCenter.subtract(newCenter);
 
-       _createAnimProxy: function () {
+               if (!offset.x && !offset.y) { return this; }
 
-               var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
-               this._panes.mapPane.appendChild(proxy);
+               if (options.animate && options.pan) {
+                       this.panBy(offset);
 
-               this.on('zoomanim', function (e) {
-                       var prop = L.DomUtil.TRANSFORM,
-                           transform = proxy.style[prop];
+               } else {
+                       if (options.pan) {
+                               this._rawPanBy(offset);
+                       }
 
-                       L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
+                       this.fire('move');
 
-                       // workaround for case when transform is the same and so transitionend event is not fired
-                       if (transform === proxy.style[prop] && this._animatingZoom) {
-                               this._onZoomTransitionEnd();
+                       if (options.debounceMoveend) {
+                               clearTimeout(this._sizeTimer);
+                               this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
+                       } else {
+                               this.fire('moveend');
                        }
-               }, this);
+               }
 
-               this.on('load moveend', function () {
-                       var c = this.getCenter(),
-                           z = this.getZoom();
-                       L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
-               }, this);
+               // @section Map state change events
+               // @event resize: ResizeEvent
+               // Fired when the map is resized.
+               return this.fire('resize', {
+                       oldSize: oldSize,
+                       newSize: newSize
+               });
        },
 
-       _catchTransitionEnd: function (e) {
-               if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
-                       this._onZoomTransitionEnd();
+       // @section Methods for modifying map state
+       // @method stop(): this
+       // Stops the currently running `panTo` or `flyTo` animation, if any.
+       stop: function () {
+               this.setZoom(this._limitZoom(this._zoom));
+               if (!this.options.zoomSnap) {
+                       this.fire('viewreset');
                }
+               return this._stop();
        },
 
-       _nothingToAnimate: function () {
-               return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
-       },
+       // @section Geolocation methods
+       // @method locate(options?: Locate options): this
+       // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
+       // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
+       // and optionally sets the map view to the user's location with respect to
+       // detection accuracy (or to the world view if geolocation failed).
+       // Note that, if your page doesn't use HTTPS, this method will fail in
+       // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
+       // See `Locate options` for more details.
+       locate: function (options) {
 
-       _tryAnimatedZoom: function (center, zoom, options) {
+               options = this._locateOptions = extend({
+                       timeout: 10000,
+                       watch: false
+                       // setView: false
+                       // maxZoom: <Number>
+                       // maximumAge: 0
+                       // enableHighAccuracy: false
+               }, options);
 
-               if (this._animatingZoom) { return true; }
+               if (!('geolocation' in navigator)) {
+                       this._handleGeolocationError({
+                               code: 0,
+                               message: 'Geolocation not supported.'
+                       });
+                       return this;
+               }
 
-               options = options || {};
+               var onResponse = bind(this._handleGeolocationResponse, this),
+                   onError = bind(this._handleGeolocationError, this);
 
-               // don't animate if disabled, not supported or zoom difference is too large
-               if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
-                       Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
+               if (options.watch) {
+                       this._locationWatchId =
+                               navigator.geolocation.watchPosition(onResponse, onError, options);
+               } else {
+                       navigator.geolocation.getCurrentPosition(onResponse, onError, options);
+               }
+               return this;
+       },
 
-               // offset is the pixel coords of the zoom origin relative to the current center
-               var scale = this.getZoomScale(zoom),
-                   offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
+       // @method stopLocate(): this
+       // Stops watching location previously initiated by `map.locate({watch: true})`
+       // and aborts resetting the map view if map.locate was called with
+       // `{setView: true}`.
+       stopLocate: function () {
+               if (navigator.geolocation && navigator.geolocation.clearWatch) {
+                       navigator.geolocation.clearWatch(this._locationWatchId);
+               }
+               if (this._locateOptions) {
+                       this._locateOptions.setView = false;
+               }
+               return this;
+       },
 
-               // don't animate if the zoom origin isn't within one screen from the current center, unless forced
-               if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
+       _handleGeolocationError: function (error) {
+               var c = error.code,
+                   message = error.message ||
+                           (c === 1 ? 'permission denied' :
+                           (c === 2 ? 'position unavailable' : 'timeout'));
 
-               L.Util.requestAnimFrame(function () {
-                       this
-                           ._moveStart(true)
-                           ._animateZoom(center, zoom, true);
-               }, this);
+               if (this._locateOptions.setView && !this._loaded) {
+                       this.fitWorld();
+               }
 
-               return true;
+               // @section Location events
+               // @event locationerror: ErrorEvent
+               // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
+               this.fire('locationerror', {
+                       code: c,
+                       message: 'Geolocation error: ' + message + '.'
+               });
        },
 
-       _animateZoom: function (center, zoom, startAnim, noUpdate) {
-               if (startAnim) {
-                       this._animatingZoom = true;
-
-                       // remember what center/zoom to set after animation
-                       this._animateToCenter = center;
-                       this._animateToZoom = zoom;
+       _handleGeolocationResponse: function (pos) {
+               var lat = pos.coords.latitude,
+                   lng = pos.coords.longitude,
+                   latlng = new LatLng(lat, lng),
+                   bounds = latlng.toBounds(pos.coords.accuracy),
+                   options = this._locateOptions;
 
-                       L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
+               if (options.setView) {
+                       var zoom = this.getBoundsZoom(bounds);
+                       this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
                }
 
-               // @event zoomanim: ZoomAnimEvent
-               // Fired on every frame of a zoom animation
-               this.fire('zoomanim', {
-                       center: center,
-                       zoom: zoom,
-                       noUpdate: noUpdate
-               });
+               var data = {
+                       latlng: latlng,
+                       bounds: bounds,
+                       timestamp: pos.timestamp
+               };
 
-               // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
-               setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
+               for (var i in pos.coords) {
+                       if (typeof pos.coords[i] === 'number') {
+                               data[i] = pos.coords[i];
+                       }
+               }
+
+               // @event locationfound: LocationEvent
+               // Fired when geolocation (using the [`locate`](#map-locate) method)
+               // went successfully.
+               this.fire('locationfound', data);
        },
 
-       _onZoomTransitionEnd: function () {
-               if (!this._animatingZoom) { return; }
+       // TODO handler.addTo
+       // TODO Appropiate docs section?
+       // @section Other Methods
+       // @method addHandler(name: String, HandlerClass: Function): this
+       // Adds a new `Handler` to the map, given its name and constructor function.
+       addHandler: function (name, HandlerClass) {
+               if (!HandlerClass) { return this; }
 
-               L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+               var handler = this[name] = new HandlerClass(this);
 
-               this._animatingZoom = false;
+               this._handlers.push(handler);
 
-               this._move(this._animateToCenter, this._animateToZoom);
+               if (this.options[name]) {
+                       handler.enable();
+               }
 
-               // This anim frame should prevent an obscure iOS webkit tile loading race condition.
-               L.Util.requestAnimFrame(function () {
-                       this._moveEnd(true);
-               }, this);
-       }
-});
+               return this;
+       },
 
-// @section
+       // @method remove(): this
+       // Destroys the map and clears all related event listeners.
+       remove: function () {
 
-// @factory L.map(id: String, options?: Map options)
-// Instantiates a map object given the DOM ID of a `<div>` element
-// and optionally an object literal with `Map options`.
-//
-// @alternative
-// @factory L.map(el: HTMLElement, options?: Map options)
-// Instantiates a map object given an instance of a `<div>` HTML element
-// and optionally an object literal with `Map options`.
-L.map = function (id, options) {
-       return new L.Map(id, options);
-};
+               this._initEvents(true);
 
+               if (this._containerId !== this._container._leaflet_id) {
+                       throw new Error('Map container is being reused by another instance');
+               }
 
+               try {
+                       // throws error in IE6-8
+                       delete this._container._leaflet_id;
+                       delete this._containerId;
+               } catch (e) {
+                       /*eslint-disable */
+                       this._container._leaflet_id = undefined;
+                       /*eslint-enable */
+                       this._containerId = undefined;
+               }
 
+               remove(this._mapPane);
 
-/*
- * @class Layer
- * @inherits Evented
- * @aka L.Layer
- * @aka ILayer
- *
- * A set of methods from the Layer base class that all Leaflet layers use.
- * Inherits all methods, options and events from `L.Evented`.
- *
- * @example
- *
- * ```js
- * var layer = L.Marker(latlng).addTo(map);
- * layer.addTo(map);
- * layer.remove();
- * ```
- *
- * @event add: Event
- * Fired after the layer is added to a map
- *
- * @event remove: Event
- * Fired after the layer is removed from a map
- */
+               if (this._clearControlPos) {
+                       this._clearControlPos();
+               }
 
+               this._clearHandlers();
 
-L.Layer = L.Evented.extend({
+               if (this._loaded) {
+                       // @section Map state change events
+                       // @event unload: Event
+                       // Fired when the map is destroyed with [remove](#map-remove) method.
+                       this.fire('unload');
+               }
 
-       // Classes extending `L.Layer` will inherit the following options:
-       options: {
-               // @option pane: String = 'overlayPane'
-               // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
-               pane: 'overlayPane',
-               nonBubblingEvents: [],  // Array of events that should not be bubbled to DOM parents (like the map),
+               var i;
+               for (i in this._layers) {
+                       this._layers[i].remove();
+               }
+               for (i in this._panes) {
+                       remove(this._panes[i]);
+               }
 
-               // @option attribution: String = null
-               // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
-               attribution: null
-       },
+               this._layers = [];
+               this._panes = [];
+               delete this._mapPane;
+               delete this._renderer;
 
-       /* @section
-        * Classes extending `L.Layer` will inherit the following methods:
-        *
-        * @method addTo(map: Map): this
-        * Adds the layer to the given map
-        */
-       addTo: function (map) {
-               map.addLayer(this);
                return this;
        },
 
-       // @method remove: this
-       // Removes the layer from the map it is currently active on.
-       remove: function () {
-               return this.removeFrom(this._map || this._mapToAdd);
-       },
+       // @section Other Methods
+       // @method createPane(name: String, container?: HTMLElement): HTMLElement
+       // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
+       // then returns it. The pane is created as a child of `container`, or
+       // as a child of the main map pane if not set.
+       createPane: function (name, container) {
+               var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
+                   pane = create$1('div', className, container || this._mapPane);
 
-       // @method removeFrom(map: Map): this
-       // Removes the layer from the given map
-       removeFrom: function (obj) {
-               if (obj) {
-                       obj.removeLayer(this);
+               if (name) {
+                       this._panes[name] = pane;
                }
-               return this;
+               return pane;
        },
 
-       // @method getPane(name? : String): HTMLElement
-       // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
-       getPane: function (name) {
-               return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
-       },
+       // @section Methods for Getting Map State
 
-       addInteractiveTarget: function (targetEl) {
-               this._map._targets[L.stamp(targetEl)] = this;
-               return this;
+       // @method getCenter(): LatLng
+       // Returns the geographical center of the map view
+       getCenter: function () {
+               this._checkIfLoaded();
+
+               if (this._lastCenter && !this._moved()) {
+                       return this._lastCenter;
+               }
+               return this.layerPointToLatLng(this._getCenterLayerPoint());
        },
 
-       removeInteractiveTarget: function (targetEl) {
-               delete this._map._targets[L.stamp(targetEl)];
-               return this;
+       // @method getZoom(): Number
+       // Returns the current zoom level of the map view
+       getZoom: function () {
+               return this._zoom;
        },
 
-       // @method getAttribution: String
-       // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
-       getAttribution: function () {
-               return this.options.attribution;
+       // @method getBounds(): LatLngBounds
+       // Returns the geographical bounds visible in the current map view
+       getBounds: function () {
+               var bounds = this.getPixelBounds(),
+                   sw = this.unproject(bounds.getBottomLeft()),
+                   ne = this.unproject(bounds.getTopRight());
+
+               return new LatLngBounds(sw, ne);
        },
 
-       _layerAdd: function (e) {
-               var map = e.target;
+       // @method getMinZoom(): Number
+       // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
+       getMinZoom: function () {
+               return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
+       },
 
-               // check in case layer gets added and then removed before the map is ready
-               if (!map.hasLayer(this)) { return; }
+       // @method getMaxZoom(): Number
+       // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
+       getMaxZoom: function () {
+               return this.options.maxZoom === undefined ?
+                       (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
+                       this.options.maxZoom;
+       },
 
-               this._map = map;
-               this._zoomAnimated = map._zoomAnimated;
+       // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
+       // Returns the maximum zoom level on which the given bounds fit to the map
+       // view in its entirety. If `inside` (optional) is set to `true`, the method
+       // instead returns the minimum zoom level on which the map view fits into
+       // the given bounds in its entirety.
+       getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
+               bounds = toLatLngBounds(bounds);
+               padding = toPoint(padding || [0, 0]);
 
-               if (this.getEvents) {
-                       var events = this.getEvents();
-                       map.on(events, this);
-                       this.once('remove', function () {
-                               map.off(events, this);
-                       }, this);
-               }
+               var zoom = this.getZoom() || 0,
+                   min = this.getMinZoom(),
+                   max = this.getMaxZoom(),
+                   nw = bounds.getNorthWest(),
+                   se = bounds.getSouthEast(),
+                   size = this.getSize().subtract(padding),
+                   boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
+                   snap = any3d ? this.options.zoomSnap : 1,
+                   scalex = size.x / boundsSize.x,
+                   scaley = size.y / boundsSize.y,
+                   scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
 
-               this.onAdd(map);
+               zoom = this.getScaleZoom(scale, zoom);
 
-               if (this.getAttribution && map.attributionControl) {
-                       map.attributionControl.addAttribution(this.getAttribution());
+               if (snap) {
+                       zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
+                       zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
                }
 
-               this.fire('add');
-               map.fire('layeradd', {layer: this});
-       }
-});
+               return Math.max(min, Math.min(max, zoom));
+       },
 
-/* @section Extension methods
- * @uninheritable
- *
- * Every layer should extend from `L.Layer` and (re-)implement the following methods.
- *
- * @method onAdd(map: Map): this
- * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
- *
- * @method onRemove(map: Map): this
- * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
- *
- * @method getEvents(): Object
- * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
- *
- * @method getAttribution(): String
- * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
- *
- * @method beforeAdd(map: Map): this
- * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
- */
+       // @method getSize(): Point
+       // Returns the current size of the map container (in pixels).
+       getSize: function () {
+               if (!this._size || this._sizeChanged) {
+                       this._size = new Point(
+                               this._container.clientWidth || 0,
+                               this._container.clientHeight || 0);
 
+                       this._sizeChanged = false;
+               }
+               return this._size.clone();
+       },
 
-/* @namespace Map
- * @section Layer events
- *
- * @event layeradd: LayerEvent
- * Fired when a new layer is added to the map.
- *
- * @event layerremove: LayerEvent
- * Fired when some layer is removed from the map
- *
- * @section Methods for Layers and Controls
- */
-L.Map.include({
-       // @method addLayer(layer: Layer): this
-       // Adds the given layer to the map
-       addLayer: function (layer) {
-               var id = L.stamp(layer);
-               if (this._layers[id]) { return this; }
-               this._layers[id] = layer;
+       // @method getPixelBounds(): Bounds
+       // Returns the bounds of the current map view in projected pixel
+       // coordinates (sometimes useful in layer and overlay implementations).
+       getPixelBounds: function (center, zoom) {
+               var topLeftPoint = this._getTopLeftPoint(center, zoom);
+               return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
+       },
 
-               layer._mapToAdd = this;
+       // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
+       // the map pane? "left point of the map layer" can be confusing, specially
+       // since there can be negative offsets.
+       // @method getPixelOrigin(): Point
+       // Returns the projected pixel coordinates of the top left point of
+       // the map layer (useful in custom layer and overlay implementations).
+       getPixelOrigin: function () {
+               this._checkIfLoaded();
+               return this._pixelOrigin;
+       },
 
-               if (layer.beforeAdd) {
-                       layer.beforeAdd(this);
-               }
+       // @method getPixelWorldBounds(zoom?: Number): Bounds
+       // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
+       // If `zoom` is omitted, the map's current zoom level is used.
+       getPixelWorldBounds: function (zoom) {
+               return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
+       },
 
-               this.whenReady(layer._layerAdd, layer);
+       // @section Other Methods
 
-               return this;
+       // @method getPane(pane: String|HTMLElement): HTMLElement
+       // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
+       getPane: function (pane) {
+               return typeof pane === 'string' ? this._panes[pane] : pane;
        },
 
-       // @method removeLayer(layer: Layer): this
-       // Removes the given layer from the map.
-       removeLayer: function (layer) {
-               var id = L.stamp(layer);
-
-               if (!this._layers[id]) { return this; }
+       // @method getPanes(): Object
+       // Returns a plain object containing the names of all [panes](#map-pane) as keys and
+       // the panes as values.
+       getPanes: function () {
+               return this._panes;
+       },
 
-               if (this._loaded) {
-                       layer.onRemove(this);
-               }
+       // @method getContainer: HTMLElement
+       // Returns the HTML element that contains the map.
+       getContainer: function () {
+               return this._container;
+       },
 
-               if (layer.getAttribution && this.attributionControl) {
-                       this.attributionControl.removeAttribution(layer.getAttribution());
-               }
 
-               delete this._layers[id];
+       // @section Conversion Methods
 
-               if (this._loaded) {
-                       this.fire('layerremove', {layer: layer});
-                       layer.fire('remove');
-               }
+       // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
+       // Returns the scale factor to be applied to a map transition from zoom level
+       // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
+       getZoomScale: function (toZoom, fromZoom) {
+               // TODO replace with universal implementation after refactoring projections
+               var crs = this.options.crs;
+               fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
+               return crs.scale(toZoom) / crs.scale(fromZoom);
+       },
 
-               layer._map = layer._mapToAdd = null;
+       // @method getScaleZoom(scale: Number, fromZoom: Number): Number
+       // Returns the zoom level that the map would end up at, if it is at `fromZoom`
+       // level and everything is scaled by a factor of `scale`. Inverse of
+       // [`getZoomScale`](#map-getZoomScale).
+       getScaleZoom: function (scale, fromZoom) {
+               var crs = this.options.crs;
+               fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
+               var zoom = crs.zoom(scale * crs.scale(fromZoom));
+               return isNaN(zoom) ? Infinity : zoom;
+       },
 
-               return this;
+       // @method project(latlng: LatLng, zoom: Number): Point
+       // Projects a geographical coordinate `LatLng` according to the projection
+       // of the map's CRS, then scales it according to `zoom` and the CRS's
+       // `Transformation`. The result is pixel coordinate relative to
+       // the CRS origin.
+       project: function (latlng, zoom) {
+               zoom = zoom === undefined ? this._zoom : zoom;
+               return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
        },
 
-       // @method hasLayer(layer: Layer): Boolean
-       // Returns `true` if the given layer is currently added to the map
-       hasLayer: function (layer) {
-               return !!layer && (L.stamp(layer) in this._layers);
+       // @method unproject(point: Point, zoom: Number): LatLng
+       // Inverse of [`project`](#map-project).
+       unproject: function (point, zoom) {
+               zoom = zoom === undefined ? this._zoom : zoom;
+               return this.options.crs.pointToLatLng(toPoint(point), zoom);
        },
 
-       /* @method eachLayer(fn: Function, context?: Object): this
-        * Iterates over the layers of the map, optionally specifying context of the iterator function.
-        * ```
-        * map.eachLayer(function(layer){
-        *     layer.bindPopup('Hello');
-        * });
-        * ```
-        */
-       eachLayer: function (method, context) {
-               for (var i in this._layers) {
-                       method.call(context, this._layers[i]);
-               }
-               return this;
+       // @method layerPointToLatLng(point: Point): LatLng
+       // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
+       // returns the corresponding geographical coordinate (for the current zoom level).
+       layerPointToLatLng: function (point) {
+               var projectedPoint = toPoint(point).add(this.getPixelOrigin());
+               return this.unproject(projectedPoint);
        },
 
-       _addLayers: function (layers) {
-               layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
+       // @method latLngToLayerPoint(latlng: LatLng): Point
+       // Given a geographical coordinate, returns the corresponding pixel coordinate
+       // relative to the [origin pixel](#map-getpixelorigin).
+       latLngToLayerPoint: function (latlng) {
+               var projectedPoint = this.project(toLatLng(latlng))._round();
+               return projectedPoint._subtract(this.getPixelOrigin());
+       },
 
-               for (var i = 0, len = layers.length; i < len; i++) {
-                       this.addLayer(layers[i]);
-               }
+       // @method wrapLatLng(latlng: LatLng): LatLng
+       // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
+       // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
+       // CRS's bounds.
+       // By default this means longitude is wrapped around the dateline so its
+       // value is between -180 and +180 degrees.
+       wrapLatLng: function (latlng) {
+               return this.options.crs.wrapLatLng(toLatLng(latlng));
        },
 
-       _addZoomLimit: function (layer) {
-               if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
-                       this._zoomBoundLayers[L.stamp(layer)] = layer;
-                       this._updateZoomLevels();
-               }
+       // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
+       // Returns a `LatLngBounds` with the same size as the given one, ensuring that
+       // its center is within the CRS's bounds.
+       // By default this means the center longitude is wrapped around the dateline so its
+       // value is between -180 and +180 degrees, and the majority of the bounds
+       // overlaps the CRS's bounds.
+       wrapLatLngBounds: function (latlng) {
+               return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
        },
 
-       _removeZoomLimit: function (layer) {
-               var id = L.stamp(layer);
+       // @method distance(latlng1: LatLng, latlng2: LatLng): Number
+       // Returns the distance between two geographical coordinates according to
+       // the map's CRS. By default this measures distance in meters.
+       distance: function (latlng1, latlng2) {
+               return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
+       },
 
-               if (this._zoomBoundLayers[id]) {
-                       delete this._zoomBoundLayers[id];
-                       this._updateZoomLevels();
-               }
+       // @method containerPointToLayerPoint(point: Point): Point
+       // Given a pixel coordinate relative to the map container, returns the corresponding
+       // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
+       containerPointToLayerPoint: function (point) { // (Point)
+               return toPoint(point).subtract(this._getMapPanePos());
        },
 
-       _updateZoomLevels: function () {
-               var minZoom = Infinity,
-                   maxZoom = -Infinity,
-                   oldZoomSpan = this._getZoomSpan();
+       // @method layerPointToContainerPoint(point: Point): Point
+       // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
+       // returns the corresponding pixel coordinate relative to the map container.
+       layerPointToContainerPoint: function (point) { // (Point)
+               return toPoint(point).add(this._getMapPanePos());
+       },
 
-               for (var i in this._zoomBoundLayers) {
-                       var options = this._zoomBoundLayers[i].options;
+       // @method containerPointToLatLng(point: Point): LatLng
+       // Given a pixel coordinate relative to the map container, returns
+       // the corresponding geographical coordinate (for the current zoom level).
+       containerPointToLatLng: function (point) {
+               var layerPoint = this.containerPointToLayerPoint(toPoint(point));
+               return this.layerPointToLatLng(layerPoint);
+       },
 
-                       minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
-                       maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
-               }
+       // @method latLngToContainerPoint(latlng: LatLng): Point
+       // Given a geographical coordinate, returns the corresponding pixel coordinate
+       // relative to the map container.
+       latLngToContainerPoint: function (latlng) {
+               return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
+       },
 
-               this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
-               this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
+       // @method mouseEventToContainerPoint(ev: MouseEvent): Point
+       // Given a MouseEvent object, returns the pixel coordinate relative to the
+       // map container where the event took place.
+       mouseEventToContainerPoint: function (e) {
+               return getMousePosition(e, this._container);
+       },
 
-               // @section Map state change events
-               // @event zoomlevelschange: Event
-               // Fired when the number of zoomlevels on the map is changed due
-               // to adding or removing a layer.
-               if (oldZoomSpan !== this._getZoomSpan()) {
-                       this.fire('zoomlevelschange');
-               }
+       // @method mouseEventToLayerPoint(ev: MouseEvent): Point
+       // Given a MouseEvent object, returns the pixel coordinate relative to
+       // the [origin pixel](#map-getpixelorigin) where the event took place.
+       mouseEventToLayerPoint: function (e) {
+               return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
+       },
 
-               if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
-                       this.setZoom(this._layersMaxZoom);
-               }
-               if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
-                       this.setZoom(this._layersMinZoom);
-               }
-       }
-});
+       // @method mouseEventToLatLng(ev: MouseEvent): LatLng
+       // Given a MouseEvent object, returns geographical coordinate where the
+       // event took place.
+       mouseEventToLatLng: function (e) { // (MouseEvent)
+               return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
+       },
 
 
+       // map initialization methods
 
-/*
- * @namespace DomEvent
- * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
- */
+       _initContainer: function (id) {
+               var container = this._container = get(id);
 
-// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
+               if (!container) {
+                       throw new Error('Map container not found.');
+               } else if (container._leaflet_id) {
+                       throw new Error('Map container is already initialized.');
+               }
 
+               on(container, 'scroll', this._onScroll, this);
+               this._containerId = stamp(container);
+       },
 
+       _initLayout: function () {
+               var container = this._container;
 
-var eventsKey = '_leaflet_events';
+               this._fadeAnimated = this.options.fadeAnimation && any3d;
 
-L.DomEvent = {
+               addClass(container, 'leaflet-container' +
+                       (touch ? ' leaflet-touch' : '') +
+                       (retina ? ' leaflet-retina' : '') +
+                       (ielt9 ? ' leaflet-oldie' : '') +
+                       (safari ? ' leaflet-safari' : '') +
+                       (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
 
-       // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
-       // Adds a listener function (`fn`) to a particular DOM event type of the
-       // element `el`. You can optionally specify the context of the listener
-       // (object the `this` keyword will point to). You can also pass several
-       // space-separated types (e.g. `'click dblclick'`).
+               var position = getStyle(container, 'position');
 
-       // @alternative
-       // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
-       // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-       on: function (obj, types, fn, context) {
+               if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
+                       container.style.position = 'relative';
+               }
 
-               if (typeof types === 'object') {
-                       for (var type in types) {
-                               this._on(obj, type, types[type], fn);
-                       }
-               } else {
-                       types = L.Util.splitWords(types);
+               this._initPanes();
 
-                       for (var i = 0, len = types.length; i < len; i++) {
-                               this._on(obj, types[i], fn, context);
-                       }
+               if (this._initControlPos) {
+                       this._initControlPos();
                }
-
-               return this;
        },
 
-       // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
-       // Removes a previously added listener function. If no function is specified,
-       // it will remove all the listeners of that particular DOM event from the element.
-       // Note that if you passed a custom context to on, you must pass the same
-       // context to `off` in order to remove the listener.
+       _initPanes: function () {
+               var panes = this._panes = {};
+               this._paneRenderers = {};
 
-       // @alternative
-       // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
-       // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-       off: function (obj, types, fn, context) {
+               // @section
+               //
+               // Panes are DOM elements used to control the ordering of layers on the map. You
+               // can access panes with [`map.getPane`](#map-getpane) or
+               // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
+               // [`map.createPane`](#map-createpane) method.
+               //
+               // Every map has the following default panes that differ only in zIndex.
+               //
+               // @pane mapPane: HTMLElement = 'auto'
+               // Pane that contains all other map panes
 
-               if (typeof types === 'object') {
-                       for (var type in types) {
-                               this._off(obj, type, types[type], fn);
-                       }
-               } else {
-                       types = L.Util.splitWords(types);
+               this._mapPane = this.createPane('mapPane', this._container);
+               setPosition(this._mapPane, new Point(0, 0));
+
+               // @pane tilePane: HTMLElement = 200
+               // Pane for `GridLayer`s and `TileLayer`s
+               this.createPane('tilePane');
+               // @pane overlayPane: HTMLElement = 400
+               // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
+               this.createPane('shadowPane');
+               // @pane shadowPane: HTMLElement = 500
+               // Pane for overlay shadows (e.g. `Marker` shadows)
+               this.createPane('overlayPane');
+               // @pane markerPane: HTMLElement = 600
+               // Pane for `Icon`s of `Marker`s
+               this.createPane('markerPane');
+               // @pane tooltipPane: HTMLElement = 650
+               // Pane for tooltip.
+               this.createPane('tooltipPane');
+               // @pane popupPane: HTMLElement = 700
+               // Pane for `Popup`s.
+               this.createPane('popupPane');
 
-                       for (var i = 0, len = types.length; i < len; i++) {
-                               this._off(obj, types[i], fn, context);
-                       }
+               if (!this.options.markerZoomAnimation) {
+                       addClass(panes.markerPane, 'leaflet-zoom-hide');
+                       addClass(panes.shadowPane, 'leaflet-zoom-hide');
                }
-
-               return this;
        },
 
-       _on: function (obj, type, fn, context) {
-               var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
-
-               if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
-
-               var handler = function (e) {
-                       return fn.call(context || obj, e || window.event);
-               };
-
-               var originalHandler = handler;
 
-               if (L.Browser.pointer && type.indexOf('touch') === 0) {
-                       this.addPointerListener(obj, type, handler, id);
+       // private methods that modify map state
 
-               } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
-                          !(L.Browser.pointer && L.Browser.chrome)) {
-                       // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
-                       // See #5180
-                       this.addDoubleTapListener(obj, handler, id);
+       // @section Map state change events
+       _resetView: function (center, zoom) {
+               setPosition(this._mapPane, new Point(0, 0));
 
-               } else if ('addEventListener' in obj) {
+               var loading = !this._loaded;
+               this._loaded = true;
+               zoom = this._limitZoom(zoom);
 
-                       if (type === 'mousewheel') {
-                               obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+               this.fire('viewprereset');
 
-                       } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
-                               handler = function (e) {
-                                       e = e || window.event;
-                                       if (L.DomEvent._isExternalTarget(obj, e)) {
-                                               originalHandler(e);
-                                       }
-                               };
-                               obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
+               var zoomChanged = this._zoom !== zoom;
+               this
+                       ._moveStart(zoomChanged)
+                       ._move(center, zoom)
+                       ._moveEnd(zoomChanged);
 
-                       } else {
-                               if (type === 'click' && L.Browser.android) {
-                                       handler = function (e) {
-                                               return L.DomEvent._filterClick(e, originalHandler);
-                                       };
-                               }
-                               obj.addEventListener(type, handler, false);
-                       }
+               // @event viewreset: Event
+               // Fired when the map needs to redraw its content (this usually happens
+               // on map zoom or load). Very useful for creating custom overlays.
+               this.fire('viewreset');
 
-               } else if ('attachEvent' in obj) {
-                       obj.attachEvent('on' + type, handler);
+               // @event load: Event
+               // Fired when the map is initialized (when its center and zoom are set
+               // for the first time).
+               if (loading) {
+                       this.fire('load');
                }
-
-               obj[eventsKey] = obj[eventsKey] || {};
-               obj[eventsKey][id] = handler;
-
-               return this;
        },
 
-       _off: function (obj, type, fn, context) {
-
-               var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
-                   handler = obj[eventsKey] && obj[eventsKey][id];
-
-               if (!handler) { return this; }
-
-               if (L.Browser.pointer && type.indexOf('touch') === 0) {
-                       this.removePointerListener(obj, type, id);
+       _moveStart: function (zoomChanged) {
+               // @event zoomstart: Event
+               // Fired when the map zoom is about to change (e.g. before zoom animation).
+               // @event movestart: Event
+               // Fired when the view of the map starts changing (e.g. user starts dragging the map).
+               if (zoomChanged) {
+                       this.fire('zoomstart');
+               }
+               return this.fire('movestart');
+       },
 
-               } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
-                       this.removeDoubleTapListener(obj, id);
+       _move: function (center, zoom, data) {
+               if (zoom === undefined) {
+                       zoom = this._zoom;
+               }
+               var zoomChanged = this._zoom !== zoom;
 
-               } else if ('removeEventListener' in obj) {
+               this._zoom = zoom;
+               this._lastCenter = center;
+               this._pixelOrigin = this._getNewPixelOrigin(center);
 
-                       if (type === 'mousewheel') {
-                               obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+               // @event zoom: Event
+               // Fired repeatedly during any change in zoom level, including zoom
+               // and fly animations.
+               if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
+                       this.fire('zoom', data);
+               }
 
-                       } else {
-                               obj.removeEventListener(
-                                       type === 'mouseenter' ? 'mouseover' :
-                                       type === 'mouseleave' ? 'mouseout' : type, handler, false);
-                       }
+               // @event move: Event
+               // Fired repeatedly during any movement of the map, including pan and
+               // fly animations.
+               return this.fire('move', data);
+       },
 
-               } else if ('detachEvent' in obj) {
-                       obj.detachEvent('on' + type, handler);
+       _moveEnd: function (zoomChanged) {
+               // @event zoomend: Event
+               // Fired when the map has changed, after any animations.
+               if (zoomChanged) {
+                       this.fire('zoomend');
                }
 
-               obj[eventsKey][id] = null;
-
-               return this;
+               // @event moveend: Event
+               // Fired when the center of the map stops changing (e.g. user stopped
+               // dragging the map).
+               return this.fire('moveend');
        },
 
-       // @function stopPropagation(ev: DOMEvent): this
-       // Stop the given event from propagation to parent elements. Used inside the listener functions:
-       // ```js
-       // L.DomEvent.on(div, 'click', function (ev) {
-       //      L.DomEvent.stopPropagation(ev);
-       // });
-       // ```
-       stopPropagation: function (e) {
-
-               if (e.stopPropagation) {
-                       e.stopPropagation();
-               } else if (e.originalEvent) {  // In case of Leaflet event.
-                       e.originalEvent._stopped = true;
-               } else {
-                       e.cancelBubble = true;
+       _stop: function () {
+               cancelAnimFrame(this._flyToFrame);
+               if (this._panAnim) {
+                       this._panAnim.stop();
                }
-               L.DomEvent._skipped(e);
-
                return this;
        },
 
-       // @function disableScrollPropagation(el: HTMLElement): this
-       // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
-       disableScrollPropagation: function (el) {
-               return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
+       _rawPanBy: function (offset) {
+               setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
        },
 
-       // @function disableClickPropagation(el: HTMLElement): this
-       // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
-       // `'mousedown'` and `'touchstart'` events (plus browser variants).
-       disableClickPropagation: function (el) {
-               var stop = L.DomEvent.stopPropagation;
-
-               L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
-
-               return L.DomEvent.on(el, {
-                       click: L.DomEvent._fakeStop,
-                       dblclick: stop
-               });
+       _getZoomSpan: function () {
+               return this.getMaxZoom() - this.getMinZoom();
        },
 
-       // @function preventDefault(ev: DOMEvent): this
-       // Prevents the default action of the DOM Event `ev` from happening (such as
-       // following a link in the href of the a element, or doing a POST request
-       // with page reload when a `<form>` is submitted).
-       // Use it inside listener functions.
-       preventDefault: function (e) {
-
-               if (e.preventDefault) {
-                       e.preventDefault();
-               } else {
-                       e.returnValue = false;
+       _panInsideMaxBounds: function () {
+               if (!this._enforcingBounds) {
+                       this.panInsideBounds(this.options.maxBounds);
                }
-               return this;
-       },
-
-       // @function stop(ev): this
-       // Does `stopPropagation` and `preventDefault` at the same time.
-       stop: function (e) {
-               return L.DomEvent
-                       .preventDefault(e)
-                       .stopPropagation(e);
        },
 
-       // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
-       // Gets normalized mouse position from a DOM event relative to the
-       // `container` or to the whole page if not specified.
-       getMousePosition: function (e, container) {
-               if (!container) {
-                       return new L.Point(e.clientX, e.clientY);
+       _checkIfLoaded: function () {
+               if (!this._loaded) {
+                       throw new Error('Set map center and zoom first.');
                }
+       },
 
-               var rect = container.getBoundingClientRect();
+       // DOM event handling
 
-               return new L.Point(
-                       e.clientX - rect.left - container.clientLeft,
-                       e.clientY - rect.top - container.clientTop);
-       },
+       // @section Interaction events
+       _initEvents: function (remove$$1) {
+               this._targets = {};
+               this._targets[stamp(this._container)] = this;
 
-       // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
-       // and Firefox scrolls device pixels, not CSS pixels
-       _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
-                       L.Browser.gecko ? window.devicePixelRatio :
-                       1,
+               var onOff = remove$$1 ? off : on;
 
-       // @function getWheelDelta(ev: DOMEvent): Number
-       // Gets normalized wheel delta from a mousewheel DOM event, in vertical
-       // pixels scrolled (negative if scrolling down).
-       // Events from pointing devices without precise scrolling are mapped to
-       // a best guess of 60 pixels.
-       getWheelDelta: function (e) {
-               return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
-                      (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
-                      (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
-                      (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
-                      (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
-                      e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
-                      (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
-                      e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
-                      0;
-       },
+               // @event click: MouseEvent
+               // Fired when the user clicks (or taps) the map.
+               // @event dblclick: MouseEvent
+               // Fired when the user double-clicks (or double-taps) the map.
+               // @event mousedown: MouseEvent
+               // Fired when the user pushes the mouse button on the map.
+               // @event mouseup: MouseEvent
+               // Fired when the user releases the mouse button on the map.
+               // @event mouseover: MouseEvent
+               // Fired when the mouse enters the map.
+               // @event mouseout: MouseEvent
+               // Fired when the mouse leaves the map.
+               // @event mousemove: MouseEvent
+               // Fired while the mouse moves over the map.
+               // @event contextmenu: MouseEvent
+               // Fired when the user pushes the right mouse button on the map, prevents
+               // default browser context menu from showing if there are listeners on
+               // this event. Also fired on mobile when the user holds a single touch
+               // for a second (also called long press).
+               // @event keypress: KeyboardEvent
+               // Fired when the user presses a key from the keyboard while the map is focused.
+               onOff(this._container, 'click dblclick mousedown mouseup ' +
+                       'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
 
-       _skipEvents: {},
+               if (this.options.trackResize) {
+                       onOff(window, 'resize', this._onResize, this);
+               }
 
-       _fakeStop: function (e) {
-               // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
-               L.DomEvent._skipEvents[e.type] = true;
+               if (any3d && this.options.transform3DLimit) {
+                       (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
+               }
        },
 
-       _skipped: function (e) {
-               var skipped = this._skipEvents[e.type];
-               // reset when checking, as it's only used in map container and propagates outside of the map
-               this._skipEvents[e.type] = false;
-               return skipped;
+       _onResize: function () {
+               cancelAnimFrame(this._resizeRequest);
+               this._resizeRequest = requestAnimFrame(
+                       function () { this.invalidateSize({debounceMoveend: true}); }, this);
        },
 
-       // check if element really left/entered the event target (for mouseenter/mouseleave)
-       _isExternalTarget: function (el, e) {
+       _onScroll: function () {
+               this._container.scrollTop  = 0;
+               this._container.scrollLeft = 0;
+       },
 
-               var related = e.relatedTarget;
+       _onMoveEnd: function () {
+               var pos = this._getMapPanePos();
+               if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
+                       // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
+                       // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
+                       this._resetView(this.getCenter(), this.getZoom());
+               }
+       },
 
-               if (!related) { return true; }
+       _findEventTargets: function (e, type) {
+               var targets = [],
+                   target,
+                   isHover = type === 'mouseout' || type === 'mouseover',
+                   src = e.target || e.srcElement,
+                   dragging = false;
 
-               try {
-                       while (related && (related !== el)) {
-                               related = related.parentNode;
+               while (src) {
+                       target = this._targets[stamp(src)];
+                       if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
+                               // Prevent firing click after you just dragged an object.
+                               dragging = true;
+                               break;
                        }
-               } catch (err) {
-                       return false;
+                       if (target && target.listens(type, true)) {
+                               if (isHover && !isExternalTarget(src, e)) { break; }
+                               targets.push(target);
+                               if (isHover) { break; }
+                       }
+                       if (src === this._container) { break; }
+                       src = src.parentNode;
                }
-               return (related !== el);
+               if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
+                       targets = [this];
+               }
+               return targets;
        },
 
-       // this is a horrible workaround for a bug in Android where a single touch triggers two click events
-       _filterClick: function (e, handler) {
-               var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
-                   elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
+       _handleDOMEvent: function (e) {
+               if (!this._loaded || skipped(e)) { return; }
 
-               // are they closer together than 500ms yet more than 100ms?
-               // Android typically triggers them ~300ms apart while multiple listeners
-               // on the same event should be triggered far faster;
-               // or check if click is simulated on the element, and if it is, reject any non-simulated events
+               var type = e.type;
 
-               if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
-                       L.DomEvent.stop(e);
-                       return;
+               if (type === 'mousedown' || type === 'keypress') {
+                       // prevents outline when clicking on keyboard-focusable element
+                       preventOutline(e.target || e.srcElement);
                }
-               L.DomEvent._lastClick = timeStamp;
-
-               handler(e);
-       }
-};
 
-// @function addListener(…): this
-// Alias to [`L.DomEvent.on`](#domevent-on)
-L.DomEvent.addListener = L.DomEvent.on;
+               this._fireDOMEvent(e, type);
+       },
 
-// @function removeListener(…): this
-// Alias to [`L.DomEvent.off`](#domevent-off)
-L.DomEvent.removeListener = L.DomEvent.off;
+       _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
 
+       _fireDOMEvent: function (e, type, targets) {
 
+               if (e.type === 'click') {
+                       // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
+                       // @event preclick: MouseEvent
+                       // Fired before mouse click on the map (sometimes useful when you
+                       // want something to happen on click before any existing click
+                       // handlers start running).
+                       var synth = extend({}, e);
+                       synth.type = 'preclick';
+                       this._fireDOMEvent(synth, synth.type, targets);
+               }
 
-/*
- * @class PosAnimation
- * @aka L.PosAnimation
- * @inherits Evented
- * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
- *
- * @example
- * ```js
- * var fx = new L.PosAnimation();
- * fx.run(el, [300, 500], 0.5);
- * ```
- *
- * @constructor L.PosAnimation()
- * Creates a `PosAnimation` object.
- *
- */
+               if (e._stopped) { return; }
 
-L.PosAnimation = L.Evented.extend({
+               // Find the layer the event is propagating from and its parents.
+               targets = (targets || []).concat(this._findEventTargets(e, type));
 
-       // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
-       // Run an animation of a given element to a new position, optionally setting
-       // duration in seconds (`0.25` by default) and easing linearity factor (3rd
-       // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
-       // `0.5` by default).
-       run: function (el, newPos, duration, easeLinearity) {
-               this.stop();
+               if (!targets.length) { return; }
 
-               this._el = el;
-               this._inProgress = true;
-               this._duration = duration || 0.25;
-               this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
+               var target = targets[0];
+               if (type === 'contextmenu' && target.listens(type, true)) {
+                       preventDefault(e);
+               }
 
-               this._startPos = L.DomUtil.getPosition(el);
-               this._offset = newPos.subtract(this._startPos);
-               this._startTime = +new Date();
+               var data = {
+                       originalEvent: e
+               };
 
-               // @event start: Event
-               // Fired when the animation starts
-               this.fire('start');
+               if (e.type !== 'keypress') {
+                       var isMarker = (target.options && 'icon' in target.options);
+                       data.containerPoint = isMarker ?
+                                       this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
+                       data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
+                       data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
+               }
 
-               this._animate();
+               for (var i = 0; i < targets.length; i++) {
+                       targets[i].fire(type, data, true);
+                       if (data.originalEvent._stopped ||
+                               (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
+               }
        },
 
-       // @method stop()
-       // Stops the animation (if currently running).
-       stop: function () {
-               if (!this._inProgress) { return; }
-
-               this._step(true);
-               this._complete();
+       _draggableMoved: function (obj) {
+               obj = obj.dragging && obj.dragging.enabled() ? obj : this;
+               return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
        },
 
-       _animate: function () {
-               // animation loop
-               this._animId = L.Util.requestAnimFrame(this._animate, this);
-               this._step();
+       _clearHandlers: function () {
+               for (var i = 0, len = this._handlers.length; i < len; i++) {
+                       this._handlers[i].disable();
+               }
        },
 
-       _step: function (round) {
-               var elapsed = (+new Date()) - this._startTime,
-                   duration = this._duration * 1000;
+       // @section Other Methods
 
-               if (elapsed < duration) {
-                       this._runFrame(this._easeOut(elapsed / duration), round);
+       // @method whenReady(fn: Function, context?: Object): this
+       // Runs the given function `fn` when the map gets initialized with
+       // a view (center and zoom) and at least one layer, or immediately
+       // if it's already initialized, optionally passing a function context.
+       whenReady: function (callback, context) {
+               if (this._loaded) {
+                       callback.call(context || this, {target: this});
                } else {
-                       this._runFrame(1);
-                       this._complete();
+                       this.on('load', callback, context);
                }
+               return this;
        },
 
-       _runFrame: function (progress, round) {
-               var pos = this._startPos.add(this._offset.multiplyBy(progress));
-               if (round) {
-                       pos._round();
-               }
-               L.DomUtil.setPosition(this._el, pos);
 
-               // @event step: Event
-               // Fired continuously during the animation.
-               this.fire('step');
+       // private methods for getting map state
+
+       _getMapPanePos: function () {
+               return getPosition(this._mapPane) || new Point(0, 0);
        },
 
-       _complete: function () {
-               L.Util.cancelAnimFrame(this._animId);
+       _moved: function () {
+               var pos = this._getMapPanePos();
+               return pos && !pos.equals([0, 0]);
+       },
 
-               this._inProgress = false;
-               // @event end: Event
-               // Fired when the animation ends.
-               this.fire('end');
+       _getTopLeftPoint: function (center, zoom) {
+               var pixelOrigin = center && zoom !== undefined ?
+                       this._getNewPixelOrigin(center, zoom) :
+                       this.getPixelOrigin();
+               return pixelOrigin.subtract(this._getMapPanePos());
        },
 
-       _easeOut: function (t) {
-               return 1 - Math.pow(1 - t, this._easeOutPower);
-       }
-});
+       _getNewPixelOrigin: function (center, zoom) {
+               var viewHalf = this.getSize()._divideBy(2);
+               return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
+       },
 
+       _latLngToNewLayerPoint: function (latlng, zoom, center) {
+               var topLeft = this._getNewPixelOrigin(center, zoom);
+               return this.project(latlng, zoom)._subtract(topLeft);
+       },
 
+       _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
+               var topLeft = this._getNewPixelOrigin(center, zoom);
+               return toBounds([
+                       this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
+                       this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
+                       this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
+                       this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
+               ]);
+       },
 
-/*
- * @namespace Projection
- * @projection L.Projection.Mercator
- *
- * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
- */
+       // layer point of the current center
+       _getCenterLayerPoint: function () {
+               return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
+       },
 
-L.Projection.Mercator = {
-       R: 6378137,
-       R_MINOR: 6356752.314245179,
+       // offset of the specified place to the current center in pixels
+       _getCenterOffset: function (latlng) {
+               return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
+       },
 
-       bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
+       // adjust center for view to get inside bounds
+       _limitCenter: function (center, zoom, bounds) {
 
-       project: function (latlng) {
-               var d = Math.PI / 180,
-                   r = this.R,
-                   y = latlng.lat * d,
-                   tmp = this.R_MINOR / r,
-                   e = Math.sqrt(1 - tmp * tmp),
-                   con = e * Math.sin(y);
+               if (!bounds) { return center; }
 
-               var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
-               y = -r * Math.log(Math.max(ts, 1E-10));
+               var centerPoint = this.project(center, zoom),
+                   viewHalf = this.getSize().divideBy(2),
+                   viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
+                   offset = this._getBoundsOffset(viewBounds, bounds, zoom);
+
+               // If offset is less than a pixel, ignore.
+               // This prevents unstable projections from getting into
+               // an infinite loop of tiny offsets.
+               if (offset.round().equals([0, 0])) {
+                       return center;
+               }
 
-               return new L.Point(latlng.lng * d * r, y);
+               return this.unproject(centerPoint.add(offset), zoom);
        },
 
-       unproject: function (point) {
-               var d = 180 / Math.PI,
-                   r = this.R,
-                   tmp = this.R_MINOR / r,
-                   e = Math.sqrt(1 - tmp * tmp),
-                   ts = Math.exp(-point.y / r),
-                   phi = Math.PI / 2 - 2 * Math.atan(ts);
+       // adjust offset for view to get inside bounds
+       _limitOffset: function (offset, bounds) {
+               if (!bounds) { return offset; }
 
-               for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
-                       con = e * Math.sin(phi);
-                       con = Math.pow((1 - con) / (1 + con), e / 2);
-                       dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
-                       phi += dphi;
-               }
+               var viewBounds = this.getPixelBounds(),
+                   newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
 
-               return new L.LatLng(phi * d, point.x * d / r);
-       }
-};
+               return offset.add(this._getBoundsOffset(newBounds, bounds));
+       },
 
+       // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
+       _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
+               var projectedMaxBounds = toBounds(
+                       this.project(maxBounds.getNorthEast(), zoom),
+                       this.project(maxBounds.getSouthWest(), zoom)
+                   ),
+                   minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
+                   maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
 
+                   dx = this._rebound(minOffset.x, -maxOffset.x),
+                   dy = this._rebound(minOffset.y, -maxOffset.y);
 
-/*
- * @namespace CRS
- * @crs L.CRS.EPSG3395
- *
- * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
- */
+               return new Point(dx, dy);
+       },
 
-L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
-       code: 'EPSG:3395',
-       projection: L.Projection.Mercator,
+       _rebound: function (left, right) {
+               return left + right > 0 ?
+                       Math.round(left - right) / 2 :
+                       Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
+       },
 
-       transformation: (function () {
-               var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
-               return new L.Transformation(scale, 0.5, -scale, 0.5);
-       }())
-});
+       _limitZoom: function (zoom) {
+               var min = this.getMinZoom(),
+                   max = this.getMaxZoom(),
+                   snap = any3d ? this.options.zoomSnap : 1;
+               if (snap) {
+                       zoom = Math.round(zoom / snap) * snap;
+               }
+               return Math.max(min, Math.min(max, zoom));
+       },
 
+       _onPanTransitionStep: function () {
+               this.fire('move');
+       },
 
+       _onPanTransitionEnd: function () {
+               removeClass(this._mapPane, 'leaflet-pan-anim');
+               this.fire('moveend');
+       },
 
-/*
- * @class GridLayer
- * @inherits Layer
- * @aka L.GridLayer
- *
- * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
- * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
- *
- *
- * @section Synchronous usage
- * @example
- *
- * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
- *
- * ```js
- * var CanvasLayer = L.GridLayer.extend({
- *     createTile: function(coords){
- *         // create a <canvas> element for drawing
- *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
- *
- *         // setup tile width and height according to the options
- *         var size = this.getTileSize();
- *         tile.width = size.x;
- *         tile.height = size.y;
- *
- *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
- *         var ctx = tile.getContext('2d');
- *
- *         // return the tile so it can be rendered on screen
- *         return tile;
- *     }
- * });
- * ```
- *
- * @section Asynchronous usage
- * @example
- *
- * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
- *
- * ```js
- * var CanvasLayer = L.GridLayer.extend({
- *     createTile: function(coords, done){
- *         var error;
- *
- *         // create a <canvas> element for drawing
- *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
- *
- *         // setup tile width and height according to the options
- *         var size = this.getTileSize();
- *         tile.width = size.x;
- *         tile.height = size.y;
- *
- *         // draw something asynchronously and pass the tile to the done() callback
- *         setTimeout(function() {
- *             done(error, tile);
- *         }, 1000);
- *
- *         return tile;
- *     }
- * });
- * ```
- *
- * @section
- */
+       _tryAnimatedPan: function (center, options) {
+               // difference between the new and current centers in pixels
+               var offset = this._getCenterOffset(center)._floor();
+
+               // don't animate too far unless animate: true specified in options
+               if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
+
+               this.panBy(offset, options);
 
+               return true;
+       },
 
-L.GridLayer = L.Layer.extend({
+       _createAnimProxy: function () {
 
-       // @section
-       // @aka GridLayer options
-       options: {
-               // @option tileSize: Number|Point = 256
-               // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
-               tileSize: 256,
+               var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
+               this._panes.mapPane.appendChild(proxy);
 
-               // @option opacity: Number = 1.0
-               // Opacity of the tiles. Can be used in the `createTile()` function.
-               opacity: 1,
+               this.on('zoomanim', function (e) {
+                       var prop = TRANSFORM,
+                           transform = this._proxy.style[prop];
 
-               // @option updateWhenIdle: Boolean = depends
-               // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
-               updateWhenIdle: L.Browser.mobile,
+                       setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
 
-               // @option updateWhenZooming: Boolean = true
-               // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
-               updateWhenZooming: true,
+                       // workaround for case when transform is the same and so transitionend event is not fired
+                       if (transform === this._proxy.style[prop] && this._animatingZoom) {
+                               this._onZoomTransitionEnd();
+                       }
+               }, this);
 
-               // @option updateInterval: Number = 200
-               // Tiles will not update more than once every `updateInterval` milliseconds when panning.
-               updateInterval: 200,
+               this.on('load moveend', function () {
+                       var c = this.getCenter(),
+                           z = this.getZoom();
+                       setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
+               }, this);
 
-               // @option zIndex: Number = 1
-               // The explicit zIndex of the tile layer.
-               zIndex: 1,
+               this._on('unload', this._destroyAnimProxy, this);
+       },
 
-               // @option bounds: LatLngBounds = undefined
-               // If set, tiles will only be loaded inside the set `LatLngBounds`.
-               bounds: null,
+       _destroyAnimProxy: function () {
+               remove(this._proxy);
+               delete this._proxy;
+       },
 
-               // @option minZoom: Number = 0
-               // The minimum zoom level that tiles will be loaded at. By default the entire map.
-               minZoom: 0,
+       _catchTransitionEnd: function (e) {
+               if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
+        &nbs