Update Leaflet
authorJohn Firebaugh <john.firebaugh@gmail.com>
Mon, 15 Oct 2012 23:23:31 +0000 (16:23 -0700)
committerJohn Firebaugh <john.firebaugh@gmail.com>
Fri, 9 Nov 2012 20:59:28 +0000 (12:59 -0800)
vendor/assets/leaflet/leaflet.css
vendor/assets/leaflet/leaflet.js

index 18ca7a14f071af297420ec31a7435378c6130c60..a9e44fd92dc816821470577b8d2bc307840465ab 100644 (file)
 .leaflet-control-zoom a {\r
        background-color: rgba(255, 255, 255, 0.75);\r
        }\r
-.leaflet-control-zoom a, .leaflet-control-layers a {\r
+.leaflet-control-zoom a, .leaflet-control-layers-toggle {\r
        background-position: 50% 50%;\r
        background-repeat: no-repeat;\r
        display: block;\r
        -webkit-border-radius: 8px;\r
        border-radius: 8px;\r
        }\r
-.leaflet-control-layers a {\r
+.leaflet-control-layers-toggle {\r
        background-image: url(images/layers.png);\r
        width: 36px;\r
        height: 36px;\r
        }\r
-.leaflet-touch .leaflet-control-layers a {\r
+.leaflet-touch .leaflet-control-layers-toggle {\r
        width: 44px;\r
        height: 44px;\r
        }\r
        color: #333;\r
        background: #fff;\r
        }\r
-.leaflet-control-layers input {\r
+.leaflet-control-layers-selector {\r
        margin-top: 2px;\r
        position: relative;\r
        top: 1px;\r
index 6efe5be5ca1c6aebd9f6464bd2fe138e5903996f..7c5b717e77389fd839d659f677793a1c19ab7828 100644 (file)
 */
 (function (window, undefined) {
 
-var L, originalL;\r
-\r
-if (typeof exports !== undefined + '') {\r
-       L = exports;\r
-} else {\r
-       originalL = window.L;\r
-       L = {};\r
-\r
-       L.noConflict = function () {\r
-               window.L = originalL;\r
-               return this;\r
-       };\r
-\r
-       window.L = L;\r
-}\r
-\r
-L.version = '0.4.4';\r
-
-
-/*\r
- * L.Util is a namespace for various utility functions.\r
- */\r
-\r
-L.Util = {\r
-       extend: function (/*Object*/ dest) /*-> Object*/ {      // merge src properties into dest\r
-               var sources = Array.prototype.slice.call(arguments, 1);\r
-               for (var j = 0, len = sources.length, src; j < len; j++) {\r
-                       src = sources[j] || {};\r
-                       for (var i in src) {\r
-                               if (src.hasOwnProperty(i)) {\r
-                                       dest[i] = src[i];\r
-                               }\r
-                       }\r
-               }\r
-               return dest;\r
-       },\r
-\r
-       bind: function (fn, obj) { // (Function, Object) -> Function\r
-               var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;\r
-               return function () {\r
-                       return fn.apply(obj, args || arguments);\r
-               };\r
-       },\r
-\r
-       stamp: (function () {\r
-               var lastId = 0, key = '_leaflet_id';\r
-               return function (/*Object*/ obj) {\r
-                       obj[key] = obj[key] || ++lastId;\r
-                       return obj[key];\r
-               };\r
-       }()),\r
-\r
-       limitExecByInterval: function (fn, time, context) {\r
-               var lock, execOnUnlock;\r
-\r
-               return function wrapperFn() {\r
-                       var args = arguments;\r
-\r
-                       if (lock) {\r
-                               execOnUnlock = true;\r
-                               return;\r
-                       }\r
-\r
-                       lock = true;\r
-\r
-                       setTimeout(function () {\r
-                               lock = false;\r
-\r
-                               if (execOnUnlock) {\r
-                                       wrapperFn.apply(context, args);\r
-                                       execOnUnlock = false;\r
-                               }\r
-                       }, time);\r
-\r
-                       fn.apply(context, args);\r
-               };\r
-       },\r
-\r
-       falseFn: function () {\r
-               return false;\r
-       },\r
-\r
-       formatNum: function (num, digits) {\r
-               var pow = Math.pow(10, digits || 5);\r
-               return Math.round(num * pow) / pow;\r
-       },\r
-\r
-       splitWords: function (str) {\r
-               return str.replace(/^\s+|\s+$/g, '').split(/\s+/);\r
-       },\r
-\r
-       setOptions: function (obj, options) {\r
-               obj.options = L.Util.extend({}, obj.options, options);\r
-               return obj.options;\r
-       },\r
-\r
-       getParamString: function (obj) {\r
-               var params = [];\r
-               for (var i in obj) {\r
-                       if (obj.hasOwnProperty(i)) {\r
-                               params.push(i + '=' + obj[i]);\r
-                       }\r
-               }\r
-               return '?' + params.join('&');\r
-       },\r
-\r
-       template: function (str, data) {\r
-               return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {\r
-                       var value = data[key];\r
-                       if (!data.hasOwnProperty(key)) {\r
-                               throw new Error('No value provided for variable ' + str);\r
-                       }\r
-                       return value;\r
-               });\r
-       },\r
-\r
-       emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='\r
-};\r
-\r
-(function () {\r
-\r
-       // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/\r
-\r
-       function getPrefixed(name) {\r
-               var i, fn,\r
-                       prefixes = ['webkit', 'moz', 'o', 'ms'];\r
-\r
-               for (i = 0; i < prefixes.length && !fn; i++) {\r
-                       fn = window[prefixes[i] + name];\r
-               }\r
-\r
-               return fn;\r
-       }\r
-\r
-       var lastTime = 0;\r
-\r
-       function timeoutDefer(fn) {\r
-               var time = +new Date(),\r
-                       timeToCall = Math.max(0, 16 - (time - lastTime));\r
-\r
-               lastTime = time + timeToCall;\r
-               return window.setTimeout(fn, timeToCall);\r
-       }\r
-\r
-       var requestFn = window.requestAnimationFrame ||\r
-                       getPrefixed('RequestAnimationFrame') || timeoutDefer;\r
-\r
-       var cancelFn = window.cancelAnimationFrame ||\r
-                       getPrefixed('CancelAnimationFrame') ||\r
-                       getPrefixed('CancelRequestAnimationFrame') ||\r
-                       function (id) {\r
-                               window.clearTimeout(id);\r
-                       };\r
-\r
-\r
-       L.Util.requestAnimFrame = function (fn, context, immediate, element) {\r
-               fn = L.Util.bind(fn, context);\r
-\r
-               if (immediate && requestFn === timeoutDefer) {\r
-                       fn();\r
-               } else {\r
-                       return requestFn.call(window, fn, element);\r
-               }\r
-       };\r
-\r
-       L.Util.cancelAnimFrame = function (id) {\r
-               if (id) {\r
-                       cancelFn.call(window, id);\r
-               }\r
-       };\r
-\r
-}());\r
-
-
-/*\r
- * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!\r
- */\r
-\r
-L.Class = function () {};\r
-\r
-L.Class.extend = function (/*Object*/ props) /*-> Class*/ {\r
-\r
-       // extended class with the new prototype\r
-       var NewClass = function () {\r
-               if (this.initialize) {\r
-                       this.initialize.apply(this, arguments);\r
-               }\r
-       };\r
-\r
-       // instantiate class without calling constructor\r
-       var F = function () {};\r
-       F.prototype = this.prototype;\r
-\r
-       var proto = new F();\r
-       proto.constructor = NewClass;\r
-\r
-       NewClass.prototype = proto;\r
-\r
-       //inherit parent's statics\r
-       for (var i in this) {\r
-               if (this.hasOwnProperty(i) && i !== 'prototype') {\r
-                       NewClass[i] = this[i];\r
-               }\r
-       }\r
-\r
-       // mix static properties into the class\r
-       if (props.statics) {\r
-               L.Util.extend(NewClass, props.statics);\r
-               delete props.statics;\r
-       }\r
-\r
-       // mix includes into the prototype\r
-       if (props.includes) {\r
-               L.Util.extend.apply(null, [proto].concat(props.includes));\r
-               delete props.includes;\r
-       }\r
-\r
-       // merge options\r
-       if (props.options && proto.options) {\r
-               props.options = L.Util.extend({}, proto.options, props.options);\r
-       }\r
-\r
-       // mix given properties into the prototype\r
-       L.Util.extend(proto, props);\r
-\r
-       return NewClass;\r
-};\r
-\r
-\r
-// method for adding properties to prototype\r
-L.Class.include = function (props) {\r
-       L.Util.extend(this.prototype, props);\r
-};\r
-\r
-L.Class.mergeOptions = function (options) {\r
-       L.Util.extend(this.prototype.options, options);\r
+var L, originalL;
+
+if (typeof exports !== undefined + '') {
+       L = exports;
+} else {
+       originalL = window.L;
+       L = {};
+
+       L.noConflict = function () {
+               window.L = originalL;
+               return this;
+       };
+
+       window.L = L;
+}
+
+L.version = '0.4.4';
+
+
+/*
+ * L.Util is a namespace for various utility functions.
+ */
+
+L.Util = {
+       extend: function (/*Object*/ dest) /*-> Object*/ {      // merge src properties into dest
+               var sources = Array.prototype.slice.call(arguments, 1);
+               for (var j = 0, len = sources.length, src; j < len; j++) {
+                       src = sources[j] || {};
+                       for (var i in src) {
+                               if (src.hasOwnProperty(i)) {
+                                       dest[i] = src[i];
+                               }
+                       }
+               }
+               return dest;
+       },
+
+       bind: function (fn, obj) { // (Function, Object) -> Function
+               var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
+               return function () {
+                       return fn.apply(obj, args || arguments);
+               };
+       },
+
+       stamp: (function () {
+               var lastId = 0, key = '_leaflet_id';
+               return function (/*Object*/ obj) {
+                       obj[key] = obj[key] || ++lastId;
+                       return obj[key];
+               };
+       }()),
+
+       limitExecByInterval: function (fn, time, context) {
+               var lock, execOnUnlock;
+
+               return function wrapperFn() {
+                       var args = arguments;
+
+                       if (lock) {
+                               execOnUnlock = true;
+                               return;
+                       }
+
+                       lock = true;
+
+                       setTimeout(function () {
+                               lock = false;
+
+                               if (execOnUnlock) {
+                                       wrapperFn.apply(context, args);
+                                       execOnUnlock = false;
+                               }
+                       }, time);
+
+                       fn.apply(context, args);
+               };
+       },
+
+       falseFn: function () {
+               return false;
+       },
+
+       formatNum: function (num, digits) {
+               var pow = Math.pow(10, digits || 5);
+               return Math.round(num * pow) / pow;
+       },
+
+       splitWords: function (str) {
+               return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
+       },
+
+       setOptions: function (obj, options) {
+               obj.options = L.Util.extend({}, obj.options, options);
+               return obj.options;
+       },
+
+       getParamString: function (obj) {
+               var params = [];
+               for (var i in obj) {
+                       if (obj.hasOwnProperty(i)) {
+                               params.push(i + '=' + obj[i]);
+                       }
+               }
+               return '?' + params.join('&');
+       },
+
+       template: function (str, data) {
+               return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
+                       var value = data[key];
+                       if (!data.hasOwnProperty(key)) {
+                               throw new Error('No value provided for variable ' + str);
+                       }
+                       return value;
+               });
+       },
+
+       emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
+};
+
+(function () {
+
+       // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+
+       function getPrefixed(name) {
+               var i, fn,
+                       prefixes = ['webkit', 'moz', 'o', 'ms'];
+
+               for (i = 0; i < prefixes.length && !fn; i++) {
+                       fn = window[prefixes[i] + name];
+               }
+
+               return fn;
+       }
+
+       var lastTime = 0;
+
+       function timeoutDefer(fn) {
+               var time = +new Date(),
+                       timeToCall = Math.max(0, 16 - (time - lastTime));
+
+               lastTime = time + timeToCall;
+               return window.setTimeout(fn, timeToCall);
+       }
+
+       var requestFn = window.requestAnimationFrame ||
+                       getPrefixed('RequestAnimationFrame') || timeoutDefer;
+
+       var cancelFn = window.cancelAnimationFrame ||
+                       getPrefixed('CancelAnimationFrame') ||
+                       getPrefixed('CancelRequestAnimationFrame') ||
+                       function (id) {
+                               window.clearTimeout(id);
+                       };
+
+
+       L.Util.requestAnimFrame = function (fn, context, immediate, element) {
+               fn = L.Util.bind(fn, context);
+
+               if (immediate && requestFn === timeoutDefer) {
+                       fn();
+               } else {
+                       return requestFn.call(window, fn, element);
+               }
+       };
+
+       L.Util.cancelAnimFrame = function (id) {
+               if (id) {
+                       cancelFn.call(window, id);
+               }
+       };
+
+}());
+
+
+/*
+ * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
+ */
+
+L.Class = function () {};
+
+L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
+
+       // extended class with the new prototype
+       var NewClass = function () {
+               if (this.initialize) {
+                       this.initialize.apply(this, arguments);
+               }
+       };
+
+       // instantiate class without calling constructor
+       var F = function () {};
+       F.prototype = this.prototype;
+
+       var proto = new F();
+       proto.constructor = NewClass;
+
+       NewClass.prototype = proto;
+
+       //inherit parent's statics
+       for (var i in this) {
+               if (this.hasOwnProperty(i) && i !== 'prototype') {
+                       NewClass[i] = this[i];
+               }
+       }
+
+       // mix static properties into the class
+       if (props.statics) {
+               L.Util.extend(NewClass, props.statics);
+               delete props.statics;
+       }
+
+       // mix includes into the prototype
+       if (props.includes) {
+               L.Util.extend.apply(null, [proto].concat(props.includes));
+               delete props.includes;
+       }
+
+       // merge options
+       if (props.options && proto.options) {
+               props.options = L.Util.extend({}, proto.options, props.options);
+       }
+
+       // mix given properties into the prototype
+       L.Util.extend(proto, props);
+
+       return NewClass;
+};
+
+
+// method for adding properties to prototype
+L.Class.include = function (props) {
+       L.Util.extend(this.prototype, props);
+};
+
+L.Class.mergeOptions = function (options) {
+       L.Util.extend(this.prototype.options, options);
+};
+
+/*
+ * L.Mixin.Events adds custom events functionality to Leaflet classes
+ */
+
+var key = '_leaflet_events';
+
+L.Mixin = {};
+
+L.Mixin.Events = {
+       
+       addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
+               var events = this[key] = this[key] || {},
+                       type, i, len;
+               
+               // Types can be a map of types/handlers
+               if (typeof types === 'object') {
+                       for (type in types) {
+                               if (types.hasOwnProperty(type)) {
+                                       this.addEventListener(type, types[type], fn);
+                               }
+                       }
+                       
+                       return this;
+               }
+               
+               types = L.Util.splitWords(types);
+               
+               for (i = 0, len = types.length; i < len; i++) {
+                       events[types[i]] = events[types[i]] || [];
+                       events[types[i]].push({
+                               action: fn,
+                               context: context || this
+                       });
+               }
+               
+               return this;
+       },
+
+       hasEventListeners: function (type) { // (String) -> Boolean
+               return (key in this) && (type in this[key]) && (this[key][type].length > 0);
+       },
+
+       removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
+               var events = this[key],
+                       type, i, len, listeners, j;
+               
+               if (typeof types === 'object') {
+                       for (type in types) {
+                               if (types.hasOwnProperty(type)) {
+                                       this.removeEventListener(type, types[type], fn);
+                               }
+                       }
+                       
+                       return this;
+               }
+               
+               types = L.Util.splitWords(types);
+
+               for (i = 0, len = types.length; i < len; i++) {
+
+                       if (this.hasEventListeners(types[i])) {
+                               listeners = events[types[i]];
+                               
+                               for (j = listeners.length - 1; j >= 0; j--) {
+                                       if (
+                                               (!fn || listeners[j].action === fn) &&
+                                               (!context || (listeners[j].context === context))
+                                       ) {
+                                               listeners.splice(j, 1);
+                                       }
+                               }
+                       }
+               }
+               
+               return this;
+       },
+
+       fireEvent: function (type, data) { // (String[, Object])
+               if (!this.hasEventListeners(type)) {
+                       return this;
+               }
+
+               var event = L.Util.extend({
+                       type: type,
+                       target: this
+               }, data);
+
+               var listeners = this[key][type].slice();
+
+               for (var i = 0, len = listeners.length; i < len; i++) {
+                       listeners[i].action.call(listeners[i].context || this, event);
+               }
+
+               return this;
+       }
+};
+
+L.Mixin.Events.on = L.Mixin.Events.addEventListener;
+L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
+L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
+
+
+(function () {
+       var ua = navigator.userAgent.toLowerCase(),
+               ie = !!window.ActiveXObject,
+               ie6 = ie && !window.XMLHttpRequest,
+               webkit = ua.indexOf("webkit") !== -1,
+               gecko = ua.indexOf("gecko") !== -1,
+               //Terrible browser detection to work around a safari / iOS / android browser bug. See TileLayer._addTile and debug/hacks/jitter.html
+               chrome = ua.indexOf("chrome") !== -1,
+               opera = window.opera,
+               android = ua.indexOf("android") !== -1,
+               android23 = ua.search("android [23]") !== -1,
+               mobile = typeof orientation !== undefined + '' ? true : false,
+               doc = document.documentElement,
+               ie3d = ie && ('transition' in doc.style),
+               webkit3d = webkit && ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
+               gecko3d = gecko && ('MozPerspective' in doc.style),
+               opera3d = opera && ('OTransition' in doc.style);
+
+       var touch = !window.L_NO_TOUCH && (function () {
+               var startName = 'ontouchstart';
+
+               // WebKit, etc
+               if (startName in doc) {
+                       return true;
+               }
+
+               // Firefox/Gecko
+               var div = document.createElement('div'),
+                       supported = false;
+
+               if (!div.setAttribute) {
+                       return false;
+               }
+               div.setAttribute(startName, 'return;');
+
+               if (typeof div[startName] === 'function') {
+                       supported = true;
+               }
+
+               div.removeAttribute(startName);
+               div = null;
+
+               return supported;
+       }());
+
+       var retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) || ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches));
+
+       L.Browser = {
+               ua: ua,
+               ie: ie,
+               ie6: ie6,
+               webkit: webkit,
+               gecko: gecko,
+               opera: opera,
+               android: android,
+               android23: android23,
+
+               chrome: chrome,
+
+               ie3d: ie3d,
+               webkit3d: webkit3d,
+               gecko3d: gecko3d,
+               opera3d: opera3d,
+               any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d),
+
+               mobile: mobile,
+               mobileWebkit: mobile && webkit,
+               mobileWebkit3d: mobile && webkit3d,
+               mobileOpera: mobile && opera,
+
+               touch: touch,
+
+               retina: retina
+       };
+}());
+
+
+/*
+ * L.Point represents a point with x and y coordinates.
+ */
+
+L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
+       this.x = (round ? Math.round(x) : x);
+       this.y = (round ? Math.round(y) : y);
+};
+
+L.Point.prototype = {
+
+       clone: function () {
+               return new L.Point(this.x, this.y);
+       },
+
+       // non-destructive, returns a new point
+       add: function (point) {
+               return this.clone()._add(L.point(point));
+       },
+
+       // destructive, used directly for performance in situations where it's safe to modify existing point
+       _add: function (point) {
+               this.x += point.x;
+               this.y += point.y;
+               return this;
+       },
+
+       subtract: function (point) {
+               return this.clone()._subtract(L.point(point));
+       },
+
+       _subtract: function (point) {
+               this.x -= point.x;
+               this.y -= point.y;
+               return this;
+       },
+
+       divideBy: function (num) {
+               return this.clone()._divideBy(num);
+       },
+
+       _divideBy: function (num) {
+               this.x /= num;
+               this.y /= num;
+               return this;
+       },
+
+       multiplyBy: function (num) {
+               return this.clone()._multiplyBy(num);
+       },
+
+       _multiplyBy: function (num) {
+               this.x *= num;
+               this.y *= num;
+               return this;
+       },
+
+       round: function () {
+               return this.clone()._round();
+       },
+
+       _round: function () {
+               this.x = Math.round(this.x);
+               this.y = Math.round(this.y);
+               return this;
+       },
+
+       floor: function () {
+               return this.clone()._floor();
+       },
+
+       _floor: function () {
+               this.x = Math.floor(this.x);
+               this.y = Math.floor(this.y);
+               return this;
+       },
+
+       distanceTo: function (point) {
+               point = L.point(point);
+
+               var x = point.x - this.x,
+                       y = point.y - this.y;
+
+               return Math.sqrt(x * x + y * y);
+       },
+
+       toString: function () {
+               return 'Point(' +
+                               L.Util.formatNum(this.x) + ', ' +
+                               L.Util.formatNum(this.y) + ')';
+       }
+};
+
+L.point = function (x, y, round) {
+       if (x instanceof L.Point) {
+               return x;
+       }
+       if (x instanceof Array) {
+               return new L.Point(x[0], x[1]);
+       }
+       if (isNaN(x)) {
+               return x;
+       }
+       return new L.Point(x, y, round);
+};
+
+
+/*
+ * L.Bounds represents a rectangular area on the screen in pixel coordinates.
+ */
+
+L.Bounds = L.Class.extend({
+
+       initialize: function (a, b) {   //(Point, Point) or Point[]
+               if (!a) { return; }
+
+               var points = b ? [a, b] : a;
+
+               for (var i = 0, len = points.length; i < len; i++) {
+                       this.extend(points[i]);
+               }
+       },
+
+       // extend the bounds to contain the given point
+       extend: function (point) { // (Point)
+               point = L.point(point);
+
+               if (!this.min && !this.max) {
+                       this.min = point.clone();
+                       this.max = point.clone();
+               } else {
+                       this.min.x = Math.min(point.x, this.min.x);
+                       this.max.x = Math.max(point.x, this.max.x);
+                       this.min.y = Math.min(point.y, this.min.y);
+                       this.max.y = Math.max(point.y, this.max.y);
+               }
+               return this;
+       },
+
+       getCenter: function (round) { // (Boolean) -> Point
+               return new L.Point(
+                               (this.min.x + this.max.x) / 2,
+                               (this.min.y + this.max.y) / 2, round);
+       },
+
+       getBottomLeft: function () { // -> Point
+               return new L.Point(this.min.x, this.max.y);
+       },
+
+       getTopRight: function () { // -> Point
+               return new L.Point(this.max.x, this.min.y);
+       },
+
+       contains: function (obj) { // (Bounds) or (Point) -> Boolean
+               var min, max;
+
+               if (typeof obj[0] === 'number' || obj instanceof L.Point) {
+                       obj = L.point(obj);
+               } else {
+                       obj = L.bounds(obj);
+               }
+
+               if (obj instanceof L.Bounds) {
+                       min = obj.min;
+                       max = obj.max;
+               } else {
+                       min = max = obj;
+               }
+
+               return (min.x >= this.min.x) &&
+                               (max.x <= this.max.x) &&
+                               (min.y >= this.min.y) &&
+                               (max.y <= this.max.y);
+       },
+
+       intersects: function (bounds) { // (Bounds) -> Boolean
+               bounds = L.bounds(bounds);
+
+               var min = this.min,
+                       max = this.max,
+                       min2 = bounds.min,
+                       max2 = bounds.max;
+
+               var xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
+                       yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
+
+               return xIntersects && yIntersects;
+       },
+
+       isValid: function () {
+               return !!(this.min && this.max);
+       }
+});
+
+L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
+       if (!a || a instanceof L.Bounds) {
+               return a;
+       }
+       return new L.Bounds(a, b);
+};
+
+
+/*
+ * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
+ */
+
+L.Transformation = L.Class.extend({
+       initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {
+               this._a = a;
+               this._b = b;
+               this._c = c;
+               this._d = d;
+       },
+
+       transform: function (point, scale) {
+               return this._transform(point.clone(), scale);
+       },
+
+       // destructive transform (faster)
+       _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+               scale = scale || 1;
+               point.x = scale * (this._a * point.x + this._b);
+               point.y = scale * (this._c * point.y + this._d);
+               return point;
+       },
+
+       untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+               scale = scale || 1;
+               return new L.Point(
+                       (point.x / scale - this._b) / this._a,
+                       (point.y / scale - this._d) / this._c);
+       }
+});
+
+
+/*
+ * L.DomUtil contains various utility functions for working with DOM.
+ */
+
+L.DomUtil = {
+       get: function (id) {
+               return (typeof id === 'string' ? document.getElementById(id) : id);
+       },
+
+       getStyle: function (el, style) {
+
+               var value = el.style[style];
+
+               if (!value && el.currentStyle) {
+                       value = el.currentStyle[style];
+               }
+
+               if (!value || value === 'auto') {
+                       var css = document.defaultView.getComputedStyle(el, null);
+                       value = css ? css[style] : null;
+               }
+
+               return value === 'auto' ? null : value;
+       },
+
+       getViewportOffset: function (element) {
+
+               var top = 0,
+                       left = 0,
+                       el = element,
+                       docBody = document.body,
+                       pos;
+
+               do {
+                       top += el.offsetTop || 0;
+                       left += el.offsetLeft || 0;
+                       pos = L.DomUtil.getStyle(el, 'position');
+
+                       if (el.offsetParent === docBody && pos === 'absolute') { break; }
+
+                       if (pos === 'fixed') {
+                               top  += docBody.scrollTop  || 0;
+                               left += docBody.scrollLeft || 0;
+                               break;
+                       }
+                       el = el.offsetParent;
+
+               } while (el);
+
+               el = element;
+
+               do {
+                       if (el === docBody) { break; }
+
+                       top  -= el.scrollTop  || 0;
+                       left -= el.scrollLeft || 0;
+
+                       el = el.parentNode;
+               } while (el);
+
+               return new L.Point(left, top);
+       },
+
+       create: function (tagName, className, container) {
+
+               var el = document.createElement(tagName);
+               el.className = className;
+
+               if (container) {
+                       container.appendChild(el);
+               }
+
+               return el;
+       },
+
+       disableTextSelection: function () {
+               if (document.selection && document.selection.empty) {
+                       document.selection.empty();
+               }
+               if (!this._onselectstart) {
+                       this._onselectstart = document.onselectstart;
+                       document.onselectstart = L.Util.falseFn;
+               }
+       },
+
+       enableTextSelection: function () {
+               if (document.onselectstart === L.Util.falseFn) {
+                       document.onselectstart = this._onselectstart;
+                       this._onselectstart = null;
+               }
+       },
+
+       hasClass: function (el, name) {
+               return (el.className.length > 0) &&
+                               new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
+       },
+
+       addClass: function (el, name) {
+               if (!L.DomUtil.hasClass(el, name)) {
+                       el.className += (el.className ? ' ' : '') + name;
+               }
+       },
+
+       removeClass: function (el, name) {
+
+               function replaceFn(w, match) {
+                       if (match === name) { return ''; }
+                       return w;
+               }
+
+               el.className = el.className
+                               .replace(/(\S+)\s*/g, replaceFn)
+                               .replace(/(^\s+|\s+$)/, '');
+       },
+
+       setOpacity: function (el, value) {
+
+               if ('opacity' in el.style) {
+                       el.style.opacity = value;
+
+               } else if (L.Browser.ie) {
+
+                       var filter = false,
+                               filterName = 'DXImageTransform.Microsoft.Alpha';
+
+                       // filters collection throws an error if we try to retrieve a filter that doesn't exist
+                       try { filter = el.filters.item(filterName); } catch (e) {}
+
+                       value = Math.round(value * 100);
+
+                       if (filter) {
+                               filter.Enabled = (value !== 100);
+                               filter.Opacity = value;
+                       } else {
+                               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+                       }
+               }
+       },
+
+       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;
+       },
+
+       getTranslateString: function (point) {
+               // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
+               // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
+               // (same speed either way), Opera 12 doesn't support translate3d
+
+               var is3d = L.Browser.webkit3d,
+                       open = 'translate' + (is3d ? '3d' : '') + '(',
+                       close = (is3d ? ',0' : '') + ')';
+
+               return open + point.x + 'px,' + point.y + 'px' + close;
+       },
+
+       getScaleString: function (scale, origin) {
+
+               var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
+                   scaleStr = ' scale(' + scale + ') ';
+
+               return preTranslateStr + scaleStr;
+       },
+
+       setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
+
+               el._leaflet_pos = point;
+
+               if (!disable3D && L.Browser.any3d) {
+                       el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);
+
+                       // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
+                       if (L.Browser.mobileWebkit3d) {
+                               el.style.WebkitBackfaceVisibility = 'hidden';
+                       }
+               } else {
+                       el.style.left = point.x + 'px';
+                       el.style.top = point.y + 'px';
+               }
+       },
+
+       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;
+       }
+};
+
+
+// prefix style property names
+
+L.DomUtil.TRANSFORM = L.DomUtil.testProp(
+               ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+
+L.DomUtil.TRANSITION = L.DomUtil.testProp(
+               ['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']);
+
+L.DomUtil.TRANSITION_END =
+               L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
+               L.DomUtil.TRANSITION + 'End' : 'transitionend';
+
+
+/*
+       CM.LatLng represents a geographical point with latitude and longtitude coordinates.
+*/
+
+L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
+       var lat = parseFloat(rawLat),
+               lng = parseFloat(rawLng);
+
+       if (isNaN(lat) || isNaN(lng)) {
+               throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
+       }
+
+       if (noWrap !== true) {
+               lat = Math.max(Math.min(lat, 90), -90);                                 // clamp latitude into -90..90
+               lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180);   // wrap longtitude into -180..180
+       }
+
+       this.lat = lat;
+       this.lng = lng;
+};
+
+L.Util.extend(L.LatLng, {
+       DEG_TO_RAD: Math.PI / 180,
+       RAD_TO_DEG: 180 / Math.PI,
+       MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
+});
+
+L.LatLng.prototype = {
+       equals: function (obj) { // (LatLng) -> Boolean
+               if (!obj) { return false; }
+
+               obj = L.latLng(obj);
+
+               var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
+               return margin <= L.LatLng.MAX_MARGIN;
+       },
+
+       toString: function (precision) { // -> String
+               return 'LatLng(' +
+                               L.Util.formatNum(this.lat, precision) + ', ' +
+                               L.Util.formatNum(this.lng, precision) + ')';
+       },
+
+       // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
+       distanceTo: function (other) { // (LatLng) -> Number
+               other = L.latLng(other);
+
+               var R = 6378137, // earth radius in meters
+                       d2r = L.LatLng.DEG_TO_RAD,
+                       dLat = (other.lat - this.lat) * d2r,
+                       dLon = (other.lng - this.lng) * d2r,
+                       lat1 = this.lat * d2r,
+                       lat2 = other.lat * d2r,
+                       sin1 = Math.sin(dLat / 2),
+                       sin2 = Math.sin(dLon / 2);
+
+               var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
+
+               return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+       }
+};
+
+L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
+       if (a instanceof L.LatLng) {
+               return a;
+       }
+       if (a instanceof Array) {
+               return new L.LatLng(a[0], a[1]);
+       }
+       if (isNaN(a)) {
+               return a;
+       }
+       return new L.LatLng(a, b, c);
+};
+
+
+
+/*
+ * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
+ */
+
+L.LatLngBounds = L.Class.extend({
+       initialize: function (southWest, northEast) {   // (LatLng, LatLng) or (LatLng[])
+               if (!southWest) { return; }
+
+               var latlngs = northEast ? [southWest, northEast] : southWest;
+
+               for (var i = 0, len = latlngs.length; i < len; i++) {
+                       this.extend(latlngs[i]);
+               }
+       },
+
+       // extend the bounds to contain the given point or bounds
+       extend: function (obj) { // (LatLng) or (LatLngBounds)
+               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+                       obj = L.latLng(obj);
+               } else {
+                       obj = L.latLngBounds(obj);
+               }
+
+               if (obj instanceof L.LatLng) {
+                       if (!this._southWest && !this._northEast) {
+                               this._southWest = new L.LatLng(obj.lat, obj.lng, true);
+                               this._northEast = new L.LatLng(obj.lat, obj.lng, true);
+                       } else {
+                               this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
+                               this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
+
+                               this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
+                               this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
+                       }
+               } else if (obj instanceof L.LatLngBounds) {
+                       this.extend(obj._southWest);
+            this.extend(obj._northEast);
+               }
+               return this;
+       },
+
+       // extend the bounds by a percentage
+       pad: function (bufferRatio) { // (Number) -> LatLngBounds
+               var sw = this._southWest,
+                       ne = this._northEast,
+                       heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
+                       widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
+
+               return new L.LatLngBounds(
+                       new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
+                       new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
+       },
+
+       getCenter: function () { // -> LatLng
+               return new L.LatLng(
+                               (this._southWest.lat + this._northEast.lat) / 2,
+                               (this._southWest.lng + this._northEast.lng) / 2);
+       },
+
+       getSouthWest: function () {
+               return this._southWest;
+       },
+
+       getNorthEast: function () {
+               return this._northEast;
+       },
+
+       getNorthWest: function () {
+               return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
+       },
+
+       getSouthEast: function () {
+               return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
+       },
+
+       contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
+               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+                       obj = L.latLng(obj);
+               } else {
+                       obj = L.latLngBounds(obj);
+               }
+
+               var sw = this._southWest,
+                       ne = this._northEast,
+                       sw2, ne2;
+
+               if (obj instanceof L.LatLngBounds) {
+                       sw2 = obj.getSouthWest();
+                       ne2 = obj.getNorthEast();
+               } else {
+                       sw2 = ne2 = obj;
+               }
+
+               return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
+                               (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
+       },
+
+       intersects: function (bounds) { // (LatLngBounds)
+               bounds = L.latLngBounds(bounds);
+
+               var sw = this._southWest,
+                       ne = this._northEast,
+                       sw2 = bounds.getSouthWest(),
+                       ne2 = bounds.getNorthEast();
+
+               var latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
+                       lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
+
+               return latIntersects && lngIntersects;
+       },
+
+       toBBoxString: function () {
+               var sw = this._southWest,
+                       ne = this._northEast;
+               return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
+       },
+
+       equals: function (bounds) { // (LatLngBounds)
+               if (!bounds) { return false; }
+
+               bounds = L.latLngBounds(bounds);
+
+               return this._southWest.equals(bounds.getSouthWest()) &&
+                      this._northEast.equals(bounds.getNorthEast());
+       },
+
+       isValid: function () {
+               return !!(this._southWest && this._northEast);
+       }
+});
+
+//TODO International date line?
+
+L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
+       if (!a || a instanceof L.LatLngBounds) {
+               return a;
+       }
+       return new L.LatLngBounds(a, b);
+};
+
+
+/*
+ * L.Projection contains various geographical projections used by CRS classes.
+ */
+
+L.Projection = {};
+
+
+
+L.Projection.SphericalMercator = {
+       MAX_LATITUDE: 85.0511287798,
+
+       project: function (latlng) { // (LatLng) -> Point
+               var d = L.LatLng.DEG_TO_RAD,
+                       max = this.MAX_LATITUDE,
+                       lat = Math.max(Math.min(max, latlng.lat), -max),
+                       x = latlng.lng * d,
+                       y = lat * d;
+               y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
+
+               return new L.Point(x, y);
+       },
+
+       unproject: function (point) { // (Point, Boolean) -> LatLng
+               var d = L.LatLng.RAD_TO_DEG,
+                       lng = point.x * d,
+                       lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
+
+               // TODO refactor LatLng wrapping
+               return new L.LatLng(lat, lng, true);
+       }
+};
+
+
+
+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, true);
+       }
+};
+
+
+
+L.CRS = {
+       latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
+               var projectedPoint = this.projection.project(latlng),
+                   scale = this.scale(zoom);
+
+               return this.transformation._transform(projectedPoint, scale);
+       },
+
+       pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
+               var scale = this.scale(zoom),
+                   untransformedPoint = this.transformation.untransform(point, scale);
+
+               return this.projection.unproject(untransformedPoint);
+       },
+
+       project: function (latlng) {
+               return this.projection.project(latlng);
+       },
+
+       scale: function (zoom) {
+               return 256 * Math.pow(2, zoom);
+       }
+};
+
+
+
+L.CRS.EPSG3857 = L.Util.extend({}, L.CRS, {
+       code: 'EPSG:3857',
+
+       projection: L.Projection.SphericalMercator,
+       transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
+
+       project: function (latlng) { // (LatLng) -> Point
+               var projectedPoint = this.projection.project(latlng),
+                       earthRadius = 6378137;
+               return projectedPoint.multiplyBy(earthRadius);
+       }
+});
+
+L.CRS.EPSG900913 = L.Util.extend({}, L.CRS.EPSG3857, {
+       code: 'EPSG:900913'
+});
+
+
+
+L.CRS.EPSG4326 = L.Util.extend({}, L.CRS, {
+       code: 'EPSG:4326',
+
+       projection: L.Projection.LonLat,
+       transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
+});
+
+
+/*
+ * L.Map is the central class of the API - it is used to create a map.
+ */
+
+L.Map = L.Class.extend({
+
+       includes: L.Mixin.Events,
+
+       options: {
+               crs: L.CRS.EPSG3857,
+
+               /*
+               center: LatLng,
+               zoom: Number,
+               layers: Array,
+               */
+
+               fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
+               trackResize: true,
+               markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
+       },
+
+       initialize: function (id, options) { // (HTMLElement or String, Object)
+               options = L.Util.setOptions(this, options);
+
+               this._initContainer(id);
+               this._initLayout();
+               this._initHooks();
+               this._initEvents();
+
+               if (options.maxBounds) {
+                       this.setMaxBounds(options.maxBounds);
+               }
+
+               if (options.center && options.zoom !== undefined) {
+                       this.setView(L.latLng(options.center), options.zoom, true);
+               }
+
+               this._initLayers(options.layers);
+       },
+
+
+       // public methods that modify map state
+
+       // replaced by animation-powered implementation in Map.PanAnimation.js
+       setView: function (center, zoom) {
+               this._resetView(L.latLng(center), this._limitZoom(zoom));
+               return this;
+       },
+
+       setZoom: function (zoom) { // (Number)
+               return this.setView(this.getCenter(), zoom);
+       },
+
+       zoomIn: function (delta) {
+               return this.setZoom(this._zoom + (delta || 1));
+       },
+
+       zoomOut: function (delta) {
+               return this.setZoom(this._zoom - (delta || 1));
+       },
+
+       fitBounds: function (bounds) { // (LatLngBounds)
+               var zoom = this.getBoundsZoom(bounds);
+               return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
+       },
+
+       fitWorld: function () {
+               var sw = new L.LatLng(-60, -170),
+                   ne = new L.LatLng(85, 179);
+
+               return this.fitBounds(new L.LatLngBounds(sw, ne));
+       },
+
+       panTo: function (center) { // (LatLng)
+               return this.setView(center, this._zoom);
+       },
+
+       panBy: function (offset) { // (Point)
+               // replaced with animated panBy in Map.Animation.js
+               this.fire('movestart');
+
+               this._rawPanBy(L.point(offset));
+
+               this.fire('move');
+               return this.fire('moveend');
+       },
+
+       setMaxBounds: function (bounds) {
+               bounds = L.latLngBounds(bounds);
+
+               this.options.maxBounds = bounds;
+
+               if (!bounds) {
+                       this._boundsMinZoom = null;
+                       return this;
+               }
+
+               var minZoom = this.getBoundsZoom(bounds, true);
+
+               this._boundsMinZoom = minZoom;
+
+               if (this._loaded) {
+                       if (this._zoom < minZoom) {
+                               this.setView(bounds.getCenter(), minZoom);
+                       } else {
+                               this.panInsideBounds(bounds);
+                       }
+               }
+
+               return this;
+       },
+
+       panInsideBounds: function (bounds) {
+               bounds = L.latLngBounds(bounds);
+
+               var viewBounds = this.getBounds(),
+                   viewSw = this.project(viewBounds.getSouthWest()),
+                   viewNe = this.project(viewBounds.getNorthEast()),
+                   sw = this.project(bounds.getSouthWest()),
+                   ne = this.project(bounds.getNorthEast()),
+                   dx = 0,
+                   dy = 0;
+
+               if (viewNe.y < ne.y) { // north
+                       dy = ne.y - viewNe.y;
+               }
+               if (viewNe.x > ne.x) { // east
+                       dx = ne.x - viewNe.x;
+               }
+               if (viewSw.y > sw.y) { // south
+                       dy = sw.y - viewSw.y;
+               }
+               if (viewSw.x < sw.x) { // west
+                       dx = sw.x - viewSw.x;
+               }
+
+               return this.panBy(new L.Point(dx, dy, true));
+       },
+
+       addLayer: function (layer) {
+               // TODO method is too big, refactor
+
+               var id = L.Util.stamp(layer);
+
+               if (this._layers[id]) { return this; }
+
+               this._layers[id] = layer;
+
+               // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
+               if (layer.options && !isNaN(layer.options.maxZoom)) {
+                       this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
+               }
+               if (layer.options && !isNaN(layer.options.minZoom)) {
+                       this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
+               }
+
+               // TODO looks ugly, refactor!!!
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
+                       this._tileLayersNum++;
+            this._tileLayersToLoad++;
+            layer.on('load', this._onTileLayerLoad, this);
+               }
+
+               this.whenReady(function () {
+                       layer.onAdd(this);
+                       this.fire('layeradd', {layer: layer});
+               }, this);
+
+               return this;
+       },
+
+       removeLayer: function (layer) {
+               var id = L.Util.stamp(layer);
+
+               if (!this._layers[id]) { return; }
+
+               layer.onRemove(this);
+
+               delete this._layers[id];
+
+               // TODO looks ugly, refactor
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
+                       this._tileLayersNum--;
+            this._tileLayersToLoad--;
+            layer.off('load', this._onTileLayerLoad, this);
+               }
+
+               return this.fire('layerremove', {layer: layer});
+       },
+
+       hasLayer: function (layer) {
+               var id = L.Util.stamp(layer);
+               return this._layers.hasOwnProperty(id);
+       },
+
+       invalidateSize: function (animate) {
+               var oldSize = this.getSize();
+
+               this._sizeChanged = true;
+
+               if (this.options.maxBounds) {
+                       this.setMaxBounds(this.options.maxBounds);
+               }
+
+               if (!this._loaded) { return this; }
+
+               var offset = oldSize._subtract(this.getSize())._divideBy(2)._round();
+
+               if (animate === true) {
+                       this.panBy(offset);
+               } else {
+                       this._rawPanBy(offset);
+
+                       this.fire('move');
+
+                       clearTimeout(this._sizeTimer);
+                       this._sizeTimer = setTimeout(L.Util.bind(this.fire, this, 'moveend'), 200);
+               }
+               return this;
+       },
+
+       // TODO handler.addTo
+       addHandler: function (name, HandlerClass) {
+               if (!HandlerClass) { return; }
+
+               this[name] = new HandlerClass(this);
+
+               if (this.options[name]) {
+                       this[name].enable();
+               }
+
+               return this;
+       },
+
+
+       // public methods for getting map state
+
+       getCenter: function () { // (Boolean) -> LatLng
+               return this.layerPointToLatLng(this._getCenterLayerPoint());
+       },
+
+       getZoom: function () {
+               return this._zoom;
+       },
+
+       getBounds: function () {
+               var bounds = this.getPixelBounds(),
+                   sw = this.unproject(bounds.getBottomLeft()),
+                   ne = this.unproject(bounds.getTopRight());
+
+               return new L.LatLngBounds(sw, ne);
+       },
+
+       getMinZoom: function () {
+               var z1 = this.options.minZoom || 0,
+                   z2 = this._layersMinZoom || 0,
+                   z3 = this._boundsMinZoom || 0;
+
+               return Math.max(z1, z2, z3);
+       },
+
+       getMaxZoom: function () {
+               var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
+                   z2 = this._layersMaxZoom  === undefined ? Infinity : this._layersMaxZoom;
+
+               return Math.min(z1, z2);
+       },
+
+       getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
+               bounds = L.latLngBounds(bounds);
+
+               var size = this.getSize(),
+                   zoom = this.options.minZoom || 0,
+                   maxZoom = this.getMaxZoom(),
+                   ne = bounds.getNorthEast(),
+                   sw = bounds.getSouthWest(),
+                   boundsSize,
+                   nePoint,
+                   swPoint,
+                   zoomNotFound = true;
+
+               if (inside) {
+                       zoom--;
+               }
+
+               do {
+                       zoom++;
+                       nePoint = this.project(ne, zoom);
+                       swPoint = this.project(sw, zoom);
+                       boundsSize = new L.Point(Math.abs(nePoint.x - swPoint.x), Math.abs(swPoint.y - nePoint.y));
+
+                       if (!inside) {
+                               zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
+                       } else {
+                               zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
+                       }
+               } while (zoomNotFound && zoom <= maxZoom);
+
+               if (zoomNotFound && inside) {
+                       return null;
+               }
+
+               return inside ? zoom : zoom - 1;
+       },
+
+       getSize: function () {
+               if (!this._size || this._sizeChanged) {
+                       this._size = new L.Point(
+                               this._container.clientWidth,
+                               this._container.clientHeight);
+
+                       this._sizeChanged = false;
+               }
+               return this._size.clone();
+       },
+
+       getPixelBounds: function () {
+               var topLeftPoint = this._getTopLeftPoint();
+               return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
+       },
+
+       getPixelOrigin: function () {
+               return this._initialTopLeftPoint;
+       },
+
+       getPanes: function () {
+               return this._panes;
+       },
+
+       getContainer: function () {
+               return this._container;
+       },
+
+
+       // TODO replace with universal implementation after refactoring projections
+
+       getZoomScale: function (toZoom) {
+               var crs = this.options.crs;
+               return crs.scale(toZoom) / crs.scale(this._zoom);
+       },
+
+       getScaleZoom: function (scale) {
+               return this._zoom + (Math.log(scale) / Math.LN2);
+       },
+
+
+       // conversion methods
+
+       project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
+               zoom = zoom === undefined ? this._zoom : zoom;
+               return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
+       },
+
+       unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
+               zoom = zoom === undefined ? this._zoom : zoom;
+               return this.options.crs.pointToLatLng(L.point(point), zoom);
+       },
+
+       layerPointToLatLng: function (point) { // (Point)
+               var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
+               return this.unproject(projectedPoint);
+       },
+
+       latLngToLayerPoint: function (latlng) { // (LatLng)
+               var projectedPoint = this.project(L.latLng(latlng))._round();
+               return projectedPoint._subtract(this._initialTopLeftPoint);
+       },
+
+       containerPointToLayerPoint: function (point) { // (Point)
+               return L.point(point).subtract(this._getMapPanePos());
+       },
+
+       layerPointToContainerPoint: function (point) { // (Point)
+               return L.point(point).add(this._getMapPanePos());
+       },
+
+       containerPointToLatLng: function (point) {
+               var layerPoint = this.containerPointToLayerPoint(L.point(point));
+               return this.layerPointToLatLng(layerPoint);
+       },
+
+       latLngToContainerPoint: function (latlng) {
+               return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
+       },
+
+       mouseEventToContainerPoint: function (e) { // (MouseEvent)
+               return L.DomEvent.getMousePosition(e, this._container);
+       },
+
+       mouseEventToLayerPoint: function (e) { // (MouseEvent)
+               return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
+       },
+
+       mouseEventToLatLng: function (e) { // (MouseEvent)
+               return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
+       },
+
+
+       // map initialization methods
+
+       _initContainer: function (id) {
+               var container = this._container = L.DomUtil.get(id);
+
+               if (container._leaflet) {
+                       throw new Error("Map container is already initialized.");
+               }
+
+               container._leaflet = true;
+       },
+
+       _initLayout: function () {
+               var container = this._container;
+
+               container.innerHTML = '';
+               L.DomUtil.addClass(container, 'leaflet-container');
+
+               if (L.Browser.touch) {
+                       L.DomUtil.addClass(container, 'leaflet-touch');
+               }
+
+               if (this.options.fadeAnimation) {
+                       L.DomUtil.addClass(container, 'leaflet-fade-anim');
+               }
+
+               var position = L.DomUtil.getStyle(container, 'position');
+
+               if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
+                       container.style.position = 'relative';
+               }
+
+               this._initPanes();
+
+               if (this._initControlPos) {
+                       this._initControlPos();
+               }
+       },
+
+       _initPanes: function () {
+               var panes = this._panes = {};
+
+               this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
+
+               this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
+               this._objectsPane = panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
+
+               panes.shadowPane = this._createPane('leaflet-shadow-pane');
+               panes.overlayPane = this._createPane('leaflet-overlay-pane');
+               panes.markerPane = this._createPane('leaflet-marker-pane');
+               panes.popupPane = this._createPane('leaflet-popup-pane');
+
+               var zoomHide = ' leaflet-zoom-hide';
+
+               if (!this.options.markerZoomAnimation) {
+                       L.DomUtil.addClass(panes.markerPane, zoomHide);
+                       L.DomUtil.addClass(panes.shadowPane, zoomHide);
+                       L.DomUtil.addClass(panes.popupPane, zoomHide);
+               }
+       },
+
+       _createPane: function (className, container) {
+               return L.DomUtil.create('div', className, container || this._objectsPane);
+       },
+
+       _initializers: [],
+
+       _initHooks: function () {
+               var i, len;
+               for (i = 0, len = this._initializers.length; i < len; i++) {
+                       this._initializers[i].call(this);
+               }
+       },
+
+       _initLayers: function (layers) {
+               layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
+
+               this._layers = {};
+               this._tileLayersNum = 0;
+
+               var i, len;
+
+               for (i = 0, len = layers.length; i < len; i++) {
+                       this.addLayer(layers[i]);
+               }
+       },
+
+
+       // private methods that modify map state
+
+       _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
+
+               var zoomChanged = (this._zoom !== zoom);
+
+               if (!afterZoomAnim) {
+                       this.fire('movestart');
+
+                       if (zoomChanged) {
+                               this.fire('zoomstart');
+                       }
+               }
+
+               this._zoom = zoom;
+
+               this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
+
+               if (!preserveMapOffset) {
+                       L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
+               } else {
+                       this._initialTopLeftPoint._add(this._getMapPanePos());
+               }
+
+               this._tileLayersToLoad = this._tileLayersNum;
+
+               var loading = !this._loaded;
+               this._loaded = true;
+
+               this.fire('viewreset', {hard: !preserveMapOffset});
+
+               this.fire('move');
+
+               if (zoomChanged || afterZoomAnim) {
+                       this.fire('zoomend');
+               }
+
+               this.fire('moveend', {hard: !preserveMapOffset});
+
+               if (loading) {
+                       this.fire('load');
+               }
+       },
+
+       _rawPanBy: function (offset) {
+               L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
+       },
+
+
+       // map events
+
+       _initEvents: function () {
+               if (!L.DomEvent) { return; }
+
+               L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
+
+               var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'],
+                       i, len;
+
+               for (i = 0, len = events.length; i < len; i++) {
+                       L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
+               }
+
+               if (this.options.trackResize) {
+                       L.DomEvent.on(window, 'resize', this._onResize, this);
+               }
+       },
+
+       _onResize: function () {
+               L.Util.cancelAnimFrame(this._resizeRequest);
+               this._resizeRequest = L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);
+       },
+
+       _onMouseClick: function (e) {
+               if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
+
+               this.fire('preclick');
+               this._fireMouseEvent(e);
+       },
+
+       _fireMouseEvent: function (e) {
+               if (!this._loaded) { return; }
+
+               var type = e.type;
+
+               type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
+
+               if (!this.hasEventListeners(type)) { return; }
+
+               if (type === 'contextmenu') {
+                       L.DomEvent.preventDefault(e);
+               }
+
+               var containerPoint = this.mouseEventToContainerPoint(e),
+                       layerPoint = this.containerPointToLayerPoint(containerPoint),
+                       latlng = this.layerPointToLatLng(layerPoint);
+
+               this.fire(type, {
+                       latlng: latlng,
+                       layerPoint: layerPoint,
+                       containerPoint: containerPoint,
+                       originalEvent: e
+               });
+       },
+
+       _onTileLayerLoad: function () {
+               // TODO super-ugly, refactor!!!
+               // clear scaled tiles after all new tiles are loaded (for performance)
+               this._tileLayersToLoad--;
+               if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
+                       clearTimeout(this._clearTileBgTimer);
+                       this._clearTileBgTimer = setTimeout(L.Util.bind(this._clearTileBg, this), 500);
+               }
+       },
+
+       whenReady: function (callback, context) {
+               if (this._loaded) {
+                       callback.call(context || this, this);
+               } else {
+                       this.on('load', callback, context);
+               }
+               return this;
+       },
+
+
+       // private methods for getting map state
+
+       _getMapPanePos: function () {
+               return L.DomUtil.getPosition(this._mapPane);
+       },
+
+       _getTopLeftPoint: function () {
+               if (!this._loaded) {
+                       throw new Error('Set map center and zoom first.');
+               }
+
+               return this._initialTopLeftPoint.subtract(this._getMapPanePos());
+       },
+
+       _getNewTopLeftPoint: function (center, zoom) {
+               var viewHalf = this.getSize()._divideBy(2);
+               // TODO round on display, not calculation to increase precision?
+               return this.project(center, zoom)._subtract(viewHalf)._round();
+       },
+
+       _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
+               var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
+               return this.project(latlng, newZoom)._subtract(topLeft);
+       },
+
+       _getCenterLayerPoint: function () {
+               return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
+       },
+
+       _getCenterOffset: function (center) {
+               return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
+       },
+
+       _limitZoom: function (zoom) {
+               var min = this.getMinZoom(),
+                       max = this.getMaxZoom();
+
+               return Math.max(min, Math.min(max, zoom));
+       }
+});
+
+L.Map.addInitHook = function (fn) {
+       var args = Array.prototype.slice.call(arguments, 1);
+
+       var init = typeof fn === 'function' ? fn : function () {
+               this[fn].apply(this, args);
+       };
+
+       this.prototype._initializers.push(init);
+};
+
+L.map = function (id, options) {
+       return new L.Map(id, options);
+};
+
+
+
+L.Projection.Mercator = {
+       MAX_LATITUDE: 85.0840591556,
+
+       R_MINOR: 6356752.3142,
+       R_MAJOR: 6378137,
+
+       project: function (latlng) { // (LatLng) -> Point
+               var d = L.LatLng.DEG_TO_RAD,
+                       max = this.MAX_LATITUDE,
+                       lat = Math.max(Math.min(max, latlng.lat), -max),
+                       r = this.R_MAJOR,
+                       r2 = this.R_MINOR,
+                       x = latlng.lng * d * r,
+                       y = lat * d,
+                       tmp = r2 / r,
+                       eccent = Math.sqrt(1.0 - tmp * tmp),
+                       con = eccent * Math.sin(y);
+
+               con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
+
+               var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
+               y = -r2 * Math.log(ts);
+
+               return new L.Point(x, y);
+       },
+
+       unproject: function (point) { // (Point, Boolean) -> LatLng
+               var d = L.LatLng.RAD_TO_DEG,
+                       r = this.R_MAJOR,
+                       r2 = this.R_MINOR,
+                       lng = point.x * d / r,
+                       tmp = r2 / r,
+                       eccent = Math.sqrt(1 - (tmp * tmp)),
+                       ts = Math.exp(- point.y / r2),
+                       phi = (Math.PI / 2) - 2 * Math.atan(ts),
+                       numIter = 15,
+                       tol = 1e-7,
+                       i = numIter,
+                       dphi = 0.1,
+                       con;
+
+               while ((Math.abs(dphi) > tol) && (--i > 0)) {
+                       con = eccent * Math.sin(phi);
+                       dphi = (Math.PI / 2) - 2 * Math.atan(ts * Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
+                       phi += dphi;
+               }
+
+               return new L.LatLng(phi * d, lng, true);
+       }
+};
+
+
+
+L.CRS.EPSG3395 = L.Util.extend({}, L.CRS, {
+       code: 'EPSG:3395',
+
+       projection: L.Projection.Mercator,
+
+       transformation: (function () {
+               var m = L.Projection.Mercator,
+                       r = m.R_MAJOR,
+                       r2 = m.R_MINOR;
+
+               return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
+       }())
+});
+
+
+/*
+ * L.TileLayer is used for standard xyz-numbered tile layers.
+ */
+
+L.TileLayer = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               minZoom: 0,
+               maxZoom: 18,
+               tileSize: 256,
+               subdomains: 'abc',
+               errorTileUrl: '',
+               attribution: '',
+               zoomOffset: 0,
+               opacity: 1,
+               /* (undefined works too)
+               zIndex: null,
+               tms: false,
+               continuousWorld: false,
+               noWrap: false,
+               zoomReverse: false,
+               detectRetina: false,
+               reuseTiles: false,
+               */
+               unloadInvisibleTiles: L.Browser.mobile,
+               updateWhenIdle: L.Browser.mobile
+       },
+
+       initialize: function (url, options) {
+               options = L.Util.setOptions(this, options);
+
+               // detecting retina displays, adjusting tileSize and zoom levels
+               if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
+
+                       options.tileSize = Math.floor(options.tileSize / 2);
+                       options.zoomOffset++;
+
+                       if (options.minZoom > 0) {
+                               options.minZoom--;
+                       }
+                       this.options.maxZoom--;
+               }
+
+               this._url = url;
+
+               var subdomains = this.options.subdomains;
+
+               if (typeof subdomains === 'string') {
+                       this.options.subdomains = subdomains.split('');
+               }
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               // create a container div for tiles
+               this._initContainer();
+
+               // create an image to clone for tiles
+               this._createTileProto();
+
+               // set up events
+               map.on({
+                       'viewreset': this._resetCallback,
+                       'moveend': this._update
+               }, this);
+
+               if (!this.options.updateWhenIdle) {
+                       this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
+                       map.on('move', this._limitedUpdate, this);
+               }
+
+               this._reset();
+               this._update();
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               map._panes.tilePane.removeChild(this._container);
+
+               map.off({
+                       'viewreset': this._resetCallback,
+                       'moveend': this._update
+               }, this);
+
+               if (!this.options.updateWhenIdle) {
+                       map.off('move', this._limitedUpdate, this);
+               }
+
+               this._container = null;
+               this._map = null;
+       },
+
+       bringToFront: function () {
+               var pane = this._map._panes.tilePane;
+
+               if (this._container) {
+                       pane.appendChild(this._container);
+                       this._setAutoZIndex(pane, Math.max);
+               }
+
+               return this;
+       },
+
+       bringToBack: function () {
+               var pane = this._map._panes.tilePane;
+
+               if (this._container) {
+                       pane.insertBefore(this._container, pane.firstChild);
+                       this._setAutoZIndex(pane, Math.min);
+               }
+
+               return this;
+       },
+
+       getAttribution: function () {
+               return this.options.attribution;
+       },
+
+       setOpacity: function (opacity) {
+               this.options.opacity = opacity;
+
+               if (this._map) {
+                       this._updateOpacity();
+               }
+
+               return this;
+       },
+
+       setZIndex: function (zIndex) {
+               this.options.zIndex = zIndex;
+               this._updateZIndex();
+
+               return this;
+       },
+
+       setUrl: function (url, noRedraw) {
+               this._url = url;
+
+               if (!noRedraw) {
+                       this.redraw();
+               }
+
+               return this;
+       },
+
+       redraw: function () {
+               if (this._map) {
+                       this._map._panes.tilePane.empty = false;
+                       this._reset(true);
+                       this._update();
+               }
+               return this;
+       },
+
+       _updateZIndex: function () {
+               if (this._container && this.options.zIndex !== undefined) {
+                       this._container.style.zIndex = this.options.zIndex;
+               }
+       },
+
+       _setAutoZIndex: function (pane, compare) {
+
+               var layers = pane.getElementsByClassName('leaflet-layer'),
+                       edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
+                       zIndex;
+
+               for (var i = 0, len = layers.length; i < len; i++) {
+
+                       if (layers[i] !== this._container) {
+                               zIndex = parseInt(layers[i].style.zIndex, 10);
+
+                               if (!isNaN(zIndex)) {
+                                       edgeZIndex = compare(edgeZIndex, zIndex);
+                               }
+                       }
+               }
+
+               this.options.zIndex = this._container.style.zIndex = (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
+       },
+
+       _updateOpacity: function () {
+               L.DomUtil.setOpacity(this._container, this.options.opacity);
+
+               // stupid webkit hack to force redrawing of tiles
+               var i,
+                       tiles = this._tiles;
+
+               if (L.Browser.webkit) {
+                       for (i in tiles) {
+                               if (tiles.hasOwnProperty(i)) {
+                                       tiles[i].style.webkitTransform += ' translate(0,0)';
+                               }
+                       }
+               }
+       },
+
+       _initContainer: function () {
+               var tilePane = this._map._panes.tilePane;
+
+               if (!this._container || tilePane.empty) {
+                       this._container = L.DomUtil.create('div', 'leaflet-layer');
+
+                       this._updateZIndex();
+
+                       tilePane.appendChild(this._container);
+
+                       if (this.options.opacity < 1) {
+                               this._updateOpacity();
+                       }
+               }
+       },
+
+       _resetCallback: function (e) {
+               this._reset(e.hard);
+       },
+
+       _reset: function (clearOldContainer) {
+               var key,
+                       tiles = this._tiles;
+
+               for (key in tiles) {
+                       if (tiles.hasOwnProperty(key)) {
+                               this.fire('tileunload', {tile: tiles[key]});
+                       }
+               }
+
+               this._tiles = {};
+               this._tilesToLoad = 0;
+
+               if (this.options.reuseTiles) {
+                       this._unusedTiles = [];
+               }
+
+               if (clearOldContainer && this._container) {
+                       this._container.innerHTML = "";
+               }
+
+               this._initContainer();
+       },
+
+       _update: function (e) {
+
+               if (!this._map) { return; }
+
+               var bounds   = this._map.getPixelBounds(),
+                   zoom     = this._map.getZoom(),
+                   tileSize = this.options.tileSize;
+
+               if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
+                       return;
+               }
+
+               var nwTilePoint = new L.Point(
+                               Math.floor(bounds.min.x / tileSize),
+                               Math.floor(bounds.min.y / tileSize)),
+                       seTilePoint = new L.Point(
+                               Math.floor(bounds.max.x / tileSize),
+                               Math.floor(bounds.max.y / tileSize)),
+                       tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
+
+               this._addTilesFromCenterOut(tileBounds);
+
+               if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
+                       this._removeOtherTiles(tileBounds);
+               }
+       },
+
+       _addTilesFromCenterOut: function (bounds) {
+               var queue = [],
+                       center = bounds.getCenter();
+
+               var j, i, point;
+
+               for (j = bounds.min.y; j <= bounds.max.y; j++) {
+                       for (i = bounds.min.x; i <= bounds.max.x; i++) {
+                               point = new L.Point(i, j);
+
+                               if (this._tileShouldBeLoaded(point)) {
+                                       queue.push(point);
+                               }
+                       }
+               }
+
+               var tilesToLoad = queue.length;
+
+               if (tilesToLoad === 0) { return; }
+
+               // load tiles in order of their distance to center
+               queue.sort(function (a, b) {
+                       return a.distanceTo(center) - b.distanceTo(center);
+               });
+
+               var fragment = document.createDocumentFragment();
+
+               // if its the first batch of tiles to load
+               if (!this._tilesToLoad) {
+                       this.fire('loading');
+               }
+
+               this._tilesToLoad += tilesToLoad;
+
+               for (i = 0; i < tilesToLoad; i++) {
+                       this._addTile(queue[i], fragment);
+               }
+
+               this._container.appendChild(fragment);
+       },
+
+       _tileShouldBeLoaded: function (tilePoint) {
+               if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
+                       return false; // already loaded
+               }
+
+               if (!this.options.continuousWorld) {
+                       var limit = this._getWrapTileNum();
+
+                       if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
+                                                       tilePoint.y < 0 || tilePoint.y >= limit) {
+                               return false; // exceeds world bounds
+                       }
+               }
+
+               return true;
+       },
+
+       _removeOtherTiles: function (bounds) {
+               var kArr, x, y, key;
+
+               for (key in this._tiles) {
+                       if (this._tiles.hasOwnProperty(key)) {
+                               kArr = key.split(':');
+                               x = parseInt(kArr[0], 10);
+                               y = parseInt(kArr[1], 10);
+
+                               // remove tile if it's out of bounds
+                               if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
+                                       this._removeTile(key);
+                               }
+                       }
+               }
+       },
+
+       _removeTile: function (key) {
+               var tile = this._tiles[key];
+
+               this.fire("tileunload", {tile: tile, url: tile.src});
+
+               if (this.options.reuseTiles) {
+                       L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
+                       this._unusedTiles.push(tile);
+               } else if (tile.parentNode === this._container) {
+                       this._container.removeChild(tile);
+               }
+
+               if (!L.Browser.android) { //For https://github.com/CloudMade/Leaflet/issues/137
+                       tile.src = L.Util.emptyImageUrl;
+               }
+
+               delete this._tiles[key];
+       },
+
+       _addTile: function (tilePoint, container) {
+               var tilePos = this._getTilePos(tilePoint);
+
+               // get unused tile - or create a new tile
+               var tile = this._getTile();
+
+               // Chrome 20 layouts much faster with top/left (Verify with timeline, frames)
+               // android 4 browser has display issues with top/left and requires transform instead
+               // android 3 browser not tested
+               // android 2 browser requires top/left or tiles disappear on load or first drag (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
+               // (other browsers don't currently care) - see debug/hacks/jitter.html for an example
+               L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
+
+               this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
+
+               this._loadTile(tile, tilePoint);
+
+               if (tile.parentNode !== this._container) {
+                       container.appendChild(tile);
+               }
+       },
+
+       _getZoomForUrl: function () {
+
+               var options = this.options,
+                       zoom = this._map.getZoom();
+
+               if (options.zoomReverse) {
+                       zoom = options.maxZoom - zoom;
+               }
+
+               return zoom + options.zoomOffset;
+       },
+
+       _getTilePos: function (tilePoint) {
+               var origin = this._map.getPixelOrigin(),
+                       tileSize = this.options.tileSize;
+
+               return tilePoint.multiplyBy(tileSize).subtract(origin);
+       },
+
+       // image-specific code (override to implement e.g. Canvas or SVG tile layer)
+
+       getTileUrl: function (tilePoint) {
+               this._adjustTilePoint(tilePoint);
+
+               return L.Util.template(this._url, L.Util.extend({
+                       s: this._getSubdomain(tilePoint),
+                       z: this._getZoomForUrl(),
+                       x: tilePoint.x,
+                       y: tilePoint.y
+               }, this.options));
+       },
+
+       _getWrapTileNum: function () {
+               // TODO refactor, limit is not valid for non-standard projections
+               return Math.pow(2, this._getZoomForUrl());
+       },
+
+       _adjustTilePoint: function (tilePoint) {
+
+               var limit = this._getWrapTileNum();
+
+               // wrap tile coordinates
+               if (!this.options.continuousWorld && !this.options.noWrap) {
+                       tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
+               }
+
+               if (this.options.tms) {
+                       tilePoint.y = limit - tilePoint.y - 1;
+               }
+       },
+
+       _getSubdomain: function (tilePoint) {
+               var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
+               return this.options.subdomains[index];
+       },
+
+       _createTileProto: function () {
+               var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
+               img.galleryimg = 'no';
+
+               var tileSize = this.options.tileSize;
+               img.style.width = tileSize + 'px';
+               img.style.height = tileSize + 'px';
+       },
+
+       _getTile: function () {
+               if (this.options.reuseTiles && this._unusedTiles.length > 0) {
+                       var tile = this._unusedTiles.pop();
+                       this._resetTile(tile);
+                       return tile;
+               }
+               return this._createTile();
+       },
+
+       _resetTile: function (tile) {
+               // Override if data stored on a tile needs to be cleaned up before reuse
+       },
+
+       _createTile: function () {
+               var tile = this._tileImg.cloneNode(false);
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;
+               return tile;
+       },
+
+       _loadTile: function (tile, tilePoint) {
+               tile._layer  = this;
+               tile.onload  = this._tileOnLoad;
+               tile.onerror = this._tileOnError;
+
+               tile.src     = this.getTileUrl(tilePoint);
+       },
+
+    _tileLoaded: function () {
+        this._tilesToLoad--;
+        if (!this._tilesToLoad) {
+            this.fire('load');
+        }
+    },
+
+       _tileOnLoad: function (e) {
+               var layer = this._layer;
+
+               //Only if we are loading an actual image
+               if (this.src !== L.Util.emptyImageUrl) {
+                       L.DomUtil.addClass(this, 'leaflet-tile-loaded');
+
+                       layer.fire('tileload', {
+                               tile: this,
+                               url: this.src
+                       });
+               }
+
+               layer._tileLoaded();
+       },
+
+       _tileOnError: function (e) {
+               var layer = this._layer;
+
+               layer.fire('tileerror', {
+                       tile: this,
+                       url: this.src
+               });
+
+               var newUrl = layer.options.errorTileUrl;
+               if (newUrl) {
+                       this.src = newUrl;
+               }
+
+        layer._tileLoaded();
+    }
+});
+
+L.tileLayer = function (url, options) {
+       return new L.TileLayer(url, options);
+};
+
+
+L.TileLayer.WMS = L.TileLayer.extend({
+
+       defaultWmsParams: {
+               service: 'WMS',
+               request: 'GetMap',
+               version: '1.1.1',
+               layers: '',
+               styles: '',
+               format: 'image/jpeg',
+               transparent: false
+       },
+
+       initialize: function (url, options) { // (String, Object)
+
+               this._url = url;
+
+               var wmsParams = L.Util.extend({}, this.defaultWmsParams);
+
+               if (options.detectRetina && L.Browser.retina) {
+                       wmsParams.width = wmsParams.height = this.options.tileSize * 2;
+               } else {
+                       wmsParams.width = wmsParams.height = this.options.tileSize;
+               }
+
+               for (var i in options) {
+                       // all keys that are not TileLayer options go to WMS params
+                       if (!this.options.hasOwnProperty(i)) {
+                               wmsParams[i] = options[i];
+                       }
+               }
+
+               this.wmsParams = wmsParams;
+
+               L.Util.setOptions(this, options);
+       },
+
+       onAdd: function (map) {
+
+               var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
+               this.wmsParams[projectionKey] = map.options.crs.code;
+
+               L.TileLayer.prototype.onAdd.call(this, map);
+       },
+
+       getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
+
+               var map = this._map,
+                       crs = map.options.crs,
+                       tileSize = this.options.tileSize,
+
+                       nwPoint = tilePoint.multiplyBy(tileSize),
+                       sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
+
+                       nw = crs.project(map.unproject(nwPoint, zoom)),
+                       se = crs.project(map.unproject(sePoint, zoom)),
+
+                       bbox = [nw.x, se.y, se.x, nw.y].join(','),
+
+                       url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
+
+               return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
+       },
+
+       setParams: function (params, noRedraw) {
+
+               L.Util.extend(this.wmsParams, params);
+
+               if (!noRedraw) {
+                       this.redraw();
+               }
+
+               return this;
+       }
+});
+
+L.tileLayer.wms = function (url, options) {
+       return new L.TileLayer.WMS(url, options);
+};
+
+
+L.TileLayer.Canvas = L.TileLayer.extend({
+       options: {
+               async: false
+       },
+
+       initialize: function (options) {
+               L.Util.setOptions(this, options);
+       },
+
+       redraw: function () {
+               var i,
+                       tiles = this._tiles;
+
+               for (i in tiles) {
+                       if (tiles.hasOwnProperty(i)) {
+                               this._redrawTile(tiles[i]);
+                       }
+               }
+       },
+
+       _redrawTile: function (tile) {
+               this.drawTile(tile, tile._tilePoint, tile._zoom);
+       },
+
+       _createTileProto: function () {
+               var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
+
+               var tileSize = this.options.tileSize;
+               proto.width = tileSize;
+               proto.height = tileSize;
+       },
+
+       _createTile: function () {
+               var tile = this._canvasProto.cloneNode(false);
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;
+               return tile;
+       },
+
+       _loadTile: function (tile, tilePoint, zoom) {
+               tile._layer = this;
+               tile._tilePoint = tilePoint;
+               tile._zoom = zoom;
+
+               this.drawTile(tile, tilePoint, zoom);
+
+               if (!this.options.async) {
+                       this.tileDrawn(tile);
+               }
+       },
+
+       drawTile: function (tile, tilePoint, zoom) {
+               // override with rendering code
+       },
+
+       tileDrawn: function (tile) {
+               this._tileOnLoad.call(tile);
+       }
+});
+
+
+L.tileLayer.canvas = function (options) {
+       return new L.TileLayer.Canvas(options);
+};
+
+L.ImageOverlay = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               opacity: 1
+       },
+
+       initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
+               this._url = url;
+               this._bounds = L.latLngBounds(bounds);
+
+               L.Util.setOptions(this, options);
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               if (!this._image) {
+                       this._initImage();
+               }
+
+               map._panes.overlayPane.appendChild(this._image);
+
+               map.on('viewreset', this._reset, this);
+
+               if (map.options.zoomAnimation && L.Browser.any3d) {
+                       map.on('zoomanim', this._animateZoom, this);
+               }
+
+               this._reset();
+       },
+
+       onRemove: function (map) {
+               map.getPanes().overlayPane.removeChild(this._image);
+
+               map.off('viewreset', this._reset, this);
+
+               if (map.options.zoomAnimation) {
+                       map.off('zoomanim', this._animateZoom, this);
+               }
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       setOpacity: function (opacity) {
+               this.options.opacity = opacity;
+               this._updateOpacity();
+               return this;
+       },
+
+       // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
+       bringToFront: function () {
+               if (this._image) {
+                       this._map._panes.overlayPane.appendChild(this._image);
+               }
+               return this;
+       },
+
+       bringToBack: function () {
+               var pane = this._map._panes.overlayPane;
+               if (this._image) {
+                       pane.insertBefore(this._image, pane.firstChild);
+               }
+               return this;
+       },
+
+       _initImage: function () {
+               this._image = L.DomUtil.create('img', 'leaflet-image-layer');
+
+               if (this._map.options.zoomAnimation && L.Browser.any3d) {
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
+               } else {
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
+               }
+
+               this._updateOpacity();
+
+               //TODO createImage util method to remove duplication
+               L.Util.extend(this._image, {
+                       galleryimg: 'no',
+                       onselectstart: L.Util.falseFn,
+                       onmousemove: L.Util.falseFn,
+                       onload: L.Util.bind(this._onImageLoad, this),
+                       src: this._url
+               });
+       },
+
+       _animateZoom: function (e) {
+               var map = this._map,
+                       image = this._image,
+                   scale = map.getZoomScale(e.zoom),
+                   nw = this._bounds.getNorthWest(),
+                   se = this._bounds.getSouthEast(),
+
+                   topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
+                   size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
+                   currentSize = map.latLngToLayerPoint(se)._subtract(map.latLngToLayerPoint(nw)),
+                   origin = topLeft._add(size._subtract(currentSize)._divideBy(2));
+
+               image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
+       },
+
+       _reset: function () {
+               var image   = this._image,
+                   topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
+                   size    = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
+
+               L.DomUtil.setPosition(image, topLeft);
+
+               image.style.width  = size.x + 'px';
+               image.style.height = size.y + 'px';
+       },
+
+       _onImageLoad: function () {
+               this.fire('load');
+       },
+
+       _updateOpacity: function () {
+               L.DomUtil.setOpacity(this._image, this.options.opacity);
+       }
+});
+
+L.imageOverlay = function (url, bounds, options) {
+       return new L.ImageOverlay(url, bounds, options);
+};
+
+
+L.Icon = L.Class.extend({
+       options: {
+               /*
+               iconUrl: (String) (required)
+               iconSize: (Point) (can be set through CSS)
+               iconAnchor: (Point) (centered by default if size is specified, can be set in CSS with negative margins)
+               popupAnchor: (Point) (if not specified, popup opens in the anchor point)
+               shadowUrl: (Point) (no shadow by default)
+               shadowSize: (Point)
+               shadowAnchor: (Point)
+               */
+               className: ''
+       },
+
+       initialize: function (options) {
+               L.Util.setOptions(this, options);
+       },
+
+       createIcon: function () {
+               return this._createIcon('icon');
+       },
+
+       createShadow: function () {
+               return this._createIcon('shadow');
+       },
+
+       _createIcon: function (name) {
+               var src = this._getIconUrl(name);
+
+               if (!src) {
+                       if (name === 'icon') {
+                               throw new Error("iconUrl not set in Icon options (see the docs).");
+                       }
+                       return null;
+               }
+
+               var img = this._createImg(src);
+               this._setIconStyles(img, name);
+
+               return img;
+       },
+
+       _setIconStyles: function (img, name) {
+               var options = this.options,
+                       size = L.point(options[name + 'Size']),
+                       anchor;
+
+               if (name === 'shadow') {
+                       anchor = L.point(options.shadowAnchor || options.iconAnchor);
+               } else {
+                       anchor = L.point(options.iconAnchor);
+               }
+
+               if (!anchor && size) {
+                       anchor = size.divideBy(2, true);
+               }
+
+               img.className = 'leaflet-marker-' + name + ' ' + options.className;
+
+               if (anchor) {
+                       img.style.marginLeft = (-anchor.x) + 'px';
+                       img.style.marginTop  = (-anchor.y) + 'px';
+               }
+
+               if (size) {
+                       img.style.width  = size.x + 'px';
+                       img.style.height = size.y + 'px';
+               }
+       },
+
+       _createImg: function (src) {
+               var el;
+
+               if (!L.Browser.ie6) {
+                       el = document.createElement('img');
+                       el.src = src;
+               } else {
+                       el = document.createElement('div');
+                       el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
+               }
+               return el;
+       },
+
+       _getIconUrl: function (name) {
+               return this.options[name + 'Url'];
+       }
+});
+
+L.icon = function (options) {
+       return new L.Icon(options);
+};
+
+
+
+L.Icon.Default = L.Icon.extend({
+
+       options: {
+               iconSize: new L.Point(25, 41),
+               iconAnchor: new L.Point(12, 41),
+               popupAnchor: new L.Point(1, -34),
+
+               shadowSize: new L.Point(41, 41)
+       },
+
+       _getIconUrl: function (name) {
+               var key = name + 'Url';
+
+               if (this.options[key]) {
+                       return this.options[key];
+               }
+
+               var path = L.Icon.Default.imagePath;
+
+               if (!path) {
+                       throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
+               }
+
+               return path + '/marker-' + name + '.png';
+       }
+});
+
+L.Icon.Default.imagePath = (function () {
+       var scripts = document.getElementsByTagName('script'),
+           leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
+
+       var i, len, src, matches;
+
+       for (i = 0, len = scripts.length; i < len; i++) {
+               src = scripts[i].src;
+               matches = src.match(leafletRe);
+
+               if (matches) {
+                       return src.split(leafletRe)[0] + '/images';
+               }
+       }
+}());
+
+
+/*
+ * L.Marker is used to display clickable/draggable icons on the map.
+ */
+
+L.Marker = L.Class.extend({
+
+       includes: L.Mixin.Events,
+
+       options: {
+               icon: new L.Icon.Default(),
+               title: '',
+               clickable: true,
+               draggable: false,
+               zIndexOffset: 0,
+               opacity: 1
+       },
+
+       initialize: function (latlng, options) {
+               L.Util.setOptions(this, options);
+               this._latlng = L.latLng(latlng);
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               map.on('viewreset', this.update, this);
+
+               this._initIcon();
+               this.update();
+
+               if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
+                       map.on('zoomanim', this._animateZoom, this);
+               }
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               this._removeIcon();
+
+               this.fire('remove');
+
+               map.off({
+                       'viewreset': this.update,
+                       'zoomanim': this._animateZoom
+               }, this);
+
+               this._map = null;
+       },
+
+       getLatLng: function () {
+               return this._latlng;
+       },
+
+       setLatLng: function (latlng) {
+               this._latlng = L.latLng(latlng);
+
+               this.update();
+
+               this.fire('move', { latlng: this._latlng });
+       },
+
+       setZIndexOffset: function (offset) {
+               this.options.zIndexOffset = offset;
+               this.update();
+       },
+
+       setIcon: function (icon) {
+               if (this._map) {
+                       this._removeIcon();
+               }
+
+               this.options.icon = icon;
+
+               if (this._map) {
+                       this._initIcon();
+                       this.update();
+               }
+       },
+
+       update: function () {
+               if (!this._icon) { return; }
+
+               var pos = this._map.latLngToLayerPoint(this._latlng).round();
+               this._setPos(pos);
+       },
+
+       _initIcon: function () {
+               var options = this.options,
+                   map = this._map,
+                   animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
+                   classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
+                   needOpacityUpdate = false;
+
+               if (!this._icon) {
+                       this._icon = options.icon.createIcon();
+
+                       if (options.title) {
+                               this._icon.title = options.title;
+                       }
+
+                       this._initInteraction();
+                       needOpacityUpdate = (this.options.opacity < 1);
+
+                       L.DomUtil.addClass(this._icon, classToAdd);
+               }
+               if (!this._shadow) {
+                       this._shadow = options.icon.createShadow();
+
+                       if (this._shadow) {
+                               L.DomUtil.addClass(this._shadow, classToAdd);
+                               needOpacityUpdate = (this.options.opacity < 1);
+                       }
+               }
+
+               if (needOpacityUpdate) {
+                       this._updateOpacity();
+               }
+
+               var panes = this._map._panes;
+
+               panes.markerPane.appendChild(this._icon);
+
+               if (this._shadow) {
+                       panes.shadowPane.appendChild(this._shadow);
+               }
+       },
+
+       _removeIcon: function () {
+               var panes = this._map._panes;
+
+               panes.markerPane.removeChild(this._icon);
+
+               if (this._shadow) {
+                       panes.shadowPane.removeChild(this._shadow);
+               }
+
+               this._icon = this._shadow = null;
+       },
+
+       _setPos: function (pos) {
+               L.DomUtil.setPosition(this._icon, pos);
+
+               if (this._shadow) {
+                       L.DomUtil.setPosition(this._shadow, pos);
+               }
+
+               this._icon.style.zIndex = pos.y + this.options.zIndexOffset;
+       },
+
+       _animateZoom: function (opt) {
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
+
+               this._setPos(pos);
+       },
+
+       _initInteraction: function () {
+               if (!this.options.clickable) {
+                       return;
+               }
+
+               var icon = this._icon,
+                       events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
+
+               L.DomUtil.addClass(icon, 'leaflet-clickable');
+               L.DomEvent.on(icon, 'click', this._onMouseClick, this);
+
+               for (var i = 0; i < events.length; i++) {
+                       L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
+               }
+
+               if (L.Handler.MarkerDrag) {
+                       this.dragging = new L.Handler.MarkerDrag(this);
+
+                       if (this.options.draggable) {
+                               this.dragging.enable();
+                       }
+               }
+       },
+
+       _onMouseClick: function (e) {
+               if (this.hasEventListeners(e.type)) {
+                       L.DomEvent.stopPropagation(e);
+               }
+               if (this.dragging && this.dragging.moved()) { return; }
+               if (this._map.dragging && this._map.dragging.moved()) { return; }
+               this.fire(e.type, {
+                       originalEvent: e
+               });
+       },
+
+       _fireMouseEvent: function (e) {
+               this.fire(e.type, {
+                       originalEvent: e
+               });
+               if (e.type !== 'mousedown') {
+                       L.DomEvent.stopPropagation(e);
+               }
+       },
+
+       setOpacity: function (opacity) {
+               this.options.opacity = opacity;
+               if (this._map) {
+                       this._updateOpacity();
+               }
+       },
+
+       _updateOpacity: function () {
+               L.DomUtil.setOpacity(this._icon, this.options.opacity);
+               if (this._shadow) {
+                       L.DomUtil.setOpacity(this._shadow, this.options.opacity);
+               }
+       }
+});
+
+L.marker = function (latlng, options) {
+       return new L.Marker(latlng, options);
+};
+
+
+L.DivIcon = L.Icon.extend({
+       options: {
+               iconSize: new L.Point(12, 12), // also can be set through CSS
+               /*
+               iconAnchor: (Point)
+               popupAnchor: (Point)
+               html: (String)
+               bgPos: (Point)
+               */
+               className: 'leaflet-div-icon'
+       },
+
+       createIcon: function () {
+               var div = document.createElement('div'),
+                   options = this.options;
+
+               if (options.html) {
+                       div.innerHTML = options.html;
+               }
+
+               if (options.bgPos) {
+                       div.style.backgroundPosition =
+                                       (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
+               }
+
+               this._setIconStyles(div, 'icon');
+               return div;
+       },
+
+       createShadow: function () {
+               return null;
+       }
+});
+
+L.divIcon = function (options) {
+       return new L.DivIcon(options);
+};
+
+
+
+L.Map.mergeOptions({
+       closePopupOnClick: true
+});
+
+L.Popup = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               minWidth: 50,
+               maxWidth: 300,
+               maxHeight: null,
+               autoPan: true,
+               closeButton: true,
+               offset: new L.Point(0, 6),
+               autoPanPadding: new L.Point(5, 5),
+               className: ''
+       },
+
+       initialize: function (options, source) {
+               L.Util.setOptions(this, options);
+
+               this._source = source;
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               if (!this._container) {
+                       this._initLayout();
+               }
+               this._updateContent();
+
+               var animFade = map.options.fadeAnimation;
+
+               if (animFade) {
+                       L.DomUtil.setOpacity(this._container, 0);
+               }
+               map._panes.popupPane.appendChild(this._container);
+
+               map.on('viewreset', this._updatePosition, this);
+
+               if (L.Browser.any3d) {
+                       map.on('zoomanim', this._zoomAnimation, this);
+               }
+
+               if (map.options.closePopupOnClick) {
+                       map.on('preclick', this._close, this);
+               }
+
+               this._update();
+
+               if (animFade) {
+                       L.DomUtil.setOpacity(this._container, 1);
+               }
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       openOn: function (map) {
+               map.openPopup(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               map._panes.popupPane.removeChild(this._container);
+
+               L.Util.falseFn(this._container.offsetWidth); // force reflow
+
+               map.off({
+                       viewreset: this._updatePosition,
+                       preclick: this._close,
+                       zoomanim: this._zoomAnimation
+               }, this);
+
+               if (map.options.fadeAnimation) {
+                       L.DomUtil.setOpacity(this._container, 0);
+               }
+
+               this._map = null;
+       },
+
+       setLatLng: function (latlng) {
+               this._latlng = L.latLng(latlng);
+               this._update();
+               return this;
+       },
+
+       setContent: function (content) {
+               this._content = content;
+               this._update();
+               return this;
+       },
+
+       _close: function () {
+               var map = this._map;
+
+               if (map) {
+                       map._popup = null;
+
+                       map
+                               .removeLayer(this)
+                               .fire('popupclose', {popup: this});
+               }
+       },
+
+       _initLayout: function () {
+               var prefix = 'leaflet-popup',
+                       container = this._container = L.DomUtil.create('div', prefix + ' ' + this.options.className + ' leaflet-zoom-animated'),
+                       closeButton;
+
+               if (this.options.closeButton) {
+                       closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
+                       closeButton.href = '#close';
+                       closeButton.innerHTML = '&#215;';
+
+                       L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
+               }
+
+               var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
+               L.DomEvent.disableClickPropagation(wrapper);
+
+               this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
+               L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
+
+               this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
+               this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
+       },
+
+       _update: function () {
+               if (!this._map) { return; }
+
+               this._container.style.visibility = 'hidden';
+
+               this._updateContent();
+               this._updateLayout();
+               this._updatePosition();
+
+               this._container.style.visibility = '';
+
+               this._adjustPan();
+       },
+
+       _updateContent: function () {
+               if (!this._content) { return; }
+
+               if (typeof this._content === 'string') {
+                       this._contentNode.innerHTML = this._content;
+               } else {
+                       while (this._contentNode.hasChildNodes()) {
+                               this._contentNode.removeChild(this._contentNode.firstChild);
+                       }
+                       this._contentNode.appendChild(this._content);
+               }
+               this.fire('contentupdate');
+       },
+
+       _updateLayout: function () {
+               var container = this._contentNode,
+                       style = container.style;
+
+               style.width = '';
+               style.whiteSpace = 'nowrap';
+
+               var width = container.offsetWidth;
+               width = Math.min(width, this.options.maxWidth);
+               width = Math.max(width, this.options.minWidth);
+
+               style.width = (width + 1) + 'px';
+               style.whiteSpace = '';
+
+               style.height = '';
+
+               var height = container.offsetHeight,
+                       maxHeight = this.options.maxHeight,
+                       scrolledClass = 'leaflet-popup-scrolled';
+
+               if (maxHeight && height > maxHeight) {
+                       style.height = maxHeight + 'px';
+                       L.DomUtil.addClass(container, scrolledClass);
+               } else {
+                       L.DomUtil.removeClass(container, scrolledClass);
+               }
+
+               this._containerWidth = this._container.offsetWidth;
+       },
+
+       _updatePosition: function () {
+               var pos = this._map.latLngToLayerPoint(this._latlng),
+                       is3d = L.Browser.any3d,
+                       offset = this.options.offset;
+
+               if (is3d) {
+                       L.DomUtil.setPosition(this._container, pos);
+               }
+
+               this._containerBottom = -offset.y - (is3d ? 0 : pos.y);
+               this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x);
+
+               //Bottom position the popup in case the height of the popup changes (images loading etc)
+               this._container.style.bottom = this._containerBottom + 'px';
+               this._container.style.left = this._containerLeft + 'px';
+       },
+
+       _zoomAnimation: function (opt) {
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
+
+               L.DomUtil.setPosition(this._container, pos);
+       },
+
+       _adjustPan: function () {
+               if (!this.options.autoPan) { return; }
+
+               var map = this._map,
+                       containerHeight = this._container.offsetHeight,
+                       containerWidth = this._containerWidth,
+
+                       layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
+
+               if (L.Browser.any3d) {
+                       layerPos._add(L.DomUtil.getPosition(this._container));
+               }
+
+               var containerPos = map.layerPointToContainerPoint(layerPos),
+                       padding = this.options.autoPanPadding,
+                       size = map.getSize(),
+                       dx = 0,
+                       dy = 0;
+
+               if (containerPos.x < 0) {
+                       dx = containerPos.x - padding.x;
+               }
+               if (containerPos.x + containerWidth > size.x) {
+                       dx = containerPos.x + containerWidth - size.x + padding.x;
+               }
+               if (containerPos.y < 0) {
+                       dy = containerPos.y - padding.y;
+               }
+               if (containerPos.y + containerHeight > size.y) {
+                       dy = containerPos.y + containerHeight - size.y + padding.y;
+               }
+
+               if (dx || dy) {
+                       map.panBy(new L.Point(dx, dy));
+               }
+       },
+
+       _onCloseButtonClick: function (e) {
+               this._close();
+               L.DomEvent.stop(e);
+       }
+});
+
+L.popup = function (options, source) {
+       return new L.Popup(options, source);
+};
+
+
+/*
+ * Popup extension to L.Marker, adding openPopup & bindPopup methods.
+ */
+
+L.Marker.include({
+       openPopup: function () {
+               if (this._popup && this._map) {
+                       this._popup.setLatLng(this._latlng);
+                       this._map.openPopup(this._popup);
+               }
+
+               return this;
+       },
+
+       closePopup: function () {
+               if (this._popup) {
+                       this._popup._close();
+               }
+               return this;
+       },
+
+       bindPopup: function (content, options) {
+               var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
+
+               anchor = anchor.add(L.Popup.prototype.options.offset);
+
+               if (options && options.offset) {
+                       anchor = anchor.add(options.offset);
+               }
+
+               options = L.Util.extend({offset: anchor}, options);
+
+               if (!this._popup) {
+                       this
+                               .on('click', this.openPopup, this)
+                               .on('remove', this.closePopup, this)
+                               .on('move', this._movePopup, this);
+               }
+
+               this._popup = new L.Popup(options, this)
+                       .setContent(content);
+
+               return this;
+       },
+
+       unbindPopup: function () {
+               if (this._popup) {
+                       this._popup = null;
+                       this
+                               .off('click', this.openPopup)
+                               .off('remove', this.closePopup)
+                               .off('move', this._movePopup);
+               }
+               return this;
+       },
+
+       _movePopup: function (e) {
+               this._popup.setLatLng(e.latlng);
+       }
+});
+
+
+
+L.Map.include({
+       openPopup: function (popup) {
+               this.closePopup();
+
+               this._popup = popup;
+
+               return this
+                       .addLayer(popup)
+                       .fire('popupopen', {popup: this._popup});
+       },
+
+       closePopup: function () {
+               if (this._popup) {
+                       this._popup._close();
+               }
+               return this;
+       }
+});
+
+/*
+ * L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer.
+ */
+
+L.LayerGroup = L.Class.extend({
+       initialize: function (layers) {
+               this._layers = {};
+
+               var i, len;
+
+               if (layers) {
+                       for (i = 0, len = layers.length; i < len; i++) {
+                               this.addLayer(layers[i]);
+                       }
+               }
+       },
+
+       addLayer: function (layer) {
+               var id = L.Util.stamp(layer);
+
+               this._layers[id] = layer;
+
+               if (this._map) {
+                       this._map.addLayer(layer);
+               }
+
+               return this;
+       },
+
+       removeLayer: function (layer) {
+               var id = L.Util.stamp(layer);
+
+               delete this._layers[id];
+
+               if (this._map) {
+                       this._map.removeLayer(layer);
+               }
+
+               return this;
+       },
+
+       clearLayers: function () {
+               this.eachLayer(this.removeLayer, this);
+               return this;
+       },
+
+       invoke: function (methodName) {
+               var args = Array.prototype.slice.call(arguments, 1),
+                       i, layer;
+
+               for (i in this._layers) {
+                       if (this._layers.hasOwnProperty(i)) {
+                               layer = this._layers[i];
+
+                               if (layer[methodName]) {
+                                       layer[methodName].apply(layer, args);
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+               this.eachLayer(map.addLayer, map);
+       },
+
+       onRemove: function (map) {
+               this.eachLayer(map.removeLayer, map);
+               this._map = null;
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       eachLayer: function (method, context) {
+               for (var i in this._layers) {
+                       if (this._layers.hasOwnProperty(i)) {
+                               method.call(context, this._layers[i]);
+                       }
+               }
+       }
+});
+
+L.layerGroup = function (layers) {
+       return new L.LayerGroup(layers);
+};
+
+
+/*
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
+ */
+
+L.FeatureGroup = L.LayerGroup.extend({
+       includes: L.Mixin.Events,
+
+       addLayer: function (layer) {
+               if (this._layers[L.Util.stamp(layer)]) {
+                       return this;
+               }
+
+               layer.on('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
+
+               L.LayerGroup.prototype.addLayer.call(this, layer);
+
+               if (this._popupContent && layer.bindPopup) {
+                       layer.bindPopup(this._popupContent);
+               }
+
+               return this;
+       },
+
+       removeLayer: function (layer) {
+               layer.off('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
+
+               L.LayerGroup.prototype.removeLayer.call(this, layer);
+
+               if (this._popupContent) {
+                       return this.invoke('unbindPopup');
+               } else {
+                       return this;
+               }
+       },
+
+       bindPopup: function (content) {
+               this._popupContent = content;
+               return this.invoke('bindPopup', content);
+       },
+
+       setStyle: function (style) {
+               return this.invoke('setStyle', style);
+       },
+
+       bringToFront: function () {
+               return this.invoke('bringToFront');
+       },
+
+       bringToBack: function () {
+               return this.invoke('bringToBack');
+       },
+
+       getBounds: function () {
+               var bounds = new L.LatLngBounds();
+               this.eachLayer(function (layer) {
+                       bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
+               }, this);
+               return bounds;
+       },
+
+       _propagateEvent: function (e) {
+               e.layer  = e.target;
+               e.target = this;
+
+               this.fire(e.type, e);
+       }
+});
+
+L.featureGroup = function (layers) {
+       return new L.FeatureGroup(layers);
+};
+
+
+/*
+ * L.Path is a base class for rendering vector paths on a map. It's inherited by Polyline, Circle, etc.
+ */
+
+L.Path = L.Class.extend({
+       includes: [L.Mixin.Events],
+
+       statics: {
+               // how much to extend the clip area around the map view
+               // (relative to its size, e.g. 0.5 is half the screen in each direction)
+               // set in such way that SVG element doesn't exceed 1280px (vector layers flicker on dragend if it is)
+               CLIP_PADDING: L.Browser.mobile ?
+                       Math.max(0, Math.min(0.5,
+                               (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2))
+                       : 0.5
+       },
+
+       options: {
+               stroke: true,
+               color: '#0033ff',
+               dashArray: null,
+               weight: 5,
+               opacity: 0.5,
+
+               fill: false,
+               fillColor: null, //same as color by default
+               fillOpacity: 0.2,
+
+               clickable: true
+       },
+
+       initialize: function (options) {
+               L.Util.setOptions(this, options);
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               if (!this._container) {
+                       this._initElements();
+                       this._initEvents();
+               }
+
+               this.projectLatlngs();
+               this._updatePath();
+
+               if (this._container) {
+                       this._map._pathRoot.appendChild(this._container);
+               }
+
+               map.on({
+                       'viewreset': this.projectLatlngs,
+                       'moveend': this._updatePath
+               }, this);
+       },
+
+       addTo: function (map) {
+               map.addLayer(this);
+               return this;
+       },
+
+       onRemove: function (map) {
+               map._pathRoot.removeChild(this._container);
+
+               this._map = null;
+
+               if (L.Browser.vml) {
+                       this._container = null;
+                       this._stroke = null;
+                       this._fill = null;
+               }
+
+               this.fire('remove');
+
+               map.off({
+                       'viewreset': this.projectLatlngs,
+                       'moveend': this._updatePath
+               }, this);
+       },
+
+       projectLatlngs: function () {
+               // do all projection stuff here
+       },
+
+       setStyle: function (style) {
+               L.Util.setOptions(this, style);
+
+               if (this._container) {
+                       this._updateStyle();
+               }
+
+               return this;
+       },
+
+       redraw: function () {
+               if (this._map) {
+                       this.projectLatlngs();
+                       this._updatePath();
+               }
+               return this;
+       }
+});
+
+L.Map.include({
+       _updatePathViewport: function () {
+               var p = L.Path.CLIP_PADDING,
+                       size = this.getSize(),
+                       panePos = L.DomUtil.getPosition(this._mapPane),
+                       min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
+                       max = min.add(size.multiplyBy(1 + p * 2)._round());
+
+               this._pathViewport = new L.Bounds(min, max);
+       }
+});
+
+
+L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
+
+L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
+
+L.Path = L.Path.extend({
+       statics: {
+               SVG: L.Browser.svg
+       },
+
+       bringToFront: function () {
+               var root = this._map._pathRoot,
+                       path = this._container;
+
+               if (path && root.lastChild !== path) {
+                       root.appendChild(path);
+               }
+               return this;
+       },
+
+       bringToBack: function () {
+               var root = this._map._pathRoot,
+                       path = this._container,
+                       first = root.firstChild;
+
+               if (path && first !== path) {
+                       root.insertBefore(path, first);
+               }
+               return this;
+       },
+
+       getPathString: function () {
+               // form path string here
+       },
+
+       _createElement: function (name) {
+               return document.createElementNS(L.Path.SVG_NS, name);
+       },
+
+       _initElements: function () {
+               this._map._initPathRoot();
+               this._initPath();
+               this._initStyle();
+       },
+
+       _initPath: function () {
+               this._container = this._createElement('g');
+
+               this._path = this._createElement('path');
+               this._container.appendChild(this._path);
+       },
+
+       _initStyle: function () {
+               if (this.options.stroke) {
+                       this._path.setAttribute('stroke-linejoin', 'round');
+                       this._path.setAttribute('stroke-linecap', 'round');
+               }
+               if (this.options.fill) {
+                       this._path.setAttribute('fill-rule', 'evenodd');
+               }
+               this._updateStyle();
+       },
+
+       _updateStyle: function () {
+               if (this.options.stroke) {
+                       this._path.setAttribute('stroke', this.options.color);
+                       this._path.setAttribute('stroke-opacity', this.options.opacity);
+                       this._path.setAttribute('stroke-width', this.options.weight);
+                       if (this.options.dashArray) {
+                               this._path.setAttribute('stroke-dasharray', this.options.dashArray);
+                       } else {
+                               this._path.removeAttribute('stroke-dasharray');
+                       }
+               } else {
+                       this._path.setAttribute('stroke', 'none');
+               }
+               if (this.options.fill) {
+                       this._path.setAttribute('fill', this.options.fillColor || this.options.color);
+                       this._path.setAttribute('fill-opacity', this.options.fillOpacity);
+               } else {
+                       this._path.setAttribute('fill', 'none');
+               }
+       },
+
+       _updatePath: function () {
+               var str = this.getPathString();
+               if (!str) {
+                       // fix webkit empty string parsing bug
+                       str = 'M0 0';
+               }
+               this._path.setAttribute('d', str);
+       },
+
+       // TODO remove duplication with L.Map
+       _initEvents: function () {
+               if (this.options.clickable) {
+                       if (L.Browser.svg || !L.Browser.vml) {
+                               this._path.setAttribute('class', 'leaflet-clickable');
+                       }
+
+                       L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
+
+                       var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'mousemove', 'contextmenu'];
+                       for (var i = 0; i < events.length; i++) {
+                               L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
+                       }
+               }
+       },
+
+       _onMouseClick: function (e) {
+               if (this._map.dragging && this._map.dragging.moved()) {
+                       return;
+               }
+
+               this._fireMouseEvent(e);
+       },
+
+       _fireMouseEvent: function (e) {
+               if (!this.hasEventListeners(e.type)) {
+                       return;
+               }
+
+               var map = this._map,
+                       containerPoint = map.mouseEventToContainerPoint(e),
+                       layerPoint = map.containerPointToLayerPoint(containerPoint),
+                       latlng = map.layerPointToLatLng(layerPoint);
+
+               this.fire(e.type, {
+                       latlng: latlng,
+                       layerPoint: layerPoint,
+                       containerPoint: containerPoint,
+                       originalEvent: e
+               });
+
+               if (e.type === 'contextmenu') {
+                       L.DomEvent.preventDefault(e);
+               }
+               L.DomEvent.stopPropagation(e);
+       }
+});
+
+L.Map.include({
+       _initPathRoot: function () {
+               if (!this._pathRoot) {
+                       this._pathRoot = L.Path.prototype._createElement('svg');
+                       this._panes.overlayPane.appendChild(this._pathRoot);
+
+                       if (this.options.zoomAnimation && L.Browser.any3d) {
+                               this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
+
+                               this.on({
+                                       'zoomanim': this._animatePathZoom,
+                                       'zoomend': this._endPathZoom
+                               });
+                       } else {
+                               this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
+                       }
+
+                       this.on('moveend', this._updateSvgViewport);
+                       this._updateSvgViewport();
+               }
+       },
+
+       _animatePathZoom: function (opt) {
+               var scale = this.getZoomScale(opt.zoom),
+                       offset = this._getCenterOffset(opt.center).divideBy(1 - 1 / scale),
+                       viewportPos = this.containerPointToLayerPoint(this.getSize().multiplyBy(-L.Path.CLIP_PADDING)),
+                       origin = viewportPos.add(offset).round();
+
+               this._pathRoot.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString((origin.multiplyBy(-1).add(L.DomUtil.getPosition(this._pathRoot)).multiplyBy(scale).add(origin))) + ' scale(' + scale + ') ';
+
+               this._pathZooming = true;
+       },
+
+       _endPathZoom: function () {
+               this._pathZooming = false;
+       },
+
+       _updateSvgViewport: function () {
+               if (this._pathZooming) {
+                       // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
+                       // When the zoom animation ends we will be updated again anyway
+                       // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
+                       return;
+               }
+
+               this._updatePathViewport();
+
+               var vp = this._pathViewport,
+                       min = vp.min,
+                       max = vp.max,
+                       width = max.x - min.x,
+                       height = max.y - min.y,
+                       root = this._pathRoot,
+                       pane = this._panes.overlayPane;
+
+               // Hack to make flicker on drag end on mobile webkit less irritating
+               if (L.Browser.mobileWebkit) {
+                       pane.removeChild(root);
+               }
+
+               L.DomUtil.setPosition(root, min);
+               root.setAttribute('width', width);
+               root.setAttribute('height', height);
+               root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
+
+               if (L.Browser.mobileWebkit) {
+                       pane.appendChild(root);
+               }
+       }
+});
+
+
+/*
+ * Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method.
+ */
+
+L.Path.include({
+
+       bindPopup: function (content, options) {
+
+               if (!this._popup || this._popup.options !== options) {
+                       this._popup = new L.Popup(options, this);
+               }
+
+               this._popup.setContent(content);
+
+               if (!this._popupHandlersAdded) {
+                       this
+                               .on('click', this._openPopup, this)
+                               .on('remove', this._closePopup, this);
+                       this._popupHandlersAdded = true;
+               }
+
+               return this;
+       },
+
+       unbindPopup: function () {
+               if (this._popup) {
+                       this._popup = null;
+                       this
+                               .off('click', this.openPopup)
+                               .off('remove', this.closePopup);
+               }
+               return this;
+       },
+
+       openPopup: function (latlng) {
+
+               if (this._popup) {
+                       latlng = latlng || this._latlng ||
+                                       this._latlngs[Math.floor(this._latlngs.length / 2)];
+
+                       this._openPopup({latlng: latlng});
+               }
+
+               return this;
+       },
+
+       _openPopup: function (e) {
+               this._popup.setLatLng(e.latlng);
+               this._map.openPopup(this._popup);
+       },
+
+       _closePopup: function () {
+               this._popup._close();
+       }
+});
+
+
+/*
+ * Vector rendering for IE6-8 through VML.
+ * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
+ */
+
+L.Browser.vml = !L.Browser.svg && (function () {
+       try {
+               var div = document.createElement('div');
+               div.innerHTML = '<v:shape adj="1"/>';
+
+               var shape = div.firstChild;
+               shape.style.behavior = 'url(#default#VML)';
+
+               return shape && (typeof shape.adj === 'object');
+       } catch (e) {
+               return false;
+       }
+}());
+
+L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
+       statics: {
+               VML: true,
+               CLIP_PADDING: 0.02
+       },
+
+       _createElement: (function () {
+               try {
+                       document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
+                       return function (name) {
+                               return document.createElement('<lvml:' + name + ' class="lvml">');
+                       };
+               } catch (e) {
+                       return function (name) {
+                               return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
+                       };
+               }
+       }()),
+
+       _initPath: function () {
+               var container = this._container = this._createElement('shape');
+               L.DomUtil.addClass(container, 'leaflet-vml-shape');
+               if (this.options.clickable) {
+                       L.DomUtil.addClass(container, 'leaflet-clickable');
+               }
+               container.coordsize = '1 1';
+
+               this._path = this._createElement('path');
+               container.appendChild(this._path);
+
+               this._map._pathRoot.appendChild(container);
+       },
+
+       _initStyle: function () {
+               this._updateStyle();
+       },
+
+       _updateStyle: function () {
+               var stroke = this._stroke,
+                       fill = this._fill,
+                       options = this.options,
+                       container = this._container;
+
+               container.stroked = options.stroke;
+               container.filled = options.fill;
+
+               if (options.stroke) {
+                       if (!stroke) {
+                               stroke = this._stroke = this._createElement('stroke');
+                               stroke.endcap = 'round';
+                               container.appendChild(stroke);
+                       }
+                       stroke.weight = options.weight + 'px';
+                       stroke.color = options.color;
+                       stroke.opacity = options.opacity;
+                       if (options.dashArray) {
+                               stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
+                       } else {
+                               stroke.dashStyle = '';
+                       }
+               } else if (stroke) {
+                       container.removeChild(stroke);
+                       this._stroke = null;
+               }
+
+               if (options.fill) {
+                       if (!fill) {
+                               fill = this._fill = this._createElement('fill');
+                               container.appendChild(fill);
+                       }
+                       fill.color = options.fillColor || options.color;
+                       fill.opacity = options.fillOpacity;
+               } else if (fill) {
+                       container.removeChild(fill);
+                       this._fill = null;
+               }
+       },
+
+       _updatePath: function () {
+               var style = this._container.style;
+
+               style.display = 'none';
+               this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
+               style.display = '';
+       }
+});
+
+L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
+       _initPathRoot: function () {
+               if (this._pathRoot) { return; }
+
+               var root = this._pathRoot = document.createElement('div');
+               root.className = 'leaflet-vml-container';
+               this._panes.overlayPane.appendChild(root);
+
+               this.on('moveend', this._updatePathViewport);
+               this._updatePathViewport();
+       }
+});
+
+
+/*
+ * Vector rendering for all browsers that support canvas.
+ */
+
+L.Browser.canvas = (function () {
+       return !!document.createElement('canvas').getContext;
+}());
+
+L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
+       statics: {
+               //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
+               CANVAS: true,
+               SVG: false
+       },
+
+       redraw: function () {
+               if (this._map) {
+                       this.projectLatlngs();
+                       this._requestUpdate();
+               }
+               return this;
+       },
+
+       setStyle: function (style) {
+               L.Util.setOptions(this, style);
+
+               if (this._map) {
+                       this._updateStyle();
+                       this._requestUpdate();
+               }
+               return this;
+       },
+
+       onRemove: function (map) {
+               map
+                   .off('viewreset', this.projectLatlngs, this)
+                   .off('moveend', this._updatePath, this);
+
+               this._requestUpdate();
+
+               this._map = null;
+       },
+
+       _requestUpdate: function () {
+               if (this._map && !L.Path._updateRequest) {
+                       L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
+               }
+       },
+
+       _fireMapMoveEnd: function () {
+               L.Path._updateRequest = null;
+               this.fire('moveend');
+       },
+
+       _initElements: function () {
+               this._map._initPathRoot();
+               this._ctx = this._map._canvasCtx;
+       },
+
+       _updateStyle: function () {
+               var options = this.options;
+
+               if (options.stroke) {
+                       this._ctx.lineWidth = options.weight;
+                       this._ctx.strokeStyle = options.color;
+               }
+               if (options.fill) {
+                       this._ctx.fillStyle = options.fillColor || options.color;
+               }
+       },
+
+       _drawPath: function () {
+               var i, j, len, len2, point, drawMethod;
+
+               this._ctx.beginPath();
+
+               for (i = 0, len = this._parts.length; i < len; i++) {
+                       for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
+                               point = this._parts[i][j];
+                               drawMethod = (j === 0 ? 'move' : 'line') + 'To';
+
+                               this._ctx[drawMethod](point.x, point.y);
+                       }
+                       // TODO refactor ugly hack
+                       if (this instanceof L.Polygon) {
+                               this._ctx.closePath();
+                       }
+               }
+       },
+
+       _checkIfEmpty: function () {
+               return !this._parts.length;
+       },
+
+       _updatePath: function () {
+               if (this._checkIfEmpty()) { return; }
+
+               var ctx = this._ctx,
+                       options = this.options;
+
+               this._drawPath();
+               ctx.save();
+               this._updateStyle();
+
+               if (options.fill) {
+                       if (options.fillOpacity < 1) {
+                               ctx.globalAlpha = options.fillOpacity;
+                       }
+                       ctx.fill();
+               }
+
+               if (options.stroke) {
+                       if (options.opacity < 1) {
+                               ctx.globalAlpha = options.opacity;
+                       }
+                       ctx.stroke();
+               }
+
+               ctx.restore();
+
+               // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
+       },
+
+       _initEvents: function () {
+               if (this.options.clickable) {
+                       // TODO hand cursor
+                       // TODO mouseover, mouseout, dblclick
+                       this._map.on('click', this._onClick, this);
+               }
+       },
+
+       _onClick: function (e) {
+               if (this._containsPoint(e.layerPoint)) {
+                       this.fire('click', e);
+               }
+       }
+});
+
+L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
+       _initPathRoot: function () {
+               var root = this._pathRoot,
+                       ctx;
+
+               if (!root) {
+                       root = this._pathRoot = document.createElement("canvas");
+                       root.style.position = 'absolute';
+                       ctx = this._canvasCtx = root.getContext('2d');
+
+                       ctx.lineCap = "round";
+                       ctx.lineJoin = "round";
+
+                       this._panes.overlayPane.appendChild(root);
+
+                       if (this.options.zoomAnimation) {
+                               this._pathRoot.className = 'leaflet-zoom-animated';
+                               this.on('zoomanim', this._animatePathZoom);
+                               this.on('zoomend', this._endPathZoom);
+                       }
+                       this.on('moveend', this._updateCanvasViewport);
+                       this._updateCanvasViewport();
+               }
+       },
+
+       _updateCanvasViewport: function () {
+               if (this._pathZooming) {
+                       //Don't redraw while zooming. See _updateSvgViewport for more details
+                       return;
+               }
+               this._updatePathViewport();
+
+               var vp = this._pathViewport,
+                       min = vp.min,
+                       size = vp.max.subtract(min),
+                       root = this._pathRoot;
+
+               //TODO check if this works properly on mobile webkit
+               L.DomUtil.setPosition(root, min);
+               root.width = size.x;
+               root.height = size.y;
+               root.getContext('2d').translate(-min.x, -min.y);
+       }
+});
+
+
+/*
+ * L.LineUtil contains different utility functions for line segments
+ * and polylines (clipping, simplification, distances, etc.)
+ */
+
+L.LineUtil = {
+
+       // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
+       // Improves rendering performance dramatically by lessening the number of points to draw.
+
+       simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
+               if (!tolerance || !points.length) {
+                       return points.slice();
+               }
+
+               var sqTolerance = tolerance * tolerance;
+
+               // stage 1: vertex reduction
+               points = this._reducePoints(points, sqTolerance);
+
+               // stage 2: Douglas-Peucker simplification
+               points = this._simplifyDP(points, sqTolerance);
+
+               return points;
+       },
+
+       // distance from a point to a segment between two points
+       pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
+               return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
+       },
+
+       closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
+               return this._sqClosestPointOnSegment(p, p1, p2);
+       },
+
+       // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
+       _simplifyDP: function (points, sqTolerance) {
+
+               var len = points.length,
+                       ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
+                       markers = new ArrayConstructor(len);
+
+               markers[0] = markers[len - 1] = 1;
+
+               this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
+
+               var i,
+                       newPoints = [];
+
+               for (i = 0; i < len; i++) {
+                       if (markers[i]) {
+                               newPoints.push(points[i]);
+                       }
+               }
+
+               return newPoints;
+       },
+
+       _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
+
+               var maxSqDist = 0,
+                       index, i, sqDist;
+
+               for (i = first + 1; i <= last - 1; i++) {
+                       sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
+
+                       if (sqDist > maxSqDist) {
+                               index = i;
+                               maxSqDist = sqDist;
+                       }
+               }
+
+               if (maxSqDist > sqTolerance) {
+                       markers[index] = 1;
+
+                       this._simplifyDPStep(points, markers, sqTolerance, first, index);
+                       this._simplifyDPStep(points, markers, sqTolerance, index, last);
+               }
+       },
+
+       // reduce points that are too close to each other to a single point
+       _reducePoints: function (points, sqTolerance) {
+               var reducedPoints = [points[0]];
+
+               for (var i = 1, prev = 0, len = points.length; i < len; i++) {
+                       if (this._sqDist(points[i], points[prev]) > sqTolerance) {
+                               reducedPoints.push(points[i]);
+                               prev = i;
+                       }
+               }
+               if (prev < len - 1) {
+                       reducedPoints.push(points[len - 1]);
+               }
+               return reducedPoints;
+       },
+
+       /*jshint bitwise:false */ // temporarily allow bitwise oprations
+
+       // Cohen-Sutherland line clipping algorithm.
+       // Used to avoid rendering parts of a polyline that are not currently visible.
+
+       clipSegment: function (a, b, bounds, useLastCode) {
+               var min = bounds.min,
+                       max = bounds.max;
+
+               var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
+                       codeB = this._getBitCode(b, bounds);
+
+               // save 2nd code to avoid calculating it on the next segment
+               this._lastCode = codeB;
+
+               while (true) {
+                       // if a,b is inside the clip window (trivial accept)
+                       if (!(codeA | codeB)) {
+                               return [a, b];
+                       // if a,b is outside the clip window (trivial reject)
+                       } else if (codeA & codeB) {
+                               return false;
+                       // other cases
+                       } else {
+                               var codeOut = codeA || codeB,
+                                       p = this._getEdgeIntersection(a, b, codeOut, bounds),
+                                       newCode = this._getBitCode(p, bounds);
+
+                               if (codeOut === codeA) {
+                                       a = p;
+                                       codeA = newCode;
+                               } else {
+                                       b = p;
+                                       codeB = newCode;
+                               }
+                       }
+               }
+       },
+
+       _getEdgeIntersection: function (a, b, code, bounds) {
+               var dx = b.x - a.x,
+                       dy = b.y - a.y,
+                       min = bounds.min,
+                       max = bounds.max;
+
+               if (code & 8) { // top
+                       return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
+               } else if (code & 4) { // bottom
+                       return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
+               } else if (code & 2) { // right
+                       return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
+               } else if (code & 1) { // left
+                       return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
+               }
+       },
+
+       _getBitCode: function (/*Point*/ p, bounds) {
+               var code = 0;
+
+               if (p.x < bounds.min.x) { // left
+                       code |= 1;
+               } else if (p.x > bounds.max.x) { // right
+                       code |= 2;
+               }
+               if (p.y < bounds.min.y) { // bottom
+                       code |= 4;
+               } else if (p.y > bounds.max.y) { // top
+                       code |= 8;
+               }
+
+               return code;
+       },
+
+       /*jshint bitwise:true */
+
+       // square distance (to avoid unnecessary Math.sqrt calls)
+       _sqDist: function (p1, p2) {
+               var dx = p2.x - p1.x,
+                       dy = p2.y - p1.y;
+               return dx * dx + dy * dy;
+       },
+
+       // return closest point on segment or distance to that point
+       _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
+               var x = p1.x,
+                       y = p1.y,
+                       dx = p2.x - x,
+                       dy = p2.y - y,
+                       dot = dx * dx + dy * dy,
+                       t;
+
+               if (dot > 0) {
+                       t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
+
+                       if (t > 1) {
+                               x = p2.x;
+                               y = p2.y;
+                       } else if (t > 0) {
+                               x += dx * t;
+                               y += dy * t;
+                       }
+               }
+
+               dx = p.x - x;
+               dy = p.y - y;
+
+               return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
+       }
+};
+
+
+L.Polyline = L.Path.extend({
+       initialize: function (latlngs, options) {
+               L.Path.prototype.initialize.call(this, options);
+
+               this._latlngs = this._convertLatLngs(latlngs);
+
+               // TODO refactor: move to Polyline.Edit.js
+               if (L.Handler.PolyEdit) {
+                       this.editing = new L.Handler.PolyEdit(this);
+
+                       if (this.options.editable) {
+                               this.editing.enable();
+                       }
+               }
+       },
+
+       options: {
+               // how much to simplify the polyline on each zoom level
+               // more = better performance and smoother look, less = more accurate
+               smoothFactor: 1.0,
+               noClip: false
+       },
+
+       projectLatlngs: function () {
+               this._originalPoints = [];
+
+               for (var i = 0, len = this._latlngs.length; i < len; i++) {
+                       this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
+               }
+       },
+
+       getPathString: function () {
+               for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
+                       str += this._getPathPartStr(this._parts[i]);
+               }
+               return str;
+       },
+
+       getLatLngs: function () {
+               return this._latlngs;
+       },
+
+       setLatLngs: function (latlngs) {
+               this._latlngs = this._convertLatLngs(latlngs);
+               return this.redraw();
+       },
+
+       addLatLng: function (latlng) {
+               this._latlngs.push(L.latLng(latlng));
+               return this.redraw();
+       },
+
+       spliceLatLngs: function (index, howMany) {
+               var removed = [].splice.apply(this._latlngs, arguments);
+               this._convertLatLngs(this._latlngs);
+               this.redraw();
+               return removed;
+       },
+
+       closestLayerPoint: function (p) {
+               var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
+
+               for (var j = 0, jLen = parts.length; j < jLen; j++) {
+                       var points = parts[j];
+                       for (var i = 1, len = points.length; i < len; i++) {
+                               p1 = points[i - 1];
+                               p2 = points[i];
+                               var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
+                               if (sqDist < minDistance) {
+                                       minDistance = sqDist;
+                                       minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
+                               }
+                       }
+               }
+               if (minPoint) {
+                       minPoint.distance = Math.sqrt(minDistance);
+               }
+               return minPoint;
+       },
+
+       getBounds: function () {
+               var b = new L.LatLngBounds();
+               var latLngs = this.getLatLngs();
+               for (var i = 0, len = latLngs.length; i < len; i++) {
+                       b.extend(latLngs[i]);
+               }
+               return b;
+       },
+
+       // TODO refactor: move to Polyline.Edit.js
+       onAdd: function (map) {
+               L.Path.prototype.onAdd.call(this, map);
+
+               if (this.editing && this.editing.enabled()) {
+                       this.editing.addHooks();
+               }
+       },
+
+       onRemove: function (map) {
+               if (this.editing && this.editing.enabled()) {
+                       this.editing.removeHooks();
+               }
+
+               L.Path.prototype.onRemove.call(this, map);
+       },
+
+       _convertLatLngs: function (latlngs) {
+               var i, len;
+               for (i = 0, len = latlngs.length; i < len; i++) {
+                       if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
+                               return;
+                       }
+                       latlngs[i] = L.latLng(latlngs[i]);
+               }
+               return latlngs;
+       },
+
+       _initEvents: function () {
+               L.Path.prototype._initEvents.call(this);
+       },
+
+       _getPathPartStr: function (points) {
+               var round = L.Path.VML;
+
+               for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
+                       p = points[j];
+                       if (round) {
+                               p._round();
+                       }
+                       str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
+               }
+               return str;
+       },
+
+       _clipPoints: function () {
+               var points = this._originalPoints,
+                       len = points.length,
+                       i, k, segment;
+
+               if (this.options.noClip) {
+                       this._parts = [points];
+                       return;
+               }
+
+               this._parts = [];
+
+               var parts = this._parts,
+                       vp = this._map._pathViewport,
+                       lu = L.LineUtil;
+
+               for (i = 0, k = 0; i < len - 1; i++) {
+                       segment = lu.clipSegment(points[i], points[i + 1], vp, i);
+                       if (!segment) {
+                               continue;
+                       }
+
+                       parts[k] = parts[k] || [];
+                       parts[k].push(segment[0]);
+
+                       // if segment goes out of screen, or it's the last one, it's the end of the line part
+                       if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
+                               parts[k].push(segment[1]);
+                               k++;
+                       }
+               }
+       },
+
+       // simplify each clipped part of the polyline
+       _simplifyPoints: function () {
+               var parts = this._parts,
+                       lu = L.LineUtil;
+
+               for (var i = 0, len = parts.length; i < len; i++) {
+                       parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
+               }
+       },
+
+       _updatePath: function () {
+               if (!this._map) { return; }
+
+               this._clipPoints();
+               this._simplifyPoints();
+
+               L.Path.prototype._updatePath.call(this);
+       }
+});
+
+L.polyline = function (latlngs, options) {
+       return new L.Polyline(latlngs, options);
+};
+
+
+/*
+ * L.PolyUtil contains utilify functions for polygons (clipping, etc.).
+ */
+
+/*jshint bitwise:false */ // allow bitwise oprations here
+
+L.PolyUtil = {};
+
+/*
+ * Sutherland-Hodgeman polygon clipping algorithm.
+ * Used to avoid rendering parts of a polygon that are not currently visible.
+ */
+L.PolyUtil.clipPolygon = function (points, bounds) {
+       var min = bounds.min,
+               max = bounds.max,
+               clippedPoints,
+               edges = [1, 4, 2, 8],
+               i, j, k,
+               a, b,
+               len, edge, p,
+               lu = L.LineUtil;
+
+       for (i = 0, len = points.length; i < len; i++) {
+               points[i]._code = lu._getBitCode(points[i], bounds);
+       }
+
+       // for each edge (left, bottom, right, top)
+       for (k = 0; k < 4; k++) {
+               edge = edges[k];
+               clippedPoints = [];
+
+               for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
+                       a = points[i];
+                       b = points[j];
+
+                       // if a is inside the clip window
+                       if (!(a._code & edge)) {
+                               // if b is outside the clip window (a->b goes out of screen)
+                               if (b._code & edge) {
+                                       p = lu._getEdgeIntersection(b, a, edge, bounds);
+                                       p._code = lu._getBitCode(p, bounds);
+                                       clippedPoints.push(p);
+                               }
+                               clippedPoints.push(a);
+
+                       // else if b is inside the clip window (a->b enters the screen)
+                       } else if (!(b._code & edge)) {
+                               p = lu._getEdgeIntersection(b, a, edge, bounds);
+                               p._code = lu._getBitCode(p, bounds);
+                               clippedPoints.push(p);
+                       }
+               }
+               points = clippedPoints;
+       }
+
+       return points;
+};
+
+/*jshint bitwise:true */
+
+
+/*
+ * L.Polygon is used to display polygons on a map.
+ */
+
+L.Polygon = L.Polyline.extend({
+       options: {
+               fill: true
+       },
+
+       initialize: function (latlngs, options) {
+               L.Polyline.prototype.initialize.call(this, latlngs, options);
+
+               if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
+                       this._latlngs = this._convertLatLngs(latlngs[0]);
+                       this._holes = latlngs.slice(1);
+               }
+       },
+
+       projectLatlngs: function () {
+               L.Polyline.prototype.projectLatlngs.call(this);
+
+               // project polygon holes points
+               // TODO move this logic to Polyline to get rid of duplication
+               this._holePoints = [];
+
+               if (!this._holes) {
+                       return;
+               }
+
+               for (var i = 0, len = this._holes.length, hole; i < len; i++) {
+                       this._holePoints[i] = [];
+
+                       for (var j = 0, len2 = this._holes[i].length; j < len2; j++) {
+                               this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
+                       }
+               }
+       },
+
+       _clipPoints: function () {
+               var points = this._originalPoints,
+                       newParts = [];
+
+               this._parts = [points].concat(this._holePoints);
+
+               if (this.options.noClip) {
+                       return;
+               }
+
+               for (var i = 0, len = this._parts.length; i < len; i++) {
+                       var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
+                       if (!clipped.length) {
+                               continue;
+                       }
+                       newParts.push(clipped);
+               }
+
+               this._parts = newParts;
+       },
+
+       _getPathPartStr: function (points) {
+               var str = L.Polyline.prototype._getPathPartStr.call(this, points);
+               return str + (L.Browser.svg ? 'z' : 'x');
+       }
+});
+
+L.polygon = function (latlngs, options) {
+       return new L.Polygon(latlngs, options);
+};
+
+
+/*
+ * Contains L.MultiPolyline and L.MultiPolygon layers.
+ */
+
+(function () {
+       function createMulti(Klass) {
+               return L.FeatureGroup.extend({
+                       initialize: function (latlngs, options) {
+                               this._layers = {};
+                               this._options = options;
+                               this.setLatLngs(latlngs);
+                       },
+
+                       setLatLngs: function (latlngs) {
+                               var i = 0, len = latlngs.length;
+
+                               this.eachLayer(function (layer) {
+                                       if (i < len) {
+                                               layer.setLatLngs(latlngs[i++]);
+                                       } else {
+                                               this.removeLayer(layer);
+                                       }
+                               }, this);
+
+                               while (i < len) {
+                                       this.addLayer(new Klass(latlngs[i++], this._options));
+                               }
+
+                               return this;
+                       }
+               });
+       }
+
+       L.MultiPolyline = createMulti(L.Polyline);
+       L.MultiPolygon = createMulti(L.Polygon);
+
+       L.multiPolyline = function (latlngs, options) {
+               return new L.MultiPolyline(latlngs, options);
+       };
+
+       L.multiPolygon = function (latlngs, options) {
+               return new L.MultiPolygon(latlngs, options);
+       };
+}());
+
+
+/*
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds
+ */
+
+L.Rectangle = L.Polygon.extend({
+       initialize: function (latLngBounds, options) {
+               L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
+       },
+
+       setBounds: function (latLngBounds) {
+               this.setLatLngs(this._boundsToLatLngs(latLngBounds));
+       },
+
+       _boundsToLatLngs: function (latLngBounds) {
+               latLngBounds = L.latLngBounds(latLngBounds);
+           return [
+               latLngBounds.getSouthWest(),
+               latLngBounds.getNorthWest(),
+               latLngBounds.getNorthEast(),
+               latLngBounds.getSouthEast(),
+               latLngBounds.getSouthWest()
+           ];
+       }
+});
+
+L.rectangle = function (latLngBounds, options) {
+       return new L.Rectangle(latLngBounds, options);
+};
+
+
+/*
+ * L.Circle is a circle overlay (with a certain radius in meters).
+ */
+
+L.Circle = L.Path.extend({
+       initialize: function (latlng, radius, options) {
+               L.Path.prototype.initialize.call(this, options);
+
+               this._latlng = L.latLng(latlng);
+               this._mRadius = radius;
+       },
+
+       options: {
+               fill: true
+       },
+
+       setLatLng: function (latlng) {
+               this._latlng = L.latLng(latlng);
+               return this.redraw();
+       },
+
+       setRadius: function (radius) {
+               this._mRadius = radius;
+               return this.redraw();
+       },
+
+       projectLatlngs: function () {
+               var lngRadius = this._getLngRadius(),
+                       latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
+                       point2 = this._map.latLngToLayerPoint(latlng2);
+
+               this._point = this._map.latLngToLayerPoint(this._latlng);
+               this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
+       },
+
+       getBounds: function () {
+               var lngRadius = this._getLngRadius(),
+                       latRadius = (this._mRadius / 40075017) * 360,
+                       latlng = this._latlng,
+                       sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
+                       ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
+
+               return new L.LatLngBounds(sw, ne);
+       },
+
+       getLatLng: function () {
+               return this._latlng;
+       },
+
+       getPathString: function () {
+               var p = this._point,
+                       r = this._radius;
+
+               if (this._checkIfEmpty()) {
+                       return '';
+               }
+
+               if (L.Browser.svg) {
+                       return "M" + p.x + "," + (p.y - r) +
+                                       "A" + r + "," + r + ",0,1,1," +
+                                       (p.x - 0.1) + "," + (p.y - r) + " z";
+               } else {
+                       p._round();
+                       r = Math.round(r);
+                       return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
+               }
+       },
+
+       getRadius: function () {
+               return this._mRadius;
+       },
+
+       // TODO Earth hardcoded, move into projection code!
+
+       _getLatRadius: function () {
+               return (this._mRadius / 40075017) * 360;
+       },
+
+       _getLngRadius: function () {
+               return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
+       },
+
+       _checkIfEmpty: function () {
+               if (!this._map) {
+                       return false;
+               }
+               var vp = this._map._pathViewport,
+                       r = this._radius,
+                       p = this._point;
+
+               return p.x - r > vp.max.x || p.y - r > vp.max.y ||
+                       p.x + r < vp.min.x || p.y + r < vp.min.y;
+       }
+});
+
+L.circle = function (latlng, radius, options) {
+       return new L.Circle(latlng, radius, options);
+};
+
+
+/*
+ * L.CircleMarker is a circle overlay with a permanent pixel radius.
+ */
+
+L.CircleMarker = L.Circle.extend({
+       options: {
+               radius: 10,
+               weight: 2
+       },
+
+       initialize: function (latlng, options) {
+               L.Circle.prototype.initialize.call(this, latlng, null, options);
+               this._radius = this.options.radius;
+       },
+
+       projectLatlngs: function () {
+               this._point = this._map.latLngToLayerPoint(this._latlng);
+       },
+
+       setRadius: function (radius) {
+               this._radius = radius;
+               return this.redraw();
+       }
+});
+
+L.circleMarker = function (latlng, options) {
+       return new L.CircleMarker(latlng, options);
 };
 
-/*\r
- * L.Mixin.Events adds custom events functionality to Leaflet classes\r
- */\r
-\r
-var key = '_leaflet_events';\r
-\r
-L.Mixin = {};\r
-\r
-L.Mixin.Events = {\r
-       \r
-       addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])\r
-               var events = this[key] = this[key] || {},\r
-                       type, i, len;\r
-               \r
-               // Types can be a map of types/handlers\r
-               if (typeof types === 'object') {\r
-                       for (type in types) {\r
-                               if (types.hasOwnProperty(type)) {\r
-                                       this.addEventListener(type, types[type], fn);\r
-                               }\r
-                       }\r
-                       \r
-                       return this;\r
-               }\r
-               \r
-               types = L.Util.splitWords(types);\r
-               \r
-               for (i = 0, len = types.length; i < len; i++) {\r
-                       events[types[i]] = events[types[i]] || [];\r
-                       events[types[i]].push({\r
-                               action: fn,\r
-                               context: context || this\r
-                       });\r
-               }\r
-               \r
-               return this;\r
-       },\r
-\r
-       hasEventListeners: function (type) { // (String) -> Boolean\r
-               return (key in this) && (type in this[key]) && (this[key][type].length > 0);\r
-       },\r
-\r
-       removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])\r
-               var events = this[key],\r
-                       type, i, len, listeners, j;\r
-               \r
-               if (typeof types === 'object') {\r
-                       for (type in types) {\r
-                               if (types.hasOwnProperty(type)) {\r
-                                       this.removeEventListener(type, types[type], fn);\r
-                               }\r
-                       }\r
-                       \r
-                       return this;\r
-               }\r
-               \r
-               types = L.Util.splitWords(types);\r
-\r
-               for (i = 0, len = types.length; i < len; i++) {\r
-\r
-                       if (this.hasEventListeners(types[i])) {\r
-                               listeners = events[types[i]];\r
-                               \r
-                               for (j = listeners.length - 1; j >= 0; j--) {\r
-                                       if (\r
-                                               (!fn || listeners[j].action === fn) &&\r
-                                               (!context || (listeners[j].context === context))\r
-                                       ) {\r
-                                               listeners.splice(j, 1);\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               return this;\r
-       },\r
-\r
-       fireEvent: function (type, data) { // (String[, Object])\r
-               if (!this.hasEventListeners(type)) {\r
-                       return this;\r
-               }\r
-\r
-               var event = L.Util.extend({\r
-                       type: type,\r
-                       target: this\r
-               }, data);\r
-\r
-               var listeners = this[key][type].slice();\r
-\r
-               for (var i = 0, len = listeners.length; i < len; i++) {\r
-                       listeners[i].action.call(listeners[i].context || this, event);\r
-               }\r
-\r
-               return this;\r
-       }\r
-};\r
-\r
-L.Mixin.Events.on = L.Mixin.Events.addEventListener;\r
-L.Mixin.Events.off = L.Mixin.Events.removeEventListener;\r
-L.Mixin.Events.fire = L.Mixin.Events.fireEvent;\r
-
-
-(function () {\r
-       var ua = navigator.userAgent.toLowerCase(),\r
-               ie = !!window.ActiveXObject,\r
-               ie6 = ie && !window.XMLHttpRequest,\r
-               webkit = ua.indexOf("webkit") !== -1,\r
-               gecko = ua.indexOf("gecko") !== -1,\r
-               //Terrible browser detection to work around a safari / iOS / android browser bug. See TileLayer._addTile and debug/hacks/jitter.html\r
-               chrome = ua.indexOf("chrome") !== -1,\r
-               opera = window.opera,\r
-               android = ua.indexOf("android") !== -1,\r
-               android23 = ua.search("android [23]") !== -1,\r
-               mobile = typeof orientation !== undefined + '' ? true : false,\r
-               doc = document.documentElement,\r
-               ie3d = ie && ('transition' in doc.style),\r
-               webkit3d = webkit && ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),\r
-               gecko3d = gecko && ('MozPerspective' in doc.style),\r
-               opera3d = opera && ('OTransition' in doc.style);\r
-\r
-       var touch = !window.L_NO_TOUCH && (function () {\r
-               var startName = 'ontouchstart';\r
-\r
-               // WebKit, etc\r
-               if (startName in doc) {\r
-                       return true;\r
-               }\r
-\r
-               // Firefox/Gecko\r
-               var div = document.createElement('div'),\r
-                       supported = false;\r
-\r
-               if (!div.setAttribute) {\r
-                       return false;\r
-               }\r
-               div.setAttribute(startName, 'return;');\r
-\r
-               if (typeof div[startName] === 'function') {\r
-                       supported = true;\r
-               }\r
-\r
-               div.removeAttribute(startName);\r
-               div = null;\r
-\r
-               return supported;\r
-       }());\r
-\r
-       var retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) || ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches));\r
-\r
-       L.Browser = {\r
-               ua: ua,\r
-               ie: ie,\r
-               ie6: ie6,\r
-               webkit: webkit,\r
-               gecko: gecko,\r
-               opera: opera,\r
-               android: android,\r
-               android23: android23,\r
-\r
-               chrome: chrome,\r
-\r
-               ie3d: ie3d,\r
-               webkit3d: webkit3d,\r
-               gecko3d: gecko3d,\r
-               opera3d: opera3d,\r
-               any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d),\r
-\r
-               mobile: mobile,\r
-               mobileWebkit: mobile && webkit,\r
-               mobileWebkit3d: mobile && webkit3d,\r
-               mobileOpera: mobile && opera,\r
-\r
-               touch: touch,\r
-\r
-               retina: retina\r
-       };\r
-}());\r
-
-
-/*\r
- * L.Point represents a point with x and y coordinates.\r
- */\r
-\r
-L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {\r
-       this.x = (round ? Math.round(x) : x);\r
-       this.y = (round ? Math.round(y) : y);\r
-};\r
-\r
-L.Point.prototype = {\r
-\r
-       clone: function () {\r
-               return new L.Point(this.x, this.y);\r
-       },\r
-\r
-       // non-destructive, returns a new point\r
-       add: function (point) {\r
-               return this.clone()._add(L.point(point));\r
-       },\r
-\r
-       // destructive, used directly for performance in situations where it's safe to modify existing point\r
-       _add: function (point) {\r
-               this.x += point.x;\r
-               this.y += point.y;\r
-               return this;\r
-       },\r
-\r
-       subtract: function (point) {\r
-               return this.clone()._subtract(L.point(point));\r
-       },\r
-\r
-       _subtract: function (point) {\r
-               this.x -= point.x;\r
-               this.y -= point.y;\r
-               return this;\r
-       },\r
-\r
-       divideBy: function (num) {\r
-               return this.clone()._divideBy(num);\r
-       },\r
-\r
-       _divideBy: function (num) {\r
-               this.x /= num;\r
-               this.y /= num;\r
-               return this;\r
-       },\r
-\r
-       multiplyBy: function (num) {\r
-               return this.clone()._multiplyBy(num);\r
-       },\r
-\r
-       _multiplyBy: function (num) {\r
-               this.x *= num;\r
-               this.y *= num;\r
-               return this;\r
-       },\r
-\r
-       round: function () {\r
-               return this.clone()._round();\r
-       },\r
-\r
-       _round: function () {\r
-               this.x = Math.round(this.x);\r
-               this.y = Math.round(this.y);\r
-               return this;\r
-       },\r
-\r
-       floor: function () {\r
-               return this.clone()._floor();\r
-       },\r
-\r
-       _floor: function () {\r
-               this.x = Math.floor(this.x);\r
-               this.y = Math.floor(this.y);\r
-               return this;\r
-       },\r
-\r
-       distanceTo: function (point) {\r
-               point = L.point(point);\r
-\r
-               var x = point.x - this.x,\r
-                       y = point.y - this.y;\r
-\r
-               return Math.sqrt(x * x + y * y);\r
-       },\r
-\r
-       toString: function () {\r
-               return 'Point(' +\r
-                               L.Util.formatNum(this.x) + ', ' +\r
-                               L.Util.formatNum(this.y) + ')';\r
-       }\r
-};\r
-\r
-L.point = function (x, y, round) {\r
-       if (x instanceof L.Point) {\r
-               return x;\r
-       }\r
-       if (x instanceof Array) {\r
-               return new L.Point(x[0], x[1]);\r
-       }\r
-       if (isNaN(x)) {\r
-               return x;\r
-       }\r
-       return new L.Point(x, y, round);\r
-};\r
-
-
-/*\r
- * L.Bounds represents a rectangular area on the screen in pixel coordinates.\r
- */\r
-\r
-L.Bounds = L.Class.extend({\r
-\r
-       initialize: function (a, b) {   //(Point, Point) or Point[]\r
-               if (!a) { return; }\r
-\r
-               var points = b ? [a, b] : a;\r
-\r
-               for (var i = 0, len = points.length; i < len; i++) {\r
-                       this.extend(points[i]);\r
-               }\r
-       },\r
-\r
-       // extend the bounds to contain the given point\r
-       extend: function (point) { // (Point)\r
-               point = L.point(point);\r
-\r
-               if (!this.min && !this.max) {\r
-                       this.min = point.clone();\r
-                       this.max = point.clone();\r
-               } else {\r
-                       this.min.x = Math.min(point.x, this.min.x);\r
-                       this.max.x = Math.max(point.x, this.max.x);\r
-                       this.min.y = Math.min(point.y, this.min.y);\r
-                       this.max.y = Math.max(point.y, this.max.y);\r
-               }\r
-               return this;\r
-       },\r
-\r
-       getCenter: function (round) { // (Boolean) -> Point\r
-               return new L.Point(\r
-                               (this.min.x + this.max.x) / 2,\r
-                               (this.min.y + this.max.y) / 2, round);\r
-       },\r
-\r
-       getBottomLeft: function () { // -> Point\r
-               return new L.Point(this.min.x, this.max.y);\r
-       },\r
-\r
-       getTopRight: function () { // -> Point\r
-               return new L.Point(this.max.x, this.min.y);\r
-       },\r
-\r
-       contains: function (obj) { // (Bounds) or (Point) -> Boolean\r
-               var min, max;\r
-\r
-               if (typeof obj[0] === 'number' || obj instanceof L.Point) {\r
-                       obj = L.point(obj);\r
-               } else {\r
-                       obj = L.bounds(obj);\r
-               }\r
-\r
-               if (obj instanceof L.Bounds) {\r
-                       min = obj.min;\r
-                       max = obj.max;\r
-               } else {\r
-                       min = max = obj;\r
-               }\r
-\r
-               return (min.x >= this.min.x) &&\r
-                               (max.x <= this.max.x) &&\r
-                               (min.y >= this.min.y) &&\r
-                               (max.y <= this.max.y);\r
-       },\r
-\r
-       intersects: function (bounds) { // (Bounds) -> Boolean\r
-               bounds = L.bounds(bounds);\r
-\r
-               var min = this.min,\r
-                       max = this.max,\r
-                       min2 = bounds.min,\r
-                       max2 = bounds.max;\r
-\r
-               var xIntersects = (max2.x >= min.x) && (min2.x <= max.x),\r
-                       yIntersects = (max2.y >= min.y) && (min2.y <= max.y);\r
-\r
-               return xIntersects && yIntersects;\r
-       },\r
-\r
-       isValid: function () {\r
-               return !!(this.min && this.max);\r
-       }\r
-});\r
-\r
-L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])\r
-       if (!a || a instanceof L.Bounds) {\r
-               return a;\r
-       }\r
-       return new L.Bounds(a, b);\r
-};\r
-
-
-/*\r
- * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.\r
- */\r
-\r
-L.Transformation = L.Class.extend({\r
-       initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {\r
-               this._a = a;\r
-               this._b = b;\r
-               this._c = c;\r
-               this._d = d;\r
-       },\r
-\r
-       transform: function (point, scale) {\r
-               return this._transform(point.clone(), scale);\r
-       },\r
-\r
-       // destructive transform (faster)\r
-       _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {\r
-               scale = scale || 1;\r
-               point.x = scale * (this._a * point.x + this._b);\r
-               point.y = scale * (this._c * point.y + this._d);\r
-               return point;\r
-       },\r
-\r
-       untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {\r
-               scale = scale || 1;\r
-               return new L.Point(\r
-                       (point.x / scale - this._b) / this._a,\r
-                       (point.y / scale - this._d) / this._c);\r
-       }\r
-});\r
-
-
-/*\r
- * L.DomUtil contains various utility functions for working with DOM.\r
- */\r
-\r
-L.DomUtil = {\r
-       get: function (id) {\r
-               return (typeof id === 'string' ? document.getElementById(id) : id);\r
-       },\r
-\r
-       getStyle: function (el, style) {\r
-\r
-               var value = el.style[style];\r
-\r
-               if (!value && el.currentStyle) {\r
-                       value = el.currentStyle[style];\r
-               }\r
-\r
-               if (!value || value === 'auto') {\r
-                       var css = document.defaultView.getComputedStyle(el, null);\r
-                       value = css ? css[style] : null;\r
-               }\r
-\r
-               return value === 'auto' ? null : value;\r
-       },\r
-\r
-       getViewportOffset: function (element) {\r
-\r
-               var top = 0,\r
-                       left = 0,\r
-                       el = element,\r
-                       docBody = document.body,\r
-                       pos;\r
-\r
-               do {\r
-                       top += el.offsetTop || 0;\r
-                       left += el.offsetLeft || 0;\r
-                       pos = L.DomUtil.getStyle(el, 'position');\r
-\r
-                       if (el.offsetParent === docBody && pos === 'absolute') { break; }\r
-\r
-                       if (pos === 'fixed') {\r
-                               top  += docBody.scrollTop  || 0;\r
-                               left += docBody.scrollLeft || 0;\r
-                               break;\r
-                       }\r
-                       el = el.offsetParent;\r
-\r
-               } while (el);\r
-\r
-               el = element;\r
-\r
-               do {\r
-                       if (el === docBody) { break; }\r
-\r
-                       top  -= el.scrollTop  || 0;\r
-                       left -= el.scrollLeft || 0;\r
-\r
-                       el = el.parentNode;\r
-               } while (el);\r
-\r
-               return new L.Point(left, top);\r
-       },\r
-\r
-       create: function (tagName, className, container) {\r
-\r
-               var el = document.createElement(tagName);\r
-               el.className = className;\r
-\r
-               if (container) {\r
-                       container.appendChild(el);\r
-               }\r
-\r
-               return el;\r
-       },\r
-\r
-       disableTextSelection: function () {\r
-               if (document.selection && document.selection.empty) {\r
-                       document.selection.empty();\r
-               }\r
-               if (!this._onselectstart) {\r
-                       this._onselectstart = document.onselectstart;\r
-                       document.onselectstart = L.Util.falseFn;\r
-               }\r
-       },\r
-\r
-       enableTextSelection: function () {\r
-               if (document.onselectstart === L.Util.falseFn) {\r
-                       document.onselectstart = this._onselectstart;\r
-                       this._onselectstart = null;\r
-               }\r
-       },\r
-\r
-       hasClass: function (el, name) {\r
-               return (el.className.length > 0) &&\r
-                               new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);\r
-       },\r
-\r
-       addClass: function (el, name) {\r
-               if (!L.DomUtil.hasClass(el, name)) {\r
-                       el.className += (el.className ? ' ' : '') + name;\r
-               }\r
-       },\r
-\r
-       removeClass: function (el, name) {\r
-\r
-               function replaceFn(w, match) {\r
-                       if (match === name) { return ''; }\r
-                       return w;\r
-               }\r
-\r
-               el.className = el.className\r
-                               .replace(/(\S+)\s*/g, replaceFn)\r
-                               .replace(/(^\s+|\s+$)/, '');\r
-       },\r
-\r
-       setOpacity: function (el, value) {\r
-\r
-               if ('opacity' in el.style) {\r
-                       el.style.opacity = value;\r
-\r
-               } else if (L.Browser.ie) {\r
-\r
-                       var filter = false,\r
-                               filterName = 'DXImageTransform.Microsoft.Alpha';\r
-\r
-                       // filters collection throws an error if we try to retrieve a filter that doesn't exist\r
-                       try { filter = el.filters.item(filterName); } catch (e) {}\r
-\r
-                       value = Math.round(value * 100);\r
-\r
-                       if (filter) {\r
-                               filter.Enabled = (value !== 100);\r
-                               filter.Opacity = value;\r
-                       } else {\r
-                               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';\r
-                       }\r
-               }\r
-       },\r
-\r
-       testProp: function (props) {\r
-\r
-               var style = document.documentElement.style;\r
-\r
-               for (var i = 0; i < props.length; i++) {\r
-                       if (props[i] in style) {\r
-                               return props[i];\r
-                       }\r
-               }\r
-               return false;\r
-       },\r
-\r
-       getTranslateString: function (point) {\r
-               // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate\r
-               // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care\r
-               // (same speed either way), Opera 12 doesn't support translate3d\r
-\r
-               var is3d = L.Browser.webkit3d,\r
-                       open = 'translate' + (is3d ? '3d' : '') + '(',\r
-                       close = (is3d ? ',0' : '') + ')';\r
-\r
-               return open + point.x + 'px,' + point.y + 'px' + close;\r
-       },\r
-\r
-       getScaleString: function (scale, origin) {\r
-\r
-               var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),\r
-                   scaleStr = ' scale(' + scale + ') ';\r
-\r
-               return preTranslateStr + scaleStr;\r
-       },\r
-\r
-       setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])\r
-\r
-               el._leaflet_pos = point;\r
-\r
-               if (!disable3D && L.Browser.any3d) {\r
-                       el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);\r
-\r
-                       // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)\r
-                       if (L.Browser.mobileWebkit3d) {\r
-                               el.style.WebkitBackfaceVisibility = 'hidden';\r
-                       }\r
-               } else {\r
-                       el.style.left = point.x + 'px';\r
-                       el.style.top = point.y + 'px';\r
-               }\r
-       },\r
-\r
-       getPosition: function (el) {\r
-               // this method is only used for elements previously positioned using setPosition,\r
-               // so it's safe to cache the position for performance\r
-               return el._leaflet_pos;\r
-       }\r
-};\r
-\r
-\r
-// prefix style property names\r
-\r
-L.DomUtil.TRANSFORM = L.DomUtil.testProp(\r
-               ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);\r
-\r
-L.DomUtil.TRANSITION = L.DomUtil.testProp(\r
-               ['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']);\r
-\r
-L.DomUtil.TRANSITION_END =\r
-               L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?\r
-               L.DomUtil.TRANSITION + 'End' : 'transitionend';\r
-
-
-/*\r
-       CM.LatLng represents a geographical point with latitude and longtitude coordinates.\r
-*/\r
-\r
-L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])\r
-       var lat = parseFloat(rawLat),\r
-               lng = parseFloat(rawLng);\r
-\r
-       if (isNaN(lat) || isNaN(lng)) {\r
-               throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');\r
-       }\r
-\r
-       if (noWrap !== true) {\r
-               lat = Math.max(Math.min(lat, 90), -90);                                 // clamp latitude into -90..90\r
-               lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180);   // wrap longtitude into -180..180\r
-       }\r
-\r
-       this.lat = lat;\r
-       this.lng = lng;\r
-};\r
-\r
-L.Util.extend(L.LatLng, {\r
-       DEG_TO_RAD: Math.PI / 180,\r
-       RAD_TO_DEG: 180 / Math.PI,\r
-       MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check\r
-});\r
-\r
-L.LatLng.prototype = {\r
-       equals: function (obj) { // (LatLng) -> Boolean\r
-               if (!obj) { return false; }\r
-\r
-               obj = L.latLng(obj);\r
-\r
-               var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));\r
-               return margin <= L.LatLng.MAX_MARGIN;\r
-       },\r
-\r
-       toString: function () { // -> String\r
-               return 'LatLng(' +\r
-                               L.Util.formatNum(this.lat) + ', ' +\r
-                               L.Util.formatNum(this.lng) + ')';\r
-       },\r
-\r
-       // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula\r
-       distanceTo: function (other) { // (LatLng) -> Number\r
-               other = L.latLng(other);\r
-\r
-               var R = 6378137, // earth radius in meters\r
-                       d2r = L.LatLng.DEG_TO_RAD,\r
-                       dLat = (other.lat - this.lat) * d2r,\r
-                       dLon = (other.lng - this.lng) * d2r,\r
-                       lat1 = this.lat * d2r,\r
-                       lat2 = other.lat * d2r,\r
-                       sin1 = Math.sin(dLat / 2),\r
-                       sin2 = Math.sin(dLon / 2);\r
-\r
-               var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);\r
-\r
-               return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\r
-       }\r
-};\r
-\r
-L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)\r
-       if (a instanceof L.LatLng) {\r
-               return a;\r
-       }\r
-       if (a instanceof Array) {\r
-               return new L.LatLng(a[0], a[1]);\r
-       }\r
-       if (isNaN(a)) {\r
-               return a;\r
-       }\r
-       return new L.LatLng(a, b, c);\r
-};\r
-
-/*\r
- * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.\r
- */\r
-\r
-L.LatLngBounds = L.Class.extend({\r
-       initialize: function (southWest, northEast) {   // (LatLng, LatLng) or (LatLng[])\r
-               if (!southWest) { return; }\r
-\r
-               var latlngs = northEast ? [southWest, northEast] : southWest;\r
-\r
-               for (var i = 0, len = latlngs.length; i < len; i++) {\r
-                       this.extend(latlngs[i]);\r
-               }\r
-       },\r
-\r
-       // extend the bounds to contain the given point or bounds\r
-       extend: function (obj) { // (LatLng) or (LatLngBounds)\r
-               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {\r
-                       obj = L.latLng(obj);\r
-               } else {\r
-                       obj = L.latLngBounds(obj);\r
-               }\r
-\r
-               if (obj instanceof L.LatLng) {\r
-                       if (!this._southWest && !this._northEast) {\r
-                               this._southWest = new L.LatLng(obj.lat, obj.lng, true);\r
-                               this._northEast = new L.LatLng(obj.lat, obj.lng, true);\r
-                       } else {\r
-                               this._southWest.lat = Math.min(obj.lat, this._southWest.lat);\r
-                               this._southWest.lng = Math.min(obj.lng, this._southWest.lng);\r
-\r
-                               this._northEast.lat = Math.max(obj.lat, this._northEast.lat);\r
-                               this._northEast.lng = Math.max(obj.lng, this._northEast.lng);\r
-                       }\r
-               } else if (obj instanceof L.LatLngBounds) {\r
-                       this.extend(obj._southWest);\r
-            this.extend(obj._northEast);\r
-               }\r
-               return this;\r
-       },\r
-\r
-       // extend the bounds by a percentage\r
-       pad: function (bufferRatio) { // (Number) -> LatLngBounds\r
-               var sw = this._southWest,\r
-                       ne = this._northEast,\r
-                       heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,\r
-                       widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;\r
-\r
-               return new L.LatLngBounds(\r
-                       new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),\r
-                       new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));\r
-       },\r
-\r
-       getCenter: function () { // -> LatLng\r
-               return new L.LatLng(\r
-                               (this._southWest.lat + this._northEast.lat) / 2,\r
-                               (this._southWest.lng + this._northEast.lng) / 2);\r
-       },\r
-\r
-       getSouthWest: function () {\r
-               return this._southWest;\r
-       },\r
-\r
-       getNorthEast: function () {\r
-               return this._northEast;\r
-       },\r
-\r
-       getNorthWest: function () {\r
-               return new L.LatLng(this._northEast.lat, this._southWest.lng, true);\r
-       },\r
-\r
-       getSouthEast: function () {\r
-               return new L.LatLng(this._southWest.lat, this._northEast.lng, true);\r
-       },\r
-\r
-       contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean\r
-               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {\r
-                       obj = L.latLng(obj);\r
-               } else {\r
-                       obj = L.latLngBounds(obj);\r
-               }\r
-\r
-               var sw = this._southWest,\r
-                       ne = this._northEast,\r
-                       sw2, ne2;\r
-\r
-               if (obj instanceof L.LatLngBounds) {\r
-                       sw2 = obj.getSouthWest();\r
-                       ne2 = obj.getNorthEast();\r
-               } else {\r
-                       sw2 = ne2 = obj;\r
-               }\r
-\r
-               return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&\r
-                               (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);\r
-       },\r
-\r
-       intersects: function (bounds) { // (LatLngBounds)\r
-               bounds = L.latLngBounds(bounds);\r
-\r
-               var sw = this._southWest,\r
-                       ne = this._northEast,\r
-                       sw2 = bounds.getSouthWest(),\r
-                       ne2 = bounds.getNorthEast();\r
-\r
-               var latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),\r
-                       lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);\r
-\r
-               return latIntersects && lngIntersects;\r
-       },\r
-\r
-       toBBoxString: function () {\r
-               var sw = this._southWest,\r
-                       ne = this._northEast;\r
-               return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');\r
-       },\r
-\r
-       equals: function (bounds) { // (LatLngBounds)\r
-               if (!bounds) { return false; }\r
-\r
-               bounds = L.latLngBounds(bounds);\r
-\r
-               return this._southWest.equals(bounds.getSouthWest()) &&\r
-                      this._northEast.equals(bounds.getNorthEast());\r
-       },\r
-\r
-       isValid: function () {\r
-               return !!(this._southWest && this._northEast);\r
-       }\r
-});\r
-\r
-//TODO International date line?\r
-\r
-L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)\r
-       if (!a || a instanceof L.LatLngBounds) {\r
-               return a;\r
-       }\r
-       return new L.LatLngBounds(a, b);\r
-};\r
-
-
-/*\r
- * L.Projection contains various geographical projections used by CRS classes.\r
- */\r
-\r
-L.Projection = {};\r
-
-
-\r
-L.Projection.SphericalMercator = {\r
-       MAX_LATITUDE: 85.0511287798,\r
-\r
-       project: function (latlng) { // (LatLng) -> Point\r
-               var d = L.LatLng.DEG_TO_RAD,\r
-                       max = this.MAX_LATITUDE,\r
-                       lat = Math.max(Math.min(max, latlng.lat), -max),\r
-                       x = latlng.lng * d,\r
-                       y = lat * d;\r
-               y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));\r
-\r
-               return new L.Point(x, y);\r
-       },\r
-\r
-       unproject: function (point) { // (Point, Boolean) -> LatLng\r
-               var d = L.LatLng.RAD_TO_DEG,\r
-                       lng = point.x * d,\r
-                       lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;\r
-\r
-               // TODO refactor LatLng wrapping\r
-               return new L.LatLng(lat, lng, true);\r
-       }\r
-};\r
-
-
-\r
-L.Projection.LonLat = {\r
-       project: function (latlng) {\r
-               return new L.Point(latlng.lng, latlng.lat);\r
-       },\r
-\r
-       unproject: function (point) {\r
-               return new L.LatLng(point.y, point.x, true);\r
-       }\r
-};\r
-
-
-\r
-L.CRS = {\r
-       latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point\r
-               var projectedPoint = this.projection.project(latlng),\r
-                   scale = this.scale(zoom);\r
-\r
-               return this.transformation._transform(projectedPoint, scale);\r
-       },\r
-\r
-       pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng\r
-               var scale = this.scale(zoom),\r
-                   untransformedPoint = this.transformation.untransform(point, scale);\r
-\r
-               return this.projection.unproject(untransformedPoint);\r
-       },\r
-\r
-       project: function (latlng) {\r
-               return this.projection.project(latlng);\r
-       },\r
-\r
-       scale: function (zoom) {\r
-               return 256 * Math.pow(2, zoom);\r
-       }\r
-};\r
-
-
-\r
-L.CRS.EPSG3857 = L.Util.extend({}, L.CRS, {\r
-       code: 'EPSG:3857',\r
-\r
-       projection: L.Projection.SphericalMercator,\r
-       transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),\r
-\r
-       project: function (latlng) { // (LatLng) -> Point\r
-               var projectedPoint = this.projection.project(latlng),\r
-                       earthRadius = 6378137;\r
-               return projectedPoint.multiplyBy(earthRadius);\r
-       }\r
-});\r
-\r
-L.CRS.EPSG900913 = L.Util.extend({}, L.CRS.EPSG3857, {\r
-       code: 'EPSG:900913'\r
-});\r
-
-
-\r
-L.CRS.EPSG4326 = L.Util.extend({}, L.CRS, {\r
-       code: 'EPSG:4326',\r
-\r
-       projection: L.Projection.LonLat,\r
-       transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)\r
-});\r
-
-
-/*\r
- * L.Map is the central class of the API - it is used to create a map.\r
- */\r
-\r
-L.Map = L.Class.extend({\r
-\r
-       includes: L.Mixin.Events,\r
-\r
-       options: {\r
-               crs: L.CRS.EPSG3857,\r
-\r
-               /*\r
-               center: LatLng,\r
-               zoom: Number,\r
-               layers: Array,\r
-               */\r
-\r
-               fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,\r
-               trackResize: true,\r
-               markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d\r
-       },\r
-\r
-       initialize: function (id, options) { // (HTMLElement or String, Object)\r
-               options = L.Util.setOptions(this, options);\r
-\r
-               this._initContainer(id);\r
-               this._initLayout();\r
-               this._initHooks();\r
-               this._initEvents();\r
-\r
-               if (options.maxBounds) {\r
-                       this.setMaxBounds(options.maxBounds);\r
-               }\r
-\r
-               if (options.center && options.zoom !== undefined) {\r
-                       this.setView(L.latLng(options.center), options.zoom, true);\r
-               }\r
-\r
-               this._initLayers(options.layers);\r
-       },\r
-\r
-\r
-       // public methods that modify map state\r
-\r
-       // replaced by animation-powered implementation in Map.PanAnimation.js\r
-       setView: function (center, zoom) {\r
-               this._resetView(L.latLng(center), this._limitZoom(zoom));\r
-               return this;\r
-       },\r
-\r
-       setZoom: function (zoom) { // (Number)\r
-               return this.setView(this.getCenter(), zoom);\r
-       },\r
-\r
-       zoomIn: function (delta) {\r
-               return this.setZoom(this._zoom + (delta || 1));\r
-       },\r
-\r
-       zoomOut: function (delta) {\r
-               return this.setZoom(this._zoom - (delta || 1));\r
-       },\r
-\r
-       fitBounds: function (bounds) { // (LatLngBounds)\r
-               var zoom = this.getBoundsZoom(bounds);\r
-               return this.setView(L.latLngBounds(bounds).getCenter(), zoom);\r
-       },\r
-\r
-       fitWorld: function () {\r
-               var sw = new L.LatLng(-60, -170),\r
-                   ne = new L.LatLng(85, 179);\r
-\r
-               return this.fitBounds(new L.LatLngBounds(sw, ne));\r
-       },\r
-\r
-       panTo: function (center) { // (LatLng)\r
-               return this.setView(center, this._zoom);\r
-       },\r
-\r
-       panBy: function (offset) { // (Point)\r
-               // replaced with animated panBy in Map.Animation.js\r
-               this.fire('movestart');\r
-\r
-               this._rawPanBy(L.point(offset));\r
-\r
-               this.fire('move');\r
-               return this.fire('moveend');\r
-       },\r
-\r
-       setMaxBounds: function (bounds) {\r
-               bounds = L.latLngBounds(bounds);\r
-\r
-               this.options.maxBounds = bounds;\r
-\r
-               if (!bounds) {\r
-                       this._boundsMinZoom = null;\r
-                       return this;\r
-               }\r
-\r
-               var minZoom = this.getBoundsZoom(bounds, true);\r
-\r
-               this._boundsMinZoom = minZoom;\r
-\r
-               if (this._loaded) {\r
-                       if (this._zoom < minZoom) {\r
-                               this.setView(bounds.getCenter(), minZoom);\r
-                       } else {\r
-                               this.panInsideBounds(bounds);\r
-                       }\r
-               }\r
-\r
-               return this;\r
-       },\r
-\r
-       panInsideBounds: function (bounds) {\r
-               bounds = L.latLngBounds(bounds);\r
-\r
-               var viewBounds = this.getBounds(),\r
-                   viewSw = this.project(viewBounds.getSouthWest()),\r
-                   viewNe = this.project(viewBounds.getNorthEast()),\r
-                   sw = this.project(bounds.getSouthWest()),\r
-                   ne = this.project(bounds.getNorthEast()),\r
-                   dx = 0,\r
-                   dy = 0;\r
-\r
-               if (viewNe.y < ne.y) { // north\r
-                       dy = ne.y - viewNe.y;\r
-               }\r
-               if (viewNe.x > ne.x) { // east\r
-                       dx = ne.x - viewNe.x;\r
-               }\r
-               if (viewSw.y > sw.y) { // south\r
-                       dy = sw.y - viewSw.y;\r
-               }\r
-               if (viewSw.x < sw.x) { // west\r
-                       dx = sw.x - viewSw.x;\r
-               }\r
-\r
-               return this.panBy(new L.Point(dx, dy, true));\r
-       },\r
-\r
-       addLayer: function (layer) {\r
-               // TODO method is too big, refactor\r
-\r
-               var id = L.Util.stamp(layer);\r
-\r
-               if (this._layers[id]) { return this; }\r
-\r
-               this._layers[id] = layer;\r
-\r
-               // TODO getMaxZoom, getMinZoom in ILayer (instead of options)\r
-               if (layer.options && !isNaN(layer.options.maxZoom)) {\r
-                       this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);\r
-               }\r
-               if (layer.options && !isNaN(layer.options.minZoom)) {\r
-                       this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);\r
-               }\r
-\r
-               // TODO looks ugly, refactor!!!\r
-               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {\r
-                       this._tileLayersNum++;\r
-            this._tileLayersToLoad++;\r
-            layer.on('load', this._onTileLayerLoad, this);\r
-               }\r
-\r
-               this.whenReady(function () {\r
-                       layer.onAdd(this);\r
-                       this.fire('layeradd', {layer: layer});\r
-               }, this);\r
-\r
-               return this;\r
-       },\r
-\r
-       removeLayer: function (layer) {\r
-               var id = L.Util.stamp(layer);\r
-\r
-               if (!this._layers[id]) { return; }\r
-\r
-               layer.onRemove(this);\r
-\r
-               delete this._layers[id];\r
-\r
-               // TODO looks ugly, refactor\r
-               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {\r
-                       this._tileLayersNum--;\r
-            this._tileLayersToLoad--;\r
-            layer.off('load', this._onTileLayerLoad, this);\r
-               }\r
-\r
-               return this.fire('layerremove', {layer: layer});\r
-       },\r
-\r
-       hasLayer: function (layer) {\r
-               var id = L.Util.stamp(layer);\r
-               return this._layers.hasOwnProperty(id);\r
-       },\r
-\r
-       invalidateSize: function (animate) {\r
-               var oldSize = this.getSize();\r
-\r
-               this._sizeChanged = true;\r
-\r
-               if (this.options.maxBounds) {\r
-                       this.setMaxBounds(this.options.maxBounds);\r
-               }\r
-\r
-               if (!this._loaded) { return this; }\r
-\r
-               var offset = oldSize._subtract(this.getSize())._divideBy(2)._round();\r
-\r
-               if (animate === true) {\r
-                       this.panBy(offset);\r
-               } else {\r
-                       this._rawPanBy(offset);\r
-\r
-                       this.fire('move');\r
-\r
-                       clearTimeout(this._sizeTimer);\r
-                       this._sizeTimer = setTimeout(L.Util.bind(this.fire, this, 'moveend'), 200);\r
-               }\r
-               return this;\r
-       },\r
-\r
-       // TODO handler.addTo\r
-       addHandler: function (name, HandlerClass) {\r
-               if (!HandlerClass) { return; }\r
-\r
-               this[name] = new HandlerClass(this);\r
-\r
-               if (this.options[name]) {\r
-                       this[name].enable();\r
-               }\r
-\r
-               return this;\r
-       },\r
-\r
-\r
-       // public methods for getting map state\r
-\r
-       getCenter: function () { // (Boolean) -> LatLng\r
-               return this.layerPointToLatLng(this._getCenterLayerPoint());\r
-       },\r
-\r
-       getZoom: function () {\r
-               return this._zoom;\r
-       },\r
-\r
-       getBounds: function () {\r
-               var bounds = this.getPixelBounds(),\r
-                   sw = this.unproject(bounds.getBottomLeft()),\r
-                   ne = this.unproject(bounds.getTopRight());\r
-\r
-               return new L.LatLngBounds(sw, ne);\r
-       },\r
-\r
-       getMinZoom: function () {\r
-               var z1 = this.options.minZoom || 0,\r
-                   z2 = this._layersMinZoom || 0,\r
-                   z3 = this._boundsMinZoom || 0;\r
-\r
-               return Math.max(z1, z2, z3);\r
-       },\r
-\r
-       getMaxZoom: function () {\r
-               var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,\r
-                   z2 = this._layersMaxZoom  === undefined ? Infinity : this._layersMaxZoom;\r
-\r
-               return Math.min(z1, z2);\r
-       },\r
-\r
-       getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number\r
-               bounds = L.latLngBounds(bounds);\r
-\r
-               var size = this.getSize(),\r
-                   zoom = this.options.minZoom || 0,\r
-                   maxZoom = this.getMaxZoom(),\r
-                   ne = bounds.getNorthEast(),\r
-                   sw = bounds.getSouthWest(),\r
-                   boundsSize,\r
-                   nePoint,\r
-                   swPoint,\r
-                   zoomNotFound = true;\r
-\r
-               if (inside) {\r
-                       zoom--;\r
-               }\r
-\r
-               do {\r
-                       zoom++;\r
-                       nePoint = this.project(ne, zoom);\r
-                       swPoint = this.project(sw, zoom);\r
-                       boundsSize = new L.Point(Math.abs(nePoint.x - swPoint.x), Math.abs(swPoint.y - nePoint.y));\r
-\r
-                       if (!inside) {\r
-                               zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;\r
-                       } else {\r
-                               zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;\r
-                       }\r
-               } while (zoomNotFound && zoom <= maxZoom);\r
-\r
-               if (zoomNotFound && inside) {\r
-                       return null;\r
-               }\r
-\r
-               return inside ? zoom : zoom - 1;\r
-       },\r
-\r
-       getSize: function () {\r
-               if (!this._size || this._sizeChanged) {\r
-                       this._size = new L.Point(\r
-                               this._container.clientWidth,\r
-                               this._container.clientHeight);\r
-\r
-                       this._sizeChanged = false;\r
-               }\r
-               return this._size.clone();\r
-       },\r
-\r
-       getPixelBounds: function () {\r
-               var topLeftPoint = this._getTopLeftPoint();\r
-               return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));\r
-       },\r
-\r
-       getPixelOrigin: function () {\r
-               return this._initialTopLeftPoint;\r
-       },\r
-\r
-       getPanes: function () {\r
-               return this._panes;\r
-       },\r
-\r
-       getContainer: function () {\r
-               return this._container;\r
-       },\r
-\r
-\r
-       // TODO replace with universal implementation after refactoring projections\r
-\r
-       getZoomScale: function (toZoom) {\r
-               var crs = this.options.crs;\r
-               return crs.scale(toZoom) / crs.scale(this._zoom);\r
-       },\r
-\r
-       getScaleZoom: function (scale) {\r
-               return this._zoom + (Math.log(scale) / Math.LN2);\r
-       },\r
-\r
-\r
-       // conversion methods\r
-\r
-       project: function (latlng, zoom) { // (LatLng[, Number]) -> Point\r
-               zoom = zoom === undefined ? this._zoom : zoom;\r
-               return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);\r
-       },\r
-\r
-       unproject: function (point, zoom) { // (Point[, Number]) -> LatLng\r
-               zoom = zoom === undefined ? this._zoom : zoom;\r
-               return this.options.crs.pointToLatLng(L.point(point), zoom);\r
-       },\r
-\r
-       layerPointToLatLng: function (point) { // (Point)\r
-               var projectedPoint = L.point(point).add(this._initialTopLeftPoint);\r
-               return this.unproject(projectedPoint);\r
-       },\r
-\r
-       latLngToLayerPoint: function (latlng) { // (LatLng)\r
-               var projectedPoint = this.project(L.latLng(latlng))._round();\r
-               return projectedPoint._subtract(this._initialTopLeftPoint);\r
-       },\r
-\r
-       containerPointToLayerPoint: function (point) { // (Point)\r
-               return L.point(point).subtract(this._getMapPanePos());\r
-       },\r
-\r
-       layerPointToContainerPoint: function (point) { // (Point)\r
-               return L.point(point).add(this._getMapPanePos());\r
-       },\r
-\r
-       containerPointToLatLng: function (point) {\r
-               var layerPoint = this.containerPointToLayerPoint(L.point(point));\r
-               return this.layerPointToLatLng(layerPoint);\r
-       },\r
-\r
-       latLngToContainerPoint: function (latlng) {\r
-               return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));\r
-       },\r
-\r
-       mouseEventToContainerPoint: function (e) { // (MouseEvent)\r
-               return L.DomEvent.getMousePosition(e, this._container);\r
-       },\r
-\r
-       mouseEventToLayerPoint: function (e) { // (MouseEvent)\r
-               return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));\r
-       },\r
-\r
-       mouseEventToLatLng: function (e) { // (MouseEvent)\r
-               return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));\r
-       },\r
-\r
-\r
-       // map initialization methods\r
-\r
-       _initContainer: function (id) {\r
-               var container = this._container = L.DomUtil.get(id);\r
-\r
-               if (container._leaflet) {\r
-                       throw new Error("Map container is already initialized.");\r
-               }\r
-\r
-               container._leaflet = true;\r
-       },\r
-\r
-       _initLayout: function () {\r
-               var container = this._container;\r
-\r
-               container.innerHTML = '';\r
-               L.DomUtil.addClass(container, 'leaflet-container');\r
-\r
-               if (L.Browser.touch) {\r
-                       L.DomUtil.addClass(container, 'leaflet-touch');\r
-               }\r
-\r
-               if (this.options.fadeAnimation) {\r
-                       L.DomUtil.addClass(container, 'leaflet-fade-anim');\r
-               }\r
-\r
-               var position = L.DomUtil.getStyle(container, 'position');\r
-\r
-               if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {\r
-                       container.style.position = 'relative';\r
-               }\r
-\r
-               this._initPanes();\r
-\r
-               if (this._initControlPos) {\r
-                       this._initControlPos();\r
-               }\r
-       },\r
-\r
-       _initPanes: function () {\r
-               var panes = this._panes = {};\r
-\r
-               this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);\r
-\r
-               this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);\r
-               this._objectsPane = panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);\r
-\r
-               panes.shadowPane = this._createPane('leaflet-shadow-pane');\r
-               panes.overlayPane = this._createPane('leaflet-overlay-pane');\r
-               panes.markerPane = this._createPane('leaflet-marker-pane');\r
-               panes.popupPane = this._createPane('leaflet-popup-pane');\r
-\r
-               var zoomHide = ' leaflet-zoom-hide';\r
-\r
-               if (!this.options.markerZoomAnimation) {\r
-                       L.DomUtil.addClass(panes.markerPane, zoomHide);\r
-                       L.DomUtil.addClass(panes.shadowPane, zoomHide);\r
-                       L.DomUtil.addClass(panes.popupPane, zoomHide);\r
-               }\r
-       },\r
-\r
-       _createPane: function (className, container) {\r
-               return L.DomUtil.create('div', className, container || this._objectsPane);\r
-       },\r
-\r
-       _initializers: [],\r
-\r
-       _initHooks: function () {\r
-               var i, len;\r
-               for (i = 0, len = this._initializers.length; i < len; i++) {\r
-                       this._initializers[i].call(this);\r
-               }\r
-       },\r
-\r
-       _initLayers: function (layers) {\r
-               layers = layers ? (layers instanceof Array ? layers : [layers]) : [];\r
-\r
-               this._layers = {};\r
-               this._tileLayersNum = 0;\r
-\r
-               var i, len;\r
-\r
-               for (i = 0, len = layers.length; i < len; i++) {\r
-                       this.addLayer(layers[i]);\r
-               }\r
-       },\r
-\r
-\r
-       // private methods that modify map state\r
-\r
-       _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {\r
-\r
-               var zoomChanged = (this._zoom !== zoom);\r
-\r
-               if (!afterZoomAnim) {\r
-                       this.fire('movestart');\r
-\r
-                       if (zoomChanged) {\r
-                               this.fire('zoomstart');\r
-                       }\r
-               }\r
-\r
-               this._zoom = zoom;\r
-\r
-               this._initialTopLeftPoint = this._getNewTopLeftPoint(center);\r
-\r
-               if (!preserveMapOffset) {\r
-                       L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));\r
-               } else {\r
-                       this._initialTopLeftPoint._add(this._getMapPanePos());\r
-               }\r
-\r
-               this._tileLayersToLoad = this._tileLayersNum;\r
-\r
-               var loading = !this._loaded;\r
-               this._loaded = true;\r
-\r
-               this.fire('viewreset', {hard: !preserveMapOffset});\r
-\r
-               this.fire('move');\r
-\r
-               if (zoomChanged || afterZoomAnim) {\r
-                       this.fire('zoomend');\r
-               }\r
-\r
-               this.fire('moveend', {hard: !preserveMapOffset});\r
-\r
-               if (loading) {\r
-                       this.fire('load');\r
-               }\r
-       },\r
-\r
-       _rawPanBy: function (offset) {\r
-               L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));\r
-       },\r
-\r
-\r
-       // map events\r
-\r
-       _initEvents: function () {\r
-               if (!L.DomEvent) { return; }\r
-\r
-               L.DomEvent.on(this._container, 'click', this._onMouseClick, this);\r
-\r
-               var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'],\r
-                       i, len;\r
-\r
-               for (i = 0, len = events.length; i < len; i++) {\r
-                       L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);\r
-               }\r
-\r
-               if (this.options.trackResize) {\r
-                       L.DomEvent.on(window, 'resize', this._onResize, this);\r
-               }\r
-       },\r
-\r
-       _onResize: function () {\r
-               L.Util.cancelAnimFrame(this._resizeRequest);\r
-               this._resizeRequest = L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);\r
-       },\r
-\r
-       _onMouseClick: function (e) {\r
-               if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }\r
-\r
-               this.fire('preclick');\r
-               this._fireMouseEvent(e);\r
-       },\r
-\r
-       _fireMouseEvent: function (e) {\r
-               if (!this._loaded) { return; }\r
-\r
-               var type = e.type;\r
-\r
-               type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));\r
-\r
-               if (!this.hasEventListeners(type)) { return; }\r
-\r
-               if (type === 'contextmenu') {\r
-                       L.DomEvent.preventDefault(e);\r
-               }\r
-\r
-               var containerPoint = this.mouseEventToContainerPoint(e),\r
-                       layerPoint = this.containerPointToLayerPoint(containerPoint),\r
-                       latlng = this.layerPointToLatLng(layerPoint);\r
-\r
-               this.fire(type, {\r
-                       latlng: latlng,\r
-                       layerPoint: layerPoint,\r
-                       containerPoint: containerPoint,\r
-                       originalEvent: e\r
-               });\r
-       },\r
-\r
-       _onTileLayerLoad: function () {\r
-               // TODO super-ugly, refactor!!!\r
-               // clear scaled tiles after all new tiles are loaded (for performance)\r
-               this._tileLayersToLoad--;\r
-               if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {\r
-                       clearTimeout(this._clearTileBgTimer);\r
-                       this._clearTileBgTimer = setTimeout(L.Util.bind(this._clearTileBg, this), 500);\r
-               }\r
-       },\r
-\r
-       whenReady: function (callback, context) {\r
-               if (this._loaded) {\r
-                       callback.call(context || this, this);\r
-               } else {\r
-                       this.on('load', callback, context);\r
-               }\r
-               return this;\r
-       },\r
-\r
-\r
-       // private methods for getting map state\r
-\r
-       _getMapPanePos: function () {\r
-               return L.DomUtil.getPosition(this._mapPane);\r
-       },\r
-\r
-       _getTopLeftPoint: function () {\r
-               if (!this._loaded) {\r
-                       throw new Error('Set map center and zoom first.');\r
-               }\r
-\r
-               return this._initialTopLeftPoint.subtract(this._getMapPanePos());\r
-       },\r
-\r
-       _getNewTopLeftPoint: function (center, zoom) {\r
-               var viewHalf = this.getSize()._divideBy(2);\r
-               // TODO round on display, not calculation to increase precision?\r
-               return this.project(center, zoom)._subtract(viewHalf)._round();\r
-       },\r
-\r
-       _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {\r
-               var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());\r
-               return this.project(latlng, newZoom)._subtract(topLeft);\r
-       },\r
-\r
-       _getCenterLayerPoint: function () {\r
-               return this.containerPointToLayerPoint(this.getSize()._divideBy(2));\r
-       },\r
-\r
-       _getCenterOffset: function (center) {\r
-               return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());\r
-       },\r
-\r
-       _limitZoom: function (zoom) {\r
-               var min = this.getMinZoom(),\r
-                       max = this.getMaxZoom();\r
-\r
-               return Math.max(min, Math.min(max, zoom));\r
-       }\r
-});\r
-\r
-L.Map.addInitHook = function (fn) {\r
-       var args = Array.prototype.slice.call(arguments, 1);\r
-\r
-       var init = typeof fn === 'function' ? fn : function () {\r
-               this[fn].apply(this, args);\r
-       };\r
-\r
-       this.prototype._initializers.push(init);\r
-};\r
-\r
-L.map = function (id, options) {\r
-       return new L.Map(id, options);\r
-};\r
-
-
-\r
-L.Projection.Mercator = {\r
-       MAX_LATITUDE: 85.0840591556,\r
-\r
-       R_MINOR: 6356752.3142,\r
-       R_MAJOR: 6378137,\r
-\r
-       project: function (latlng) { // (LatLng) -> Point\r
-               var d = L.LatLng.DEG_TO_RAD,\r
-                       max = this.MAX_LATITUDE,\r
-                       lat = Math.max(Math.min(max, latlng.lat), -max),\r
-                       r = this.R_MAJOR,\r
-                       r2 = this.R_MINOR,\r
-                       x = latlng.lng * d * r,\r
-                       y = lat * d,\r
-                       tmp = r2 / r,\r
-                       eccent = Math.sqrt(1.0 - tmp * tmp),\r
-                       con = eccent * Math.sin(y);\r
-\r
-               con = Math.pow((1 - con) / (1 + con), eccent * 0.5);\r
-\r
-               var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;\r
-               y = -r2 * Math.log(ts);\r
-\r
-               return new L.Point(x, y);\r
-       },\r
-\r
-       unproject: function (point) { // (Point, Boolean) -> LatLng\r
-               var d = L.LatLng.RAD_TO_DEG,\r
-                       r = this.R_MAJOR,\r
-                       r2 = this.R_MINOR,\r
-                       lng = point.x * d / r,\r
-                       tmp = r2 / r,\r
-                       eccent = Math.sqrt(1 - (tmp * tmp)),\r
-                       ts = Math.exp(- point.y / r2),\r
-                       phi = (Math.PI / 2) - 2 * Math.atan(ts),\r
-                       numIter = 15,\r
-                       tol = 1e-7,\r
-                       i = numIter,\r
-                       dphi = 0.1,\r
-                       con;\r
-\r
-               while ((Math.abs(dphi) > tol) && (--i > 0)) {\r
-                       con = eccent * Math.sin(phi);\r
-                       dphi = (Math.PI / 2) - 2 * Math.atan(ts * Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;\r
-                       phi += dphi;\r
-               }\r
-\r
-               return new L.LatLng(phi * d, lng, true);\r
-       }\r
-};\r
-
-
-\r
-L.CRS.EPSG3395 = L.Util.extend({}, L.CRS, {\r
-       code: 'EPSG:3395',\r
-\r
-       projection: L.Projection.Mercator,\r
-\r
-       transformation: (function () {\r
-               var m = L.Projection.Mercator,\r
-                       r = m.R_MAJOR,\r
-                       r2 = m.R_MINOR;\r
-\r
-               return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);\r
-       }())\r
-});\r
-
-
-/*\r
- * L.TileLayer is used for standard xyz-numbered tile layers.\r
- */\r
-\r
-L.TileLayer = L.Class.extend({\r
-       includes: L.Mixin.Events,\r
-\r
-       options: {\r
-               minZoom: 0,\r
-               maxZoom: 18,\r
-               tileSize: 256,\r
-               subdomains: 'abc',\r
-               errorTileUrl: '',\r
-               attribution: '',\r
-               zoomOffset: 0,\r
-               opacity: 1,\r
-               /* (undefined works too)\r
-               zIndex: null,\r
-               tms: false,\r
-               continuousWorld: false,\r
-               noWrap: false,\r
-               zoomReverse: false,\r
-               detectRetina: false,\r
-               reuseTiles: false,\r
-               */\r
-               unloadInvisibleTiles: L.Browser.mobile,\r
-               updateWhenIdle: L.Browser.mobile\r
-       },\r
-\r
-       initialize: function (url, options) {\r
-               options = L.Util.setOptions(this, options);\r
-\r
-               // detecting retina displays, adjusting tileSize and zoom levels\r
-               if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {\r
-\r
-                       options.tileSize = Math.floor(options.tileSize / 2);\r
-                       options.zoomOffset++;\r
-\r
-                       if (options.minZoom > 0) {\r
-                               options.minZoom--;\r
-                       }\r
-                       this.options.maxZoom--;\r
-               }\r
-\r
-               this._url = url;\r
-\r
-               var subdomains = this.options.subdomains;\r
-\r
-               if (typeof subdomains === 'string') {\r
-                       this.options.subdomains = subdomains.split('');\r
-               }\r
-       },\r
-\r
-       onAdd: function (map) {\r
-               this._map = map;\r
-\r
-               // create a container div for tiles\r
-               this._initContainer();\r
-\r
-               // create an image to clone for tiles\r
-               this._createTileProto();\r
-\r
-               // set up events\r
-               map.on({\r
-                       'viewreset': this._resetCallback,\r
-                       'moveend': this._update\r
-               }, this);\r
-\r
-               if (!this.options.updateWhenIdle) {\r
-                       this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);\r
-                       map.on('move', this._limitedUpdate, this);\r
-               }\r
-\r
-               this._reset();\r
-               this._update();\r
-       },\r
-\r
-       addTo: function (map) {\r
-               map.addLayer(this);\r
-               return this;\r
-       },\r
-\r
-       onRemove: function (map) {\r
-               map._panes.tilePane.removeChild(this._container);\r
-\r
-               map.off({\r
-                       'viewreset': this._resetCallback,\r
-                       'moveend': this._update\r
-               }, this);\r
-\r
-               if (!this.options.updateWhenIdle) {\r
-                       map.off('move', this._limitedUpdate, this);\r
-               }\r
-\r
-               this._container = null;\r
-               this._map = null;\r
-       },\r
-\r
-       bringToFront: function () {\r
-               var pane = this._map._panes.tilePane;\r
-\r
-               if (this._container) {\r
-                       pane.appendChild(this._container);\r
-                       this._setAutoZIndex(pane, Math.max);\r
-               }\r
-\r
-               return this;\r
-       },\r
-\r
-       bringToBack: function () {\r
-               var pane = this._map._panes.tilePane;\r
-\r
-               if (this._container) {\r
-                       pane.insertBefore(this._container, pane.firstChild);\r
-                       this._setAutoZIndex(pane, Math.min);\r
-               }\r
-\r
-               return this;\r
-       },\r
-\r
-       getAttribution: function () {\r
-               return this.options.attribution;\r
-       },\r
-\r
-       setOpacity: function (opacity) {\r
-               this.options.opacity = opacity;\r
-\r
-               if (this._map) {\r
-                       this._updateOpacity();\r
-               }\r
-\r
-               return this;\r
-       },\r
-\r
-       setZIndex: function (zIndex) {\r
-               this.options.zIndex = zIndex;\r
-               this._updateZIndex();\r
-\r
-               return this;\r
-       },\r
-\r
-       setUrl: function (url, noRedraw) {\r
-               this._url = url;\r
-\r
-               if (!noRedraw) {\r
-                       this.redraw();\r
-               }\r
-\r
-               return this;\r
-       },\r
-\r
-       redraw: function () {\r
-               if (this._map) {\r
-                       this._map._panes.tilePane.empty = false;\r
-                       this._reset(true);\r
-                       this._update();\r
-               }\r
-               return this;\r
-       },\r
-\r
-       _updateZIndex: function () {\r
-               if (this._container && this.options.zIndex !== undefined) {\r
-                       this._container.style.zIndex = this.options.zIndex;\r
-               }\r
-       },\r
-\r
-       _setAutoZIndex: function (pane, compare) {\r
-\r
-               var layers = pane.getElementsByClassName('leaflet-layer'),\r
-                       edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min\r
-                       zIndex;\r
-\r
-               for (var i = 0, len = layers.length; i < len; i++) {\r
-\r
-                       if (layers[i] !== this._container) {\r
-                               zIndex = parseInt(layers[i].style.zIndex, 10);\r
-\r
-                               if (!isNaN(zIndex)) {\r
-                                       edgeZIndex = compare(edgeZIndex, zIndex);\r
-                               }\r
-                       }\r
-               }\r
-\r
-               this.options.zIndex = this._container.style.zIndex = (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);\r
-       },\r
-\r
-       _updateOpacity: function () {\r
-               L.DomUtil.setOpacity(this._container, this.options.opacity);\r
-\r
-               // stupid webkit hack to force redrawing of tiles\r
-               var i,\r
-                       tiles = this._tiles;\r
-\r
-               if (L.Browser.webkit) {\r
-                       for (i in tiles) {\r
-                               if (tiles.hasOwnProperty(i)) {\r
-                                       tiles[i].style.webkitTransform += ' translate(0,0)';\r
-                               }\r
-                       }\r
-               }\r
-       },\r
-\r
-       _initContainer: function () {\r
-               var tilePane = this._map._panes.tilePane;\r
-\r
-               if (!this._container || tilePane.empty) {\r
-                       this._container = L.DomUtil.create('div', 'leaflet-layer');\r
-\r
-                       this._updateZIndex();\r
-\r
-                       tilePane.appendChild(this._container);\r
-\r
-                       if (this.options.opacity < 1) {\r
-                               this._updateOpacity();\r
-                       }\r
-               }\r
-       },\r
-\r
-       _resetCallback: function (e) {\r
-               this._reset(e.hard);\r
-       },\r
-\r
-       _reset: function (clearOldContainer) {\r
-               var key,\r
-                       tiles = this._tiles;\r
-\r
-               for (key in tiles) {\r
-                       if (tiles.hasOwnProperty(key)) {\r
-                               this.fire('tileunload', {tile: tiles[key]});\r
-                       }\r
-               }\r
-\r
-               this._tiles = {};\r
-               this._tilesToLoad = 0;\r
-\r
-               if (this.options.reuseTiles) {\r
-                       this._unusedTiles = [];\r
-               }\r
-\r
-               if (clearOldContainer && this._container) {\r
-                       this._container.innerHTML = "";\r
-               }\r
-\r
-               this._initContainer();\r
-       },\r
-\r
-       _update: function (e) {\r
-\r
-               if (!this._map) { return; }\r
-\r
-               var bounds   = this._map.getPixelBounds(),\r
-                   zoom     = this._map.getZoom(),\r
-                   tileSize = this.options.tileSize;\r
-\r
-               if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {\r
-                       return;\r
-               }\r
-\r
-               var nwTilePoint = new L.Point(\r
-                               Math.floor(bounds.min.x / tileSize),\r
-                               Math.floor(bounds.min.y / tileSize)),\r
-                       seTilePoint = new L.Point(\r
-                               Math.floor(bounds.max.x / tileSize),\r
-                               Math.floor(bounds.max.y / tileSize)),\r
-                       tileBounds = new L.Bounds(nwTilePoint, seTilePoint);\r
-\r
-               this._addTilesFromCenterOut(tileBounds);\r
-\r
-               if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {\r
-                       this._removeOtherTiles(tileBounds);\r
-               }\r
-       },\r
-\r
-       _addTilesFromCenterOut: function (bounds) {\r
-               var queue = [],\r
-                       center = bounds.getCenter();\r
-\r
-               var j, i, point;\r
-\r
-               for (j = bounds.min.y; j <= bounds.max.y; j++) {\r
-                       for (i = bounds.min.x; i <= bounds.max.x; i++) {\r
-                               point = new L.Point(i, j);\r
-\r
-                               if (this._tileShouldBeLoaded(point)) {\r
-                                       queue.push(point);\r
-                               }\r
-                       }\r
-               }\r
-\r
-               var tilesToLoad = queue.length;\r
-\r
-               if (tilesToLoad === 0) { return; }\r
-\r
-               // load tiles in order of their distance to center\r
-               queue.sort(function (a, b) {\r
-                       return a.distanceTo(center) - b.distanceTo(center);\r
-               });\r
-\r
-               var fragment = document.createDocumentFragment();\r
-\r
-               // if its the first batch of tiles to load\r
-               if (!this._tilesToLoad) {\r
-                       this.fire('loading');\r
-               }\r
-\r
-               this._tilesToLoad += tilesToLoad;\r
-\r
-               for (i = 0; i < tilesToLoad; i++) {\r
-                       this._addTile(queue[i], fragment);\r
-               }\r
-\r
-               this._container.appendChild(fragment);\r
-       },\r
-\r
-       _tileShouldBeLoaded: function (tilePoint) {\r
-               if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {\r
-                       return false; // already loaded\r
-               }\r
-\r
-               if (!this.options.continuousWorld) {\r
-                       var limit = this._getWrapTileNum();\r
-\r
-                       if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||\r
-                                                       tilePoint.y < 0 || tilePoint.y >= limit) {\r
-                               return false; // exceeds world bounds\r
-                       }\r
-               }\r
-\r
-               return true;\r
-       },\r
-\r
-       _removeOtherTiles: function (bounds) {\r
-               var kArr, x, y, key;\r
-\r
-               for (key in this._tiles) {\r
-                       if (this._tiles.hasOwnProperty(key)) {\r
-                               kArr = key.split(':');\r
-                               x = parseInt(kArr[0], 10);\r
-                               y = parseInt(kArr[1], 10);\r
-\r
-                               // remove tile if it's out of bounds\r
-                               if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {\r
-                                       this._removeTile(key);\r
-                               }\r
-                       }\r
-               }\r
-       },\r
-\r
-       _removeTile: function (key) {\r
-               var tile = this._tiles[key];\r
-\r
-               this.fire("tileunload", {tile: tile, url: tile.src});\r
-\r
-               if (this.options.reuseTiles) {\r
-                       L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');\r
-                       this._unusedTiles.push(tile);\r
-               } else if (tile.parentNode === this._container) {\r
-                       this._container.removeChild(tile);\r
-               }\r
-\r
-               if (!L.Browser.android) { //For https://github.com/CloudMade/Leaflet/issues/137\r
-                       tile.src = L.Util.emptyImageUrl;\r
-               }\r
-\r
-               delete this._tiles[key];\r
-       },\r
-\r
-       _addTile: function (tilePoint, container) {\r
-               var tilePos = this._getTilePos(tilePoint);\r
-\r
-               // get unused tile - or create a new tile\r
-               var tile = this._getTile();\r
-\r
-               // Chrome 20 layouts much faster with top/left (Verify with timeline, frames)\r
-               // android 4 browser has display issues with top/left and requires transform instead\r
-               // android 3 browser not tested\r
-               // android 2 browser requires top/left or tiles disappear on load or first drag (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866\r
-               // (other browsers don't currently care) - see debug/hacks/jitter.html for an example\r
-               L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);\r
-\r
-               this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;\r
-\r
-               this._loadTile(tile, tilePoint);\r
-\r
-               if (tile.parentNode !== this._container) {\r
-                       container.appendChild(tile);\r
-               }\r
-       },\r
-\r
-       _getZoomForUrl: function () {\r
-\r
-               var options = this.options,\r
-                       zoom = this._map.getZoom();\r
-\r
-               if (options.zoomReverse) {\r
-                       zoom = options.maxZoom - zoom;\r
-               }\r
-\r
-               return zoom + options.zoomOffset;\r
-       },\r
-\r
-       _getTilePos: function (tilePoint) {\r
-               var origin = this._map.getPixelOrigin(),\r
-                       tileSize = this.options.tileSize;\r
-\r
-               return tilePoint.multiplyBy(tileSize).subtract(origin);\r
-       },\r
-\r
-       // image-specific code (override to implement e.g. Canvas or SVG tile layer)\r
-\r
-       getTileUrl: function (tilePoint) {\r
-               this._adjustTilePoint(tilePoint);\r
-\r
-               return L.Util.template(this._url, L.Util.extend({\r
-                       s: this._getSubdomain(tilePoint),\r
-                       z: this._getZoomForUrl(),\r
-                       x: tilePoint.x,\r
-                       y: tilePoint.y\r
-               }, this.options));\r
-       },\r
-\r
-       _getWrapTileNum: function () {\r
-               // TODO refactor, limit is not valid for non-standard projections\r
-               return Math.pow(2, this._getZoomForUrl());\r
-       },\r
-\r
-       _adjustTilePoint: function (tilePoint) {\r
-\r
-               var limit = this._getWrapTileNum();\r
-\r
-               // wrap tile coordinates\r
-               if (!this.options.continuousWorld && !this.options.noWrap) {\r
-                       tilePoint.x = ((tilePoint.x % limit) + limit) % limit;\r
-               }\r
-\r
-               if (this.options.tms) {\r
-                       tilePoint.y = limit - tilePoint.y - 1;\r
-               }\r
-       },\r
-\r
-       _getSubdomain: function (tilePoint) {\r
-               var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;\r
-               return this.options.subdomains[index];\r
-       },\r
-\r
-       _createTileProto: function () {\r
-               var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');\r
-               img.galleryimg = 'no';\r
-\r
-               var tileSize = this.options.tileSize;\r
-               img.style.width = tileSize + 'px';\r
-               img.style.height = tileSize + 'px';\r
-       },\r
-\r
-       _getTile: function () {\r
-               if (this.options.reuseTiles && this._unusedTiles.length > 0) {\r
-                       var tile = this._unusedTiles.pop();\r
-                       this._resetTile(tile);\r
-                       return tile;\r
-               }\r
-               return this._createTile();\r
-       },\r
-\r
-       _resetTile: function (tile) {\r
-               // Override if data stored on a tile needs to be cleaned up before reuse\r
-       },\r
-\r
-       _createTile: function () {\r
-               var tile = this._tileImg.cloneNode(false);\r
-               tile.onselectstart = tile.onmousemove = L.Util.falseFn;\r
-               return tile;\r
-       },\r
-\r
-       _loadTile: function (tile, tilePoint) {\r
-               tile._layer  = this;\r
-               tile.onload  = this._tileOnLoad;\r
-               tile.onerror = this._tileOnError;\r
-\r
-               tile.src     = this.getTileUrl(tilePoint);\r
-       },\r
-\r
-    _tileLoaded: function () {\r
-        this._tilesToLoad--;\r
-        if (!this._tilesToLoad) {\r
-            this.fire('load');\r
-        }\r
-    },\r
-\r
-       _tileOnLoad: function (e) {\r
-               var layer = this._layer;\r
-\r
-               //Only if we are loading an actual image\r
-               if (this.src !== L.Util.emptyImageUrl) {\r
-                       L.DomUtil.addClass(this, 'leaflet-tile-loaded');\r
-\r
-                       layer.fire('tileload', {\r
-                               tile: this,\r
-                               url: this.src\r
-                       });\r
-               }\r
-\r
-               layer._tileLoaded();\r
-       },\r
-\r
-       _tileOnError: function (e) {\r
-               var layer = this._layer;\r
-\r
-               layer.fire('tileerror', {\r
-                       tile: this,\r
-                       url: this.src\r
-               });\r
-\r
-               var newUrl = layer.options.errorTileUrl;\r
-               if (newUrl) {\r
-                       this.src = newUrl;\r
-               }\r
-\r
-        layer._tileLoaded();\r
-    }\r
-});\r
-\r
-L.tileLayer = function (url, options) {\r
-       return new L.TileLayer(url, options);\r
-};\r
-
-
-L.TileLayer.WMS = L.TileLayer.extend({\r
-\r
-       defaultWmsParams: {\r
-               service: 'WMS',\r
-               request: 'GetMap',\r
-               version: '1.1.1',\r
-               layers: '',\r
-               styles: '',\r
-               format: 'image/jpeg',\r
-               transparent: false\r
-       },\r
-\r
-       initialize: function (url, options) { // (String, Object)\r
-\r
-               this._url = url;\r
-\r
-               var wmsParams = L.Util.extend({}, this.defaultWmsParams);\r
-\r
-               if (options.detectRetina && L.Browser.retina) {\r
-                       wmsParams.width = wmsParams.height = this.options.tileSize * 2;\r
-               } else {\r
-                       wmsParams.width = wmsParams.height = this.options.tileSize;\r
-               }\r
-\r
-               for (var i in options) {\r
-                       // all keys that are not TileLayer options go to WMS params\r
-                       if (!this.options.hasOwnProperty(i)) {\r
-                               wmsParams[i] = options[i];\r
-                       }\r
-               }\r
-\r
-               this.wmsParams = wmsParams;\r
-\r
-               L.Util.setOptions(this, options);\r
-       },\r
-\r
-       onAdd: function (map) {\r
-\r
-               var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';\r
-               this.wmsParams[projectionKey] = map.options.crs.code;\r
-\r
-               L.TileLayer.prototype.onAdd.call(this, map);\r
-       },\r
-\r
-       getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String\r
-\r
-               var map = this._map,\r
-                       crs = map.options.crs,\r
-                       tileSize = this.options.tileSize,\r
-\r
-                       nwPoint = tilePoint.multiplyBy(tileSize),\r
-                       sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),\r
-\r
-                       nw = crs.project(map.unproject(nwPoint, zoom)),\r
-                       se = crs.project(map.unproject(sePoint, zoom)),\r
-\r
-                       bbox = [nw.x, se.y, se.x, nw.y].join(','),\r
-\r
-                       url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});\r
-\r
-               return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;\r
-       },\r
-\r
-       setParams: function (params, noRedraw) {\r
-\r
-               L.Util.extend(this.wmsParams, params);\r
-\r
-               if (!noRedraw) {\r
-                       this.redraw();\r
-               }\r
-\r
-               return this;\r
-       }\r
-});\r
-\r
-L.tileLayer.wms = function (url, options) {\r
-       return new L.TileLayer.WMS(url, options);\r
-};\r
-
-
-L.TileLayer.Canvas = L.TileLayer.extend({\r
-       options: {\r
-               async: false\r
-       },\r
-\r
-       initialize: function (options) {\r
-               L.Util.setOptions(this, options);\r
-       },\r
-\r
-       redraw: function () {\r
-               var i,\r
-                       tiles = this._tiles;\r
-\r
-               for (i in tiles) {\r
-                       if (tiles.hasOwnProperty(i)) {\r
-                               this._redrawTile(tiles[i]);\r
-                       }\r
-               }\r
-       },\r
-\r
-       _redrawTile: function (tile) {\r
-               this.drawTile(tile, tile._tilePoint, tile._zoom);\r
-       },\r
-\r
-       _createTileProto: function () {\r
-               var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');\r
-\r
-