integration/v2/lib/leaflet/leaflet-src.js
changeset 155 551ae48cdf0b
equal deleted inserted replaced
154:fa381a1d156f 155:551ae48cdf0b
       
     1 /*
       
     2  Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
       
     3  (c) 2010-2013, Vladimir Agafonkin, CloudMade
       
     4 */
       
     5 (function (window, document, undefined) {/*
       
     6  * The L namespace contains all Leaflet classes and functions.
       
     7  * This code allows you to handle any possible namespace conflicts.
       
     8  */
       
     9 
       
    10 var L, originalL;
       
    11 
       
    12 if (typeof exports !== undefined + '') {
       
    13 	L = exports;
       
    14 } else {
       
    15 	originalL = window.L;
       
    16 	L = {};
       
    17 
       
    18 	L.noConflict = function () {
       
    19 		window.L = originalL;
       
    20 		return this;
       
    21 	};
       
    22 
       
    23 	window.L = L;
       
    24 }
       
    25 
       
    26 L.version = '0.5.1';
       
    27 
       
    28 
       
    29 /*
       
    30  * L.Util contains various utility functions used throughout Leaflet code.
       
    31  */
       
    32 
       
    33 L.Util = {
       
    34 	extend: function (dest) { // (Object[, Object, ...]) ->
       
    35 		var sources = Array.prototype.slice.call(arguments, 1),
       
    36 		    i, j, len, src;
       
    37 
       
    38 		for (j = 0, len = sources.length; j < len; j++) {
       
    39 			src = sources[j] || {};
       
    40 			for (i in src) {
       
    41 				if (src.hasOwnProperty(i)) {
       
    42 					dest[i] = src[i];
       
    43 				}
       
    44 			}
       
    45 		}
       
    46 		return dest;
       
    47 	},
       
    48 
       
    49 	bind: function (fn, obj) { // (Function, Object) -> Function
       
    50 		var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
       
    51 		return function () {
       
    52 			return fn.apply(obj, args || arguments);
       
    53 		};
       
    54 	},
       
    55 
       
    56 	stamp: (function () {
       
    57 		var lastId = 0, key = '_leaflet_id';
       
    58 		return function (/*Object*/ obj) {
       
    59 			obj[key] = obj[key] || ++lastId;
       
    60 			return obj[key];
       
    61 		};
       
    62 	}()),
       
    63 
       
    64 	limitExecByInterval: function (fn, time, context) {
       
    65 		var lock, execOnUnlock;
       
    66 
       
    67 		return function wrapperFn() {
       
    68 			var args = arguments;
       
    69 
       
    70 			if (lock) {
       
    71 				execOnUnlock = true;
       
    72 				return;
       
    73 			}
       
    74 
       
    75 			lock = true;
       
    76 
       
    77 			setTimeout(function () {
       
    78 				lock = false;
       
    79 
       
    80 				if (execOnUnlock) {
       
    81 					wrapperFn.apply(context, args);
       
    82 					execOnUnlock = false;
       
    83 				}
       
    84 			}, time);
       
    85 
       
    86 			fn.apply(context, args);
       
    87 		};
       
    88 	},
       
    89 
       
    90 	falseFn: function () {
       
    91 		return false;
       
    92 	},
       
    93 
       
    94 	formatNum: function (num, digits) {
       
    95 		var pow = Math.pow(10, digits || 5);
       
    96 		return Math.round(num * pow) / pow;
       
    97 	},
       
    98 
       
    99 	splitWords: function (str) {
       
   100 		return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
       
   101 	},
       
   102 
       
   103 	setOptions: function (obj, options) {
       
   104 		obj.options = L.extend({}, obj.options, options);
       
   105 		return obj.options;
       
   106 	},
       
   107 
       
   108 	getParamString: function (obj, existingUrl) {
       
   109 		var params = [];
       
   110 		for (var i in obj) {
       
   111 			if (obj.hasOwnProperty(i)) {
       
   112 				params.push(i + '=' + obj[i]);
       
   113 			}
       
   114 		}
       
   115 		return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
       
   116 	},
       
   117 
       
   118 	template: function (str, data) {
       
   119 		return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
       
   120 			var value = data[key];
       
   121 			if (!data.hasOwnProperty(key)) {
       
   122 				throw new Error('No value provided for variable ' + str);
       
   123 			}
       
   124 			return value;
       
   125 		});
       
   126 	},
       
   127 
       
   128 	isArray: function (obj) {
       
   129 		return (Object.prototype.toString.call(obj) === '[object Array]');
       
   130 	},
       
   131 
       
   132 	emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
       
   133 };
       
   134 
       
   135 (function () {
       
   136 
       
   137 	// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
       
   138 
       
   139 	function getPrefixed(name) {
       
   140 		var i, fn,
       
   141 		    prefixes = ['webkit', 'moz', 'o', 'ms'];
       
   142 
       
   143 		for (i = 0; i < prefixes.length && !fn; i++) {
       
   144 			fn = window[prefixes[i] + name];
       
   145 		}
       
   146 
       
   147 		return fn;
       
   148 	}
       
   149 
       
   150 	var lastTime = 0;
       
   151 
       
   152 	function timeoutDefer(fn) {
       
   153 		var time = +new Date(),
       
   154 		    timeToCall = Math.max(0, 16 - (time - lastTime));
       
   155 
       
   156 		lastTime = time + timeToCall;
       
   157 		return window.setTimeout(fn, timeToCall);
       
   158 	}
       
   159 
       
   160 	var requestFn = window.requestAnimationFrame ||
       
   161 	        getPrefixed('RequestAnimationFrame') || timeoutDefer;
       
   162 
       
   163 	var cancelFn = window.cancelAnimationFrame ||
       
   164 	        getPrefixed('CancelAnimationFrame') ||
       
   165 	        getPrefixed('CancelRequestAnimationFrame') ||
       
   166 	        function (id) { window.clearTimeout(id); };
       
   167 
       
   168 
       
   169 	L.Util.requestAnimFrame = function (fn, context, immediate, element) {
       
   170 		fn = L.bind(fn, context);
       
   171 
       
   172 		if (immediate && requestFn === timeoutDefer) {
       
   173 			fn();
       
   174 		} else {
       
   175 			return requestFn.call(window, fn, element);
       
   176 		}
       
   177 	};
       
   178 
       
   179 	L.Util.cancelAnimFrame = function (id) {
       
   180 		if (id) {
       
   181 			cancelFn.call(window, id);
       
   182 		}
       
   183 	};
       
   184 
       
   185 }());
       
   186 
       
   187 // shortcuts for most used utility functions
       
   188 L.extend = L.Util.extend;
       
   189 L.bind = L.Util.bind;
       
   190 L.stamp = L.Util.stamp;
       
   191 L.setOptions = L.Util.setOptions;
       
   192 
       
   193 
       
   194 /*
       
   195  * L.Class powers the OOP facilities of the library.
       
   196  * Thanks to John Resig and Dean Edwards for inspiration!
       
   197  */
       
   198 
       
   199 L.Class = function () {};
       
   200 
       
   201 L.Class.extend = function (props) {
       
   202 
       
   203 	// extended class with the new prototype
       
   204 	var NewClass = function () {
       
   205 
       
   206 		// call the constructor
       
   207 		if (this.initialize) {
       
   208 			this.initialize.apply(this, arguments);
       
   209 		}
       
   210 
       
   211 		// call all constructor hooks
       
   212 		if (this._initHooks) {
       
   213 			this.callInitHooks();
       
   214 		}
       
   215 	};
       
   216 
       
   217 	// instantiate class without calling constructor
       
   218 	var F = function () {};
       
   219 	F.prototype = this.prototype;
       
   220 
       
   221 	var proto = new F();
       
   222 	proto.constructor = NewClass;
       
   223 
       
   224 	NewClass.prototype = proto;
       
   225 
       
   226 	//inherit parent's statics
       
   227 	for (var i in this) {
       
   228 		if (this.hasOwnProperty(i) && i !== 'prototype') {
       
   229 			NewClass[i] = this[i];
       
   230 		}
       
   231 	}
       
   232 
       
   233 	// mix static properties into the class
       
   234 	if (props.statics) {
       
   235 		L.extend(NewClass, props.statics);
       
   236 		delete props.statics;
       
   237 	}
       
   238 
       
   239 	// mix includes into the prototype
       
   240 	if (props.includes) {
       
   241 		L.Util.extend.apply(null, [proto].concat(props.includes));
       
   242 		delete props.includes;
       
   243 	}
       
   244 
       
   245 	// merge options
       
   246 	if (props.options && proto.options) {
       
   247 		props.options = L.extend({}, proto.options, props.options);
       
   248 	}
       
   249 
       
   250 	// mix given properties into the prototype
       
   251 	L.extend(proto, props);
       
   252 
       
   253 	proto._initHooks = [];
       
   254 
       
   255 	var parent = this;
       
   256 	// add method for calling all hooks
       
   257 	proto.callInitHooks = function () {
       
   258 
       
   259 		if (this._initHooksCalled) { return; }
       
   260 
       
   261 		if (parent.prototype.callInitHooks) {
       
   262 			parent.prototype.callInitHooks.call(this);
       
   263 		}
       
   264 
       
   265 		this._initHooksCalled = true;
       
   266 
       
   267 		for (var i = 0, len = proto._initHooks.length; i < len; i++) {
       
   268 			proto._initHooks[i].call(this);
       
   269 		}
       
   270 	};
       
   271 
       
   272 	return NewClass;
       
   273 };
       
   274 
       
   275 
       
   276 // method for adding properties to prototype
       
   277 L.Class.include = function (props) {
       
   278 	L.extend(this.prototype, props);
       
   279 };
       
   280 
       
   281 // merge new default options to the Class
       
   282 L.Class.mergeOptions = function (options) {
       
   283 	L.extend(this.prototype.options, options);
       
   284 };
       
   285 
       
   286 // add a constructor hook
       
   287 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
       
   288 	var args = Array.prototype.slice.call(arguments, 1);
       
   289 
       
   290 	var init = typeof fn === 'function' ? fn : function () {
       
   291 		this[fn].apply(this, args);
       
   292 	};
       
   293 
       
   294 	this.prototype._initHooks = this.prototype._initHooks || [];
       
   295 	this.prototype._initHooks.push(init);
       
   296 };
       
   297 
       
   298 
       
   299 /*
       
   300  * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
       
   301  */
       
   302 
       
   303 var key = '_leaflet_events';
       
   304 
       
   305 L.Mixin = {};
       
   306 
       
   307 L.Mixin.Events = {
       
   308 
       
   309 	addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
       
   310 		var events = this[key] = this[key] || {},
       
   311 			type, i, len;
       
   312 
       
   313 		// Types can be a map of types/handlers
       
   314 		if (typeof types === 'object') {
       
   315 			for (type in types) {
       
   316 				if (types.hasOwnProperty(type)) {
       
   317 					this.addEventListener(type, types[type], fn);
       
   318 				}
       
   319 			}
       
   320 
       
   321 			return this;
       
   322 		}
       
   323 
       
   324 		types = L.Util.splitWords(types);
       
   325 
       
   326 		for (i = 0, len = types.length; i < len; i++) {
       
   327 			events[types[i]] = events[types[i]] || [];
       
   328 			events[types[i]].push({
       
   329 				action: fn,
       
   330 				context: context || this
       
   331 			});
       
   332 		}
       
   333 
       
   334 		return this;
       
   335 	},
       
   336 
       
   337 	hasEventListeners: function (type) { // (String) -> Boolean
       
   338 		return (key in this) && (type in this[key]) && (this[key][type].length > 0);
       
   339 	},
       
   340 
       
   341 	removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
       
   342 		var events = this[key],
       
   343 			type, i, len, listeners, j;
       
   344 
       
   345 		if (typeof types === 'object') {
       
   346 			for (type in types) {
       
   347 				if (types.hasOwnProperty(type)) {
       
   348 					this.removeEventListener(type, types[type], fn);
       
   349 				}
       
   350 			}
       
   351 
       
   352 			return this;
       
   353 		}
       
   354 
       
   355 		types = L.Util.splitWords(types);
       
   356 
       
   357 		for (i = 0, len = types.length; i < len; i++) {
       
   358 
       
   359 			if (this.hasEventListeners(types[i])) {
       
   360 				listeners = events[types[i]];
       
   361 
       
   362 				for (j = listeners.length - 1; j >= 0; j--) {
       
   363 					if (
       
   364 						(!fn || listeners[j].action === fn) &&
       
   365 						(!context || (listeners[j].context === context))
       
   366 					) {
       
   367 						listeners.splice(j, 1);
       
   368 					}
       
   369 				}
       
   370 			}
       
   371 		}
       
   372 
       
   373 		return this;
       
   374 	},
       
   375 
       
   376 	fireEvent: function (type, data) { // (String[, Object])
       
   377 		if (!this.hasEventListeners(type)) {
       
   378 			return this;
       
   379 		}
       
   380 
       
   381 		var event = L.extend({
       
   382 			type: type,
       
   383 			target: this
       
   384 		}, data);
       
   385 
       
   386 		var listeners = this[key][type].slice();
       
   387 
       
   388 		for (var i = 0, len = listeners.length; i < len; i++) {
       
   389 			listeners[i].action.call(listeners[i].context || this, event);
       
   390 		}
       
   391 
       
   392 		return this;
       
   393 	}
       
   394 };
       
   395 
       
   396 L.Mixin.Events.on = L.Mixin.Events.addEventListener;
       
   397 L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
       
   398 L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
       
   399 
       
   400 
       
   401 /*
       
   402  * L.Browser handles different browser and feature detections for internal Leaflet use.
       
   403  */
       
   404 
       
   405 (function () {
       
   406 
       
   407 	var ie = !!window.ActiveXObject,
       
   408 	    ie6 = ie && !window.XMLHttpRequest,
       
   409 	    ie7 = ie && !document.querySelector,
       
   410 
       
   411 	    // terrible browser detection to work around Safari / iOS / Android browser bugs
       
   412 	    ua = navigator.userAgent.toLowerCase(),
       
   413 	    webkit = ua.indexOf('webkit') !== -1,
       
   414 	    chrome = ua.indexOf('chrome') !== -1,
       
   415 	    android = ua.indexOf('android') !== -1,
       
   416 	    android23 = ua.search('android [23]') !== -1,
       
   417 
       
   418 	    mobile = typeof orientation !== undefined + '',
       
   419 	    msTouch = window.navigator && window.navigator.msPointerEnabled &&
       
   420 	              window.navigator.msMaxTouchPoints,
       
   421 	    retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
       
   422 	             ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
       
   423 	              window.matchMedia('(min-resolution:144dpi)').matches),
       
   424 
       
   425 	    doc = document.documentElement,
       
   426 	    ie3d = ie && ('transition' in doc.style),
       
   427 	    webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
       
   428 	    gecko3d = 'MozPerspective' in doc.style,
       
   429 	    opera3d = 'OTransition' in doc.style,
       
   430 	    any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d);
       
   431 
       
   432 
       
   433 	var touch = !window.L_NO_TOUCH && (function () {
       
   434 
       
   435 		var startName = 'ontouchstart';
       
   436 
       
   437 		// IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
       
   438 		if (msTouch || (startName in doc)) {
       
   439 			return true;
       
   440 		}
       
   441 
       
   442 		// Firefox/Gecko
       
   443 		var div = document.createElement('div'),
       
   444 		    supported = false;
       
   445 
       
   446 		if (!div.setAttribute) {
       
   447 			return false;
       
   448 		}
       
   449 		div.setAttribute(startName, 'return;');
       
   450 
       
   451 		if (typeof div[startName] === 'function') {
       
   452 			supported = true;
       
   453 		}
       
   454 
       
   455 		div.removeAttribute(startName);
       
   456 		div = null;
       
   457 
       
   458 		return supported;
       
   459 	}());
       
   460 
       
   461 
       
   462 	L.Browser = {
       
   463 		ie: ie,
       
   464 		ie6: ie6,
       
   465 		ie7: ie7,
       
   466 		webkit: webkit,
       
   467 
       
   468 		android: android,
       
   469 		android23: android23,
       
   470 
       
   471 		chrome: chrome,
       
   472 
       
   473 		ie3d: ie3d,
       
   474 		webkit3d: webkit3d,
       
   475 		gecko3d: gecko3d,
       
   476 		opera3d: opera3d,
       
   477 		any3d: any3d,
       
   478 
       
   479 		mobile: mobile,
       
   480 		mobileWebkit: mobile && webkit,
       
   481 		mobileWebkit3d: mobile && webkit3d,
       
   482 		mobileOpera: mobile && window.opera,
       
   483 
       
   484 		touch: touch,
       
   485 		msTouch: msTouch,
       
   486 
       
   487 		retina: retina
       
   488 	};
       
   489 
       
   490 }());
       
   491 
       
   492 
       
   493 /*
       
   494  * L.Point represents a point with x and y coordinates.
       
   495  */
       
   496 
       
   497 L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
       
   498 	this.x = (round ? Math.round(x) : x);
       
   499 	this.y = (round ? Math.round(y) : y);
       
   500 };
       
   501 
       
   502 L.Point.prototype = {
       
   503 
       
   504 	clone: function () {
       
   505 		return new L.Point(this.x, this.y);
       
   506 	},
       
   507 
       
   508 	// non-destructive, returns a new point
       
   509 	add: function (point) {
       
   510 		return this.clone()._add(L.point(point));
       
   511 	},
       
   512 
       
   513 	// destructive, used directly for performance in situations where it's safe to modify existing point
       
   514 	_add: function (point) {
       
   515 		this.x += point.x;
       
   516 		this.y += point.y;
       
   517 		return this;
       
   518 	},
       
   519 
       
   520 	subtract: function (point) {
       
   521 		return this.clone()._subtract(L.point(point));
       
   522 	},
       
   523 
       
   524 	_subtract: function (point) {
       
   525 		this.x -= point.x;
       
   526 		this.y -= point.y;
       
   527 		return this;
       
   528 	},
       
   529 
       
   530 	divideBy: function (num) {
       
   531 		return this.clone()._divideBy(num);
       
   532 	},
       
   533 
       
   534 	_divideBy: function (num) {
       
   535 		this.x /= num;
       
   536 		this.y /= num;
       
   537 		return this;
       
   538 	},
       
   539 
       
   540 	multiplyBy: function (num) {
       
   541 		return this.clone()._multiplyBy(num);
       
   542 	},
       
   543 
       
   544 	_multiplyBy: function (num) {
       
   545 		this.x *= num;
       
   546 		this.y *= num;
       
   547 		return this;
       
   548 	},
       
   549 
       
   550 	round: function () {
       
   551 		return this.clone()._round();
       
   552 	},
       
   553 
       
   554 	_round: function () {
       
   555 		this.x = Math.round(this.x);
       
   556 		this.y = Math.round(this.y);
       
   557 		return this;
       
   558 	},
       
   559 
       
   560 	floor: function () {
       
   561 		return this.clone()._floor();
       
   562 	},
       
   563 
       
   564 	_floor: function () {
       
   565 		this.x = Math.floor(this.x);
       
   566 		this.y = Math.floor(this.y);
       
   567 		return this;
       
   568 	},
       
   569 
       
   570 	distanceTo: function (point) {
       
   571 		point = L.point(point);
       
   572 
       
   573 		var x = point.x - this.x,
       
   574 		    y = point.y - this.y;
       
   575 
       
   576 		return Math.sqrt(x * x + y * y);
       
   577 	},
       
   578 
       
   579 	equals: function (point) {
       
   580 		return point.x === this.x &&
       
   581 		       point.y === this.y;
       
   582 	},
       
   583 
       
   584 	toString: function () {
       
   585 		return 'Point(' +
       
   586 		        L.Util.formatNum(this.x) + ', ' +
       
   587 		        L.Util.formatNum(this.y) + ')';
       
   588 	}
       
   589 };
       
   590 
       
   591 L.point = function (x, y, round) {
       
   592 	if (x instanceof L.Point) {
       
   593 		return x;
       
   594 	}
       
   595 	if (L.Util.isArray(x)) {
       
   596 		return new L.Point(x[0], x[1]);
       
   597 	}
       
   598 	if (isNaN(x)) {
       
   599 		return x;
       
   600 	}
       
   601 	return new L.Point(x, y, round);
       
   602 };
       
   603 
       
   604 
       
   605 /*
       
   606  * L.Bounds represents a rectangular area on the screen in pixel coordinates.
       
   607  */
       
   608 
       
   609 L.Bounds = function (a, b) { //(Point, Point) or Point[]
       
   610 	if (!a) { return; }
       
   611 
       
   612 	var points = b ? [a, b] : a;
       
   613 
       
   614 	for (var i = 0, len = points.length; i < len; i++) {
       
   615 		this.extend(points[i]);
       
   616 	}
       
   617 };
       
   618 
       
   619 L.Bounds.prototype = {
       
   620 	// extend the bounds to contain the given point
       
   621 	extend: function (point) { // (Point)
       
   622 		point = L.point(point);
       
   623 
       
   624 		if (!this.min && !this.max) {
       
   625 			this.min = point.clone();
       
   626 			this.max = point.clone();
       
   627 		} else {
       
   628 			this.min.x = Math.min(point.x, this.min.x);
       
   629 			this.max.x = Math.max(point.x, this.max.x);
       
   630 			this.min.y = Math.min(point.y, this.min.y);
       
   631 			this.max.y = Math.max(point.y, this.max.y);
       
   632 		}
       
   633 		return this;
       
   634 	},
       
   635 
       
   636 	getCenter: function (round) { // (Boolean) -> Point
       
   637 		return new L.Point(
       
   638 		        (this.min.x + this.max.x) / 2,
       
   639 		        (this.min.y + this.max.y) / 2, round);
       
   640 	},
       
   641 
       
   642 	getBottomLeft: function () { // -> Point
       
   643 		return new L.Point(this.min.x, this.max.y);
       
   644 	},
       
   645 
       
   646 	getTopRight: function () { // -> Point
       
   647 		return new L.Point(this.max.x, this.min.y);
       
   648 	},
       
   649 
       
   650 	getSize: function () {
       
   651 		return this.max.subtract(this.min);
       
   652 	},
       
   653 
       
   654 	contains: function (obj) { // (Bounds) or (Point) -> Boolean
       
   655 		var min, max;
       
   656 
       
   657 		if (typeof obj[0] === 'number' || obj instanceof L.Point) {
       
   658 			obj = L.point(obj);
       
   659 		} else {
       
   660 			obj = L.bounds(obj);
       
   661 		}
       
   662 
       
   663 		if (obj instanceof L.Bounds) {
       
   664 			min = obj.min;
       
   665 			max = obj.max;
       
   666 		} else {
       
   667 			min = max = obj;
       
   668 		}
       
   669 
       
   670 		return (min.x >= this.min.x) &&
       
   671 		       (max.x <= this.max.x) &&
       
   672 		       (min.y >= this.min.y) &&
       
   673 		       (max.y <= this.max.y);
       
   674 	},
       
   675 
       
   676 	intersects: function (bounds) { // (Bounds) -> Boolean
       
   677 		bounds = L.bounds(bounds);
       
   678 
       
   679 		var min = this.min,
       
   680 		    max = this.max,
       
   681 		    min2 = bounds.min,
       
   682 		    max2 = bounds.max,
       
   683 		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
       
   684 		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
       
   685 
       
   686 		return xIntersects && yIntersects;
       
   687 	},
       
   688 
       
   689 	isValid: function () {
       
   690 		return !!(this.min && this.max);
       
   691 	}
       
   692 };
       
   693 
       
   694 L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
       
   695 	if (!a || a instanceof L.Bounds) {
       
   696 		return a;
       
   697 	}
       
   698 	return new L.Bounds(a, b);
       
   699 };
       
   700 
       
   701 
       
   702 /*
       
   703  * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
       
   704  */
       
   705 
       
   706 L.Transformation = function (a, b, c, d) {
       
   707 	this._a = a;
       
   708 	this._b = b;
       
   709 	this._c = c;
       
   710 	this._d = d;
       
   711 };
       
   712 
       
   713 L.Transformation.prototype = {
       
   714 	transform: function (point, scale) { // (Point, Number) -> Point
       
   715 		return this._transform(point.clone(), scale);
       
   716 	},
       
   717 
       
   718 	// destructive transform (faster)
       
   719 	_transform: function (point, scale) {
       
   720 		scale = scale || 1;
       
   721 		point.x = scale * (this._a * point.x + this._b);
       
   722 		point.y = scale * (this._c * point.y + this._d);
       
   723 		return point;
       
   724 	},
       
   725 
       
   726 	untransform: function (point, scale) {
       
   727 		scale = scale || 1;
       
   728 		return new L.Point(
       
   729 		        (point.x / scale - this._b) / this._a,
       
   730 		        (point.y / scale - this._d) / this._c);
       
   731 	}
       
   732 };
       
   733 
       
   734 
       
   735 /*
       
   736  * L.DomUtil contains various utility functions for working with DOM.
       
   737  */
       
   738 
       
   739 L.DomUtil = {
       
   740 	get: function (id) {
       
   741 		return (typeof id === 'string' ? document.getElementById(id) : id);
       
   742 	},
       
   743 
       
   744 	getStyle: function (el, style) {
       
   745 
       
   746 		var value = el.style[style];
       
   747 
       
   748 		if (!value && el.currentStyle) {
       
   749 			value = el.currentStyle[style];
       
   750 		}
       
   751 
       
   752 		if ((!value || value === 'auto') && document.defaultView) {
       
   753 			var css = document.defaultView.getComputedStyle(el, null);
       
   754 			value = css ? css[style] : null;
       
   755 		}
       
   756 
       
   757 		return value === 'auto' ? null : value;
       
   758 	},
       
   759 
       
   760 	getViewportOffset: function (element) {
       
   761 
       
   762 		var top = 0,
       
   763 		    left = 0,
       
   764 		    el = element,
       
   765 		    docBody = document.body,
       
   766 		    pos,
       
   767 		    ie7 = L.Browser.ie7;
       
   768 
       
   769 		do {
       
   770 			top  += el.offsetTop  || 0;
       
   771 			left += el.offsetLeft || 0;
       
   772 
       
   773 			//add borders
       
   774 			top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0;
       
   775 			left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 0;
       
   776 
       
   777 			pos = L.DomUtil.getStyle(el, 'position');
       
   778 
       
   779 			if (el.offsetParent === docBody && pos === 'absolute') { break; }
       
   780 
       
   781 			if (pos === 'fixed') {
       
   782 				top  += docBody.scrollTop  || 0;
       
   783 				left += docBody.scrollLeft || 0;
       
   784 				break;
       
   785 			}
       
   786 			el = el.offsetParent;
       
   787 
       
   788 		} while (el);
       
   789 
       
   790 		el = element;
       
   791 
       
   792 		do {
       
   793 			if (el === docBody) { break; }
       
   794 
       
   795 			top  -= el.scrollTop  || 0;
       
   796 			left -= el.scrollLeft || 0;
       
   797 
       
   798 			// webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
       
   799 			// https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
       
   800 			if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
       
   801 				left += el.scrollWidth - el.clientWidth;
       
   802 
       
   803 				// ie7 shows the scrollbar by default and provides clientWidth counting it, so we
       
   804 				// need to add it back in if it is visible; scrollbar is on the left as we are RTL
       
   805 				if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
       
   806 				           L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
       
   807 					left += 17;
       
   808 				}
       
   809 			}
       
   810 
       
   811 			el = el.parentNode;
       
   812 		} while (el);
       
   813 
       
   814 		return new L.Point(left, top);
       
   815 	},
       
   816 
       
   817 	documentIsLtr: function () {
       
   818 		if (!L.DomUtil._docIsLtrCached) {
       
   819 			L.DomUtil._docIsLtrCached = true;
       
   820 			L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === "ltr";
       
   821 		}
       
   822 		return L.DomUtil._docIsLtr;
       
   823 	},
       
   824 
       
   825 	create: function (tagName, className, container) {
       
   826 
       
   827 		var el = document.createElement(tagName);
       
   828 		el.className = className;
       
   829 
       
   830 		if (container) {
       
   831 			container.appendChild(el);
       
   832 		}
       
   833 
       
   834 		return el;
       
   835 	},
       
   836 
       
   837 	disableTextSelection: function () {
       
   838 		if (document.selection && document.selection.empty) {
       
   839 			document.selection.empty();
       
   840 		}
       
   841 		if (!this._onselectstart) {
       
   842 			this._onselectstart = document.onselectstart || null;
       
   843 			document.onselectstart = L.Util.falseFn;
       
   844 		}
       
   845 	},
       
   846 
       
   847 	enableTextSelection: function () {
       
   848 		if (document.onselectstart === L.Util.falseFn) {
       
   849 			document.onselectstart = this._onselectstart;
       
   850 			this._onselectstart = null;
       
   851 		}
       
   852 	},
       
   853 
       
   854 	hasClass: function (el, name) {
       
   855 		return (el.className.length > 0) &&
       
   856 		        new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
       
   857 	},
       
   858 
       
   859 	addClass: function (el, name) {
       
   860 		if (!L.DomUtil.hasClass(el, name)) {
       
   861 			el.className += (el.className ? ' ' : '') + name;
       
   862 		}
       
   863 	},
       
   864 
       
   865 	removeClass: function (el, name) {
       
   866 
       
   867 		function replaceFn(w, match) {
       
   868 			if (match === name) { return ''; }
       
   869 			return w;
       
   870 		}
       
   871 
       
   872 		el.className = el.className
       
   873 		        .replace(/(\S+)\s*/g, replaceFn)
       
   874 		        .replace(/(^\s+|\s+$)/, '');
       
   875 	},
       
   876 
       
   877 	setOpacity: function (el, value) {
       
   878 
       
   879 		if ('opacity' in el.style) {
       
   880 			el.style.opacity = value;
       
   881 
       
   882 		} else if ('filter' in el.style) {
       
   883 
       
   884 			var filter = false,
       
   885 			    filterName = 'DXImageTransform.Microsoft.Alpha';
       
   886 
       
   887 			// filters collection throws an error if we try to retrieve a filter that doesn't exist
       
   888 			try { filter = el.filters.item(filterName); } catch (e) {}
       
   889 
       
   890 			value = Math.round(value * 100);
       
   891 
       
   892 			if (filter) {
       
   893 				filter.Enabled = (value !== 100);
       
   894 				filter.Opacity = value;
       
   895 			} else {
       
   896 				el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
       
   897 			}
       
   898 		}
       
   899 	},
       
   900 
       
   901 	testProp: function (props) {
       
   902 
       
   903 		var style = document.documentElement.style;
       
   904 
       
   905 		for (var i = 0; i < props.length; i++) {
       
   906 			if (props[i] in style) {
       
   907 				return props[i];
       
   908 			}
       
   909 		}
       
   910 		return false;
       
   911 	},
       
   912 
       
   913 	getTranslateString: function (point) {
       
   914 		// on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
       
   915 		// makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
       
   916 		// (same speed either way), Opera 12 doesn't support translate3d
       
   917 
       
   918 		var is3d = L.Browser.webkit3d,
       
   919 		    open = 'translate' + (is3d ? '3d' : '') + '(',
       
   920 		    close = (is3d ? ',0' : '') + ')';
       
   921 
       
   922 		return open + point.x + 'px,' + point.y + 'px' + close;
       
   923 	},
       
   924 
       
   925 	getScaleString: function (scale, origin) {
       
   926 
       
   927 		var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
       
   928 		    scaleStr = ' scale(' + scale + ') ';
       
   929 
       
   930 		return preTranslateStr + scaleStr;
       
   931 	},
       
   932 
       
   933 	setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
       
   934 
       
   935 		el._leaflet_pos = point;
       
   936 
       
   937 		if (!disable3D && L.Browser.any3d) {
       
   938 			el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);
       
   939 
       
   940 			// workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
       
   941 			if (L.Browser.mobileWebkit3d) {
       
   942 				el.style.WebkitBackfaceVisibility = 'hidden';
       
   943 			}
       
   944 		} else {
       
   945 			el.style.left = point.x + 'px';
       
   946 			el.style.top = point.y + 'px';
       
   947 		}
       
   948 	},
       
   949 
       
   950 	getPosition: function (el) {
       
   951 		// this method is only used for elements previously positioned using setPosition,
       
   952 		// so it's safe to cache the position for performance
       
   953 		return el._leaflet_pos;
       
   954 	}
       
   955 };
       
   956 
       
   957 
       
   958 // prefix style property names
       
   959 
       
   960 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
       
   961         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
       
   962 
       
   963 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
       
   964 // the same for the transitionend event, in particular the Android 4.1 stock browser
       
   965 
       
   966 L.DomUtil.TRANSITION = L.DomUtil.testProp(
       
   967         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
       
   968 
       
   969 L.DomUtil.TRANSITION_END =
       
   970         L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
       
   971         L.DomUtil.TRANSITION + 'End' : 'transitionend';
       
   972 
       
   973 
       
   974 /*
       
   975  * L.LatLng represents a geographical point with latitude and longitude coordinates.
       
   976  */
       
   977 
       
   978 L.LatLng = function (rawLat, rawLng) { // (Number, Number)
       
   979 	var lat = parseFloat(rawLat),
       
   980 	    lng = parseFloat(rawLng);
       
   981 
       
   982 	if (isNaN(lat) || isNaN(lng)) {
       
   983 		throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
       
   984 	}
       
   985 
       
   986 	this.lat = lat;
       
   987 	this.lng = lng;
       
   988 };
       
   989 
       
   990 L.extend(L.LatLng, {
       
   991 	DEG_TO_RAD: Math.PI / 180,
       
   992 	RAD_TO_DEG: 180 / Math.PI,
       
   993 	MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
       
   994 });
       
   995 
       
   996 L.LatLng.prototype = {
       
   997 	equals: function (obj) { // (LatLng) -> Boolean
       
   998 		if (!obj) { return false; }
       
   999 
       
  1000 		obj = L.latLng(obj);
       
  1001 
       
  1002 		var margin = Math.max(
       
  1003 		        Math.abs(this.lat - obj.lat),
       
  1004 		        Math.abs(this.lng - obj.lng));
       
  1005 
       
  1006 		return margin <= L.LatLng.MAX_MARGIN;
       
  1007 	},
       
  1008 
       
  1009 	toString: function (precision) { // (Number) -> String
       
  1010 		return 'LatLng(' +
       
  1011 		        L.Util.formatNum(this.lat, precision) + ', ' +
       
  1012 		        L.Util.formatNum(this.lng, precision) + ')';
       
  1013 	},
       
  1014 
       
  1015 	// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
       
  1016 	// TODO move to projection code, LatLng shouldn't know about Earth
       
  1017 	distanceTo: function (other) { // (LatLng) -> Number
       
  1018 		other = L.latLng(other);
       
  1019 
       
  1020 		var R = 6378137, // earth radius in meters
       
  1021 		    d2r = L.LatLng.DEG_TO_RAD,
       
  1022 		    dLat = (other.lat - this.lat) * d2r,
       
  1023 		    dLon = (other.lng - this.lng) * d2r,
       
  1024 		    lat1 = this.lat * d2r,
       
  1025 		    lat2 = other.lat * d2r,
       
  1026 		    sin1 = Math.sin(dLat / 2),
       
  1027 		    sin2 = Math.sin(dLon / 2);
       
  1028 
       
  1029 		var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
       
  1030 
       
  1031 		return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
       
  1032 	},
       
  1033 
       
  1034 	wrap: function (a, b) { // (Number, Number) -> LatLng
       
  1035 		var lng = this.lng;
       
  1036 
       
  1037 		a = a || -180;
       
  1038 		b = b ||  180;
       
  1039 
       
  1040 		lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
       
  1041 
       
  1042 		return new L.LatLng(this.lat, lng);
       
  1043 	}
       
  1044 };
       
  1045 
       
  1046 L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
       
  1047 	if (a instanceof L.LatLng) {
       
  1048 		return a;
       
  1049 	}
       
  1050 	if (L.Util.isArray(a)) {
       
  1051 		return new L.LatLng(a[0], a[1]);
       
  1052 	}
       
  1053 	if (isNaN(a)) {
       
  1054 		return a;
       
  1055 	}
       
  1056 	return new L.LatLng(a, b);
       
  1057 };
       
  1058 
       
  1059 
       
  1060 
       
  1061 /*
       
  1062  * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
       
  1063  */
       
  1064 
       
  1065 L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
       
  1066 	if (!southWest) { return; }
       
  1067 
       
  1068 	var latlngs = northEast ? [southWest, northEast] : southWest;
       
  1069 
       
  1070 	for (var i = 0, len = latlngs.length; i < len; i++) {
       
  1071 		this.extend(latlngs[i]);
       
  1072 	}
       
  1073 };
       
  1074 
       
  1075 L.LatLngBounds.prototype = {
       
  1076 	// extend the bounds to contain the given point or bounds
       
  1077 	extend: function (obj) { // (LatLng) or (LatLngBounds)
       
  1078 		if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
       
  1079 			obj = L.latLng(obj);
       
  1080 		} else {
       
  1081 			obj = L.latLngBounds(obj);
       
  1082 		}
       
  1083 
       
  1084 		if (obj instanceof L.LatLng) {
       
  1085 			if (!this._southWest && !this._northEast) {
       
  1086 				this._southWest = new L.LatLng(obj.lat, obj.lng);
       
  1087 				this._northEast = new L.LatLng(obj.lat, obj.lng);
       
  1088 			} else {
       
  1089 				this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
       
  1090 				this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
       
  1091 
       
  1092 				this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
       
  1093 				this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
       
  1094 			}
       
  1095 		} else if (obj instanceof L.LatLngBounds) {
       
  1096 			this.extend(obj._southWest);
       
  1097 			this.extend(obj._northEast);
       
  1098 		}
       
  1099 		return this;
       
  1100 	},
       
  1101 
       
  1102 	// extend the bounds by a percentage
       
  1103 	pad: function (bufferRatio) { // (Number) -> LatLngBounds
       
  1104 		var sw = this._southWest,
       
  1105 		    ne = this._northEast,
       
  1106 		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
       
  1107 		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
       
  1108 
       
  1109 		return new L.LatLngBounds(
       
  1110 		        new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
       
  1111 		        new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
       
  1112 	},
       
  1113 
       
  1114 	getCenter: function () { // -> LatLng
       
  1115 		return new L.LatLng(
       
  1116 		        (this._southWest.lat + this._northEast.lat) / 2,
       
  1117 		        (this._southWest.lng + this._northEast.lng) / 2);
       
  1118 	},
       
  1119 
       
  1120 	getSouthWest: function () {
       
  1121 		return this._southWest;
       
  1122 	},
       
  1123 
       
  1124 	getNorthEast: function () {
       
  1125 		return this._northEast;
       
  1126 	},
       
  1127 
       
  1128 	getNorthWest: function () {
       
  1129 		return new L.LatLng(this._northEast.lat, this._southWest.lng);
       
  1130 	},
       
  1131 
       
  1132 	getSouthEast: function () {
       
  1133 		return new L.LatLng(this._southWest.lat, this._northEast.lng);
       
  1134 	},
       
  1135 
       
  1136 	contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
       
  1137 		if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
       
  1138 			obj = L.latLng(obj);
       
  1139 		} else {
       
  1140 			obj = L.latLngBounds(obj);
       
  1141 		}
       
  1142 
       
  1143 		var sw = this._southWest,
       
  1144 		    ne = this._northEast,
       
  1145 		    sw2, ne2;
       
  1146 
       
  1147 		if (obj instanceof L.LatLngBounds) {
       
  1148 			sw2 = obj.getSouthWest();
       
  1149 			ne2 = obj.getNorthEast();
       
  1150 		} else {
       
  1151 			sw2 = ne2 = obj;
       
  1152 		}
       
  1153 
       
  1154 		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
       
  1155 		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
       
  1156 	},
       
  1157 
       
  1158 	intersects: function (bounds) { // (LatLngBounds)
       
  1159 		bounds = L.latLngBounds(bounds);
       
  1160 
       
  1161 		var sw = this._southWest,
       
  1162 		    ne = this._northEast,
       
  1163 		    sw2 = bounds.getSouthWest(),
       
  1164 		    ne2 = bounds.getNorthEast(),
       
  1165 
       
  1166 		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
       
  1167 		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
       
  1168 
       
  1169 		return latIntersects && lngIntersects;
       
  1170 	},
       
  1171 
       
  1172 	toBBoxString: function () {
       
  1173 		var sw = this._southWest,
       
  1174 		    ne = this._northEast;
       
  1175 
       
  1176 		return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
       
  1177 	},
       
  1178 
       
  1179 	equals: function (bounds) { // (LatLngBounds)
       
  1180 		if (!bounds) { return false; }
       
  1181 
       
  1182 		bounds = L.latLngBounds(bounds);
       
  1183 
       
  1184 		return this._southWest.equals(bounds.getSouthWest()) &&
       
  1185 		       this._northEast.equals(bounds.getNorthEast());
       
  1186 	},
       
  1187 
       
  1188 	isValid: function () {
       
  1189 		return !!(this._southWest && this._northEast);
       
  1190 	}
       
  1191 };
       
  1192 
       
  1193 //TODO International date line?
       
  1194 
       
  1195 L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
       
  1196 	if (!a || a instanceof L.LatLngBounds) {
       
  1197 		return a;
       
  1198 	}
       
  1199 	return new L.LatLngBounds(a, b);
       
  1200 };
       
  1201 
       
  1202 
       
  1203 /*
       
  1204  * L.Projection contains various geographical projections used by CRS classes.
       
  1205  */
       
  1206 
       
  1207 L.Projection = {};
       
  1208 
       
  1209 
       
  1210 /*
       
  1211  * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
       
  1212  */
       
  1213 
       
  1214 L.Projection.SphericalMercator = {
       
  1215 	MAX_LATITUDE: 85.0511287798,
       
  1216 
       
  1217 	project: function (latlng) { // (LatLng) -> Point
       
  1218 		var d = L.LatLng.DEG_TO_RAD,
       
  1219 		    max = this.MAX_LATITUDE,
       
  1220 		    lat = Math.max(Math.min(max, latlng.lat), -max),
       
  1221 		    x = latlng.lng * d,
       
  1222 		    y = lat * d;
       
  1223 
       
  1224 		y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
       
  1225 
       
  1226 		return new L.Point(x, y);
       
  1227 	},
       
  1228 
       
  1229 	unproject: function (point) { // (Point, Boolean) -> LatLng
       
  1230 		var d = L.LatLng.RAD_TO_DEG,
       
  1231 		    lng = point.x * d,
       
  1232 		    lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
       
  1233 
       
  1234 		return new L.LatLng(lat, lng);
       
  1235 	}
       
  1236 };
       
  1237 
       
  1238 
       
  1239 /*
       
  1240  * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
       
  1241  */
       
  1242 
       
  1243 L.Projection.LonLat = {
       
  1244 	project: function (latlng) {
       
  1245 		return new L.Point(latlng.lng, latlng.lat);
       
  1246 	},
       
  1247 
       
  1248 	unproject: function (point) {
       
  1249 		return new L.LatLng(point.y, point.x);
       
  1250 	}
       
  1251 };
       
  1252 
       
  1253 
       
  1254 /*
       
  1255  * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
       
  1256  */
       
  1257 
       
  1258 L.CRS = {
       
  1259 	latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
       
  1260 		var projectedPoint = this.projection.project(latlng),
       
  1261 		    scale = this.scale(zoom);
       
  1262 
       
  1263 		return this.transformation._transform(projectedPoint, scale);
       
  1264 	},
       
  1265 
       
  1266 	pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
       
  1267 		var scale = this.scale(zoom),
       
  1268 		    untransformedPoint = this.transformation.untransform(point, scale);
       
  1269 
       
  1270 		return this.projection.unproject(untransformedPoint);
       
  1271 	},
       
  1272 
       
  1273 	project: function (latlng) {
       
  1274 		return this.projection.project(latlng);
       
  1275 	},
       
  1276 
       
  1277 	scale: function (zoom) {
       
  1278 		return 256 * Math.pow(2, zoom);
       
  1279 	}
       
  1280 };
       
  1281 
       
  1282 
       
  1283 /*
       
  1284  * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
       
  1285  */
       
  1286 
       
  1287 L.CRS.Simple = L.extend({}, L.CRS, {
       
  1288 	projection: L.Projection.LonLat,
       
  1289 	transformation: new L.Transformation(1, 0, -1, 0),
       
  1290 
       
  1291 	scale: function (zoom) {
       
  1292 		return Math.pow(2, zoom);
       
  1293 	}
       
  1294 });
       
  1295 
       
  1296 
       
  1297 /*
       
  1298  * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
       
  1299  * and is used by Leaflet by default.
       
  1300  */
       
  1301 
       
  1302 L.CRS.EPSG3857 = L.extend({}, L.CRS, {
       
  1303 	code: 'EPSG:3857',
       
  1304 
       
  1305 	projection: L.Projection.SphericalMercator,
       
  1306 	transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
       
  1307 
       
  1308 	project: function (latlng) { // (LatLng) -> Point
       
  1309 		var projectedPoint = this.projection.project(latlng),
       
  1310 		    earthRadius = 6378137;
       
  1311 		return projectedPoint.multiplyBy(earthRadius);
       
  1312 	}
       
  1313 });
       
  1314 
       
  1315 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
       
  1316 	code: 'EPSG:900913'
       
  1317 });
       
  1318 
       
  1319 
       
  1320 /*
       
  1321  * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
       
  1322  */
       
  1323 
       
  1324 L.CRS.EPSG4326 = L.extend({}, L.CRS, {
       
  1325 	code: 'EPSG:4326',
       
  1326 
       
  1327 	projection: L.Projection.LonLat,
       
  1328 	transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
       
  1329 });
       
  1330 
       
  1331 
       
  1332 /*
       
  1333  * L.Map is the central class of the API - it is used to create a map.
       
  1334  */
       
  1335 
       
  1336 L.Map = L.Class.extend({
       
  1337 
       
  1338 	includes: L.Mixin.Events,
       
  1339 
       
  1340 	options: {
       
  1341 		crs: L.CRS.EPSG3857,
       
  1342 
       
  1343 		/*
       
  1344 		center: LatLng,
       
  1345 		zoom: Number,
       
  1346 		layers: Array,
       
  1347 		*/
       
  1348 
       
  1349 		fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
       
  1350 		trackResize: true,
       
  1351 		markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
       
  1352 	},
       
  1353 
       
  1354 	initialize: function (id, options) { // (HTMLElement or String, Object)
       
  1355 		options = L.setOptions(this, options);
       
  1356 
       
  1357 		this._initContainer(id);
       
  1358 		this._initLayout();
       
  1359 		this.callInitHooks();
       
  1360 		this._initEvents();
       
  1361 
       
  1362 		if (options.maxBounds) {
       
  1363 			this.setMaxBounds(options.maxBounds);
       
  1364 		}
       
  1365 
       
  1366 		if (options.center && options.zoom !== undefined) {
       
  1367 			this.setView(L.latLng(options.center), options.zoom, true);
       
  1368 		}
       
  1369 
       
  1370 		this._initLayers(options.layers);
       
  1371 	},
       
  1372 
       
  1373 
       
  1374 	// public methods that modify map state
       
  1375 
       
  1376 	// replaced by animation-powered implementation in Map.PanAnimation.js
       
  1377 	setView: function (center, zoom) {
       
  1378 		this._resetView(L.latLng(center), this._limitZoom(zoom));
       
  1379 		return this;
       
  1380 	},
       
  1381 
       
  1382 	setZoom: function (zoom) { // (Number)
       
  1383 		return this.setView(this.getCenter(), zoom);
       
  1384 	},
       
  1385 
       
  1386 	zoomIn: function (delta) {
       
  1387 		return this.setZoom(this._zoom + (delta || 1));
       
  1388 	},
       
  1389 
       
  1390 	zoomOut: function (delta) {
       
  1391 		return this.setZoom(this._zoom - (delta || 1));
       
  1392 	},
       
  1393 
       
  1394 	fitBounds: function (bounds) { // (LatLngBounds)
       
  1395 		var zoom = this.getBoundsZoom(bounds);
       
  1396 		return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
       
  1397 	},
       
  1398 
       
  1399 	fitWorld: function () {
       
  1400 		var sw = new L.LatLng(-60, -170),
       
  1401 		    ne = new L.LatLng(85, 179);
       
  1402 
       
  1403 		return this.fitBounds(new L.LatLngBounds(sw, ne));
       
  1404 	},
       
  1405 
       
  1406 	panTo: function (center) { // (LatLng)
       
  1407 		return this.setView(center, this._zoom);
       
  1408 	},
       
  1409 
       
  1410 	panBy: function (offset) { // (Point)
       
  1411 		// replaced with animated panBy in Map.Animation.js
       
  1412 		this.fire('movestart');
       
  1413 
       
  1414 		this._rawPanBy(L.point(offset));
       
  1415 
       
  1416 		this.fire('move');
       
  1417 		return this.fire('moveend');
       
  1418 	},
       
  1419 
       
  1420 	setMaxBounds: function (bounds) {
       
  1421 		bounds = L.latLngBounds(bounds);
       
  1422 
       
  1423 		this.options.maxBounds = bounds;
       
  1424 
       
  1425 		if (!bounds) {
       
  1426 			this._boundsMinZoom = null;
       
  1427 			return this;
       
  1428 		}
       
  1429 
       
  1430 		var minZoom = this.getBoundsZoom(bounds, true);
       
  1431 
       
  1432 		this._boundsMinZoom = minZoom;
       
  1433 
       
  1434 		if (this._loaded) {
       
  1435 			if (this._zoom < minZoom) {
       
  1436 				this.setView(bounds.getCenter(), minZoom);
       
  1437 			} else {
       
  1438 				this.panInsideBounds(bounds);
       
  1439 			}
       
  1440 		}
       
  1441 
       
  1442 		return this;
       
  1443 	},
       
  1444 
       
  1445 	panInsideBounds: function (bounds) {
       
  1446 		bounds = L.latLngBounds(bounds);
       
  1447 
       
  1448 		var viewBounds = this.getBounds(),
       
  1449 		    viewSw = this.project(viewBounds.getSouthWest()),
       
  1450 		    viewNe = this.project(viewBounds.getNorthEast()),
       
  1451 		    sw = this.project(bounds.getSouthWest()),
       
  1452 		    ne = this.project(bounds.getNorthEast()),
       
  1453 		    dx = 0,
       
  1454 		    dy = 0;
       
  1455 
       
  1456 		if (viewNe.y < ne.y) { // north
       
  1457 			dy = ne.y - viewNe.y;
       
  1458 		}
       
  1459 		if (viewNe.x > ne.x) { // east
       
  1460 			dx = ne.x - viewNe.x;
       
  1461 		}
       
  1462 		if (viewSw.y > sw.y) { // south
       
  1463 			dy = sw.y - viewSw.y;
       
  1464 		}
       
  1465 		if (viewSw.x < sw.x) { // west
       
  1466 			dx = sw.x - viewSw.x;
       
  1467 		}
       
  1468 
       
  1469 		return this.panBy(new L.Point(dx, dy, true));
       
  1470 	},
       
  1471 
       
  1472 	addLayer: function (layer) {
       
  1473 		// TODO method is too big, refactor
       
  1474 
       
  1475 		var id = L.stamp(layer);
       
  1476 
       
  1477 		if (this._layers[id]) { return this; }
       
  1478 
       
  1479 		this._layers[id] = layer;
       
  1480 
       
  1481 		// TODO getMaxZoom, getMinZoom in ILayer (instead of options)
       
  1482 		if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
       
  1483 			this._zoomBoundLayers[id] = layer;
       
  1484 			this._updateZoomLevels();
       
  1485 		}
       
  1486 
       
  1487 		// TODO looks ugly, refactor!!!
       
  1488 		if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
       
  1489 			this._tileLayersNum++;
       
  1490             this._tileLayersToLoad++;
       
  1491             layer.on('load', this._onTileLayerLoad, this);
       
  1492 		}
       
  1493 
       
  1494 		this.whenReady(function () {
       
  1495 			layer.onAdd(this);
       
  1496 			this.fire('layeradd', {layer: layer});
       
  1497 		}, this);
       
  1498 
       
  1499 		return this;
       
  1500 	},
       
  1501 
       
  1502 	removeLayer: function (layer) {
       
  1503 		var id = L.stamp(layer);
       
  1504 
       
  1505 		if (!this._layers[id]) { return; }
       
  1506 
       
  1507 		layer.onRemove(this);
       
  1508 
       
  1509 		delete this._layers[id];
       
  1510 		if (this._zoomBoundLayers[id]) {
       
  1511 			delete this._zoomBoundLayers[id];
       
  1512 			this._updateZoomLevels();
       
  1513 		}
       
  1514 
       
  1515 		// TODO looks ugly, refactor
       
  1516 		if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
       
  1517 			this._tileLayersNum--;
       
  1518             this._tileLayersToLoad--;
       
  1519             layer.off('load', this._onTileLayerLoad, this);
       
  1520 		}
       
  1521 
       
  1522 		return this.fire('layerremove', {layer: layer});
       
  1523 	},
       
  1524 
       
  1525 	hasLayer: function (layer) {
       
  1526 		var id = L.stamp(layer);
       
  1527 		return this._layers.hasOwnProperty(id);
       
  1528 	},
       
  1529 
       
  1530 	invalidateSize: function (animate) {
       
  1531 		var oldSize = this.getSize();
       
  1532 
       
  1533 		this._sizeChanged = true;
       
  1534 
       
  1535 		if (this.options.maxBounds) {
       
  1536 			this.setMaxBounds(this.options.maxBounds);
       
  1537 		}
       
  1538 
       
  1539 		if (!this._loaded) { return this; }
       
  1540 
       
  1541 		var offset = oldSize._subtract(this.getSize())._divideBy(2)._round();
       
  1542 
       
  1543 		if (animate === true) {
       
  1544 			this.panBy(offset);
       
  1545 		} else {
       
  1546 			this._rawPanBy(offset);
       
  1547 
       
  1548 			this.fire('move');
       
  1549 
       
  1550 			clearTimeout(this._sizeTimer);
       
  1551 			this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
       
  1552 		}
       
  1553 		return this;
       
  1554 	},
       
  1555 
       
  1556 	// TODO handler.addTo
       
  1557 	addHandler: function (name, HandlerClass) {
       
  1558 		if (!HandlerClass) { return; }
       
  1559 
       
  1560 		this[name] = new HandlerClass(this);
       
  1561 
       
  1562 		if (this.options[name]) {
       
  1563 			this[name].enable();
       
  1564 		}
       
  1565 
       
  1566 		return this;
       
  1567 	},
       
  1568 
       
  1569 
       
  1570 	// public methods for getting map state
       
  1571 
       
  1572 	getCenter: function () { // (Boolean) -> LatLng
       
  1573 		return this.layerPointToLatLng(this._getCenterLayerPoint());
       
  1574 	},
       
  1575 
       
  1576 	getZoom: function () {
       
  1577 		return this._zoom;
       
  1578 	},
       
  1579 
       
  1580 	getBounds: function () {
       
  1581 		var bounds = this.getPixelBounds(),
       
  1582 		    sw = this.unproject(bounds.getBottomLeft()),
       
  1583 		    ne = this.unproject(bounds.getTopRight());
       
  1584 
       
  1585 		return new L.LatLngBounds(sw, ne);
       
  1586 	},
       
  1587 
       
  1588 	getMinZoom: function () {
       
  1589 		var z1 = this.options.minZoom || 0,
       
  1590 		    z2 = this._layersMinZoom || 0,
       
  1591 		    z3 = this._boundsMinZoom || 0;
       
  1592 
       
  1593 		return Math.max(z1, z2, z3);
       
  1594 	},
       
  1595 
       
  1596 	getMaxZoom: function () {
       
  1597 		var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
       
  1598 		    z2 = this._layersMaxZoom  === undefined ? Infinity : this._layersMaxZoom;
       
  1599 
       
  1600 		return Math.min(z1, z2);
       
  1601 	},
       
  1602 
       
  1603 	getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
       
  1604 		bounds = L.latLngBounds(bounds);
       
  1605 
       
  1606 		var size = this.getSize(),
       
  1607 		    zoom = this.options.minZoom || 0,
       
  1608 		    maxZoom = this.getMaxZoom(),
       
  1609 		    ne = bounds.getNorthEast(),
       
  1610 		    sw = bounds.getSouthWest(),
       
  1611 		    boundsSize,
       
  1612 		    nePoint,
       
  1613 		    swPoint,
       
  1614 		    zoomNotFound = true;
       
  1615 
       
  1616 		if (inside) {
       
  1617 			zoom--;
       
  1618 		}
       
  1619 
       
  1620 		do {
       
  1621 			zoom++;
       
  1622 			nePoint = this.project(ne, zoom);
       
  1623 			swPoint = this.project(sw, zoom);
       
  1624 
       
  1625 			boundsSize = new L.Point(
       
  1626 			        Math.abs(nePoint.x - swPoint.x),
       
  1627 			        Math.abs(swPoint.y - nePoint.y));
       
  1628 
       
  1629 			if (!inside) {
       
  1630 				zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
       
  1631 			} else {
       
  1632 				zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
       
  1633 			}
       
  1634 		} while (zoomNotFound && zoom <= maxZoom);
       
  1635 
       
  1636 		if (zoomNotFound && inside) {
       
  1637 			return null;
       
  1638 		}
       
  1639 
       
  1640 		return inside ? zoom : zoom - 1;
       
  1641 	},
       
  1642 
       
  1643 	getSize: function () {
       
  1644 		if (!this._size || this._sizeChanged) {
       
  1645 			this._size = new L.Point(
       
  1646 				this._container.clientWidth,
       
  1647 				this._container.clientHeight);
       
  1648 
       
  1649 			this._sizeChanged = false;
       
  1650 		}
       
  1651 		return this._size.clone();
       
  1652 	},
       
  1653 
       
  1654 	getPixelBounds: function () {
       
  1655 		var topLeftPoint = this._getTopLeftPoint();
       
  1656 		return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
       
  1657 	},
       
  1658 
       
  1659 	getPixelOrigin: function () {
       
  1660 		return this._initialTopLeftPoint;
       
  1661 	},
       
  1662 
       
  1663 	getPanes: function () {
       
  1664 		return this._panes;
       
  1665 	},
       
  1666 
       
  1667 	getContainer: function () {
       
  1668 		return this._container;
       
  1669 	},
       
  1670 
       
  1671 
       
  1672 	// TODO replace with universal implementation after refactoring projections
       
  1673 
       
  1674 	getZoomScale: function (toZoom) {
       
  1675 		var crs = this.options.crs;
       
  1676 		return crs.scale(toZoom) / crs.scale(this._zoom);
       
  1677 	},
       
  1678 
       
  1679 	getScaleZoom: function (scale) {
       
  1680 		return this._zoom + (Math.log(scale) / Math.LN2);
       
  1681 	},
       
  1682 
       
  1683 
       
  1684 	// conversion methods
       
  1685 
       
  1686 	project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
       
  1687 		zoom = zoom === undefined ? this._zoom : zoom;
       
  1688 		return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
       
  1689 	},
       
  1690 
       
  1691 	unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
       
  1692 		zoom = zoom === undefined ? this._zoom : zoom;
       
  1693 		return this.options.crs.pointToLatLng(L.point(point), zoom);
       
  1694 	},
       
  1695 
       
  1696 	layerPointToLatLng: function (point) { // (Point)
       
  1697 		var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
       
  1698 		return this.unproject(projectedPoint);
       
  1699 	},
       
  1700 
       
  1701 	latLngToLayerPoint: function (latlng) { // (LatLng)
       
  1702 		var projectedPoint = this.project(L.latLng(latlng))._round();
       
  1703 		return projectedPoint._subtract(this._initialTopLeftPoint);
       
  1704 	},
       
  1705 
       
  1706 	containerPointToLayerPoint: function (point) { // (Point)
       
  1707 		return L.point(point).subtract(this._getMapPanePos());
       
  1708 	},
       
  1709 
       
  1710 	layerPointToContainerPoint: function (point) { // (Point)
       
  1711 		return L.point(point).add(this._getMapPanePos());
       
  1712 	},
       
  1713 
       
  1714 	containerPointToLatLng: function (point) {
       
  1715 		var layerPoint = this.containerPointToLayerPoint(L.point(point));
       
  1716 		return this.layerPointToLatLng(layerPoint);
       
  1717 	},
       
  1718 
       
  1719 	latLngToContainerPoint: function (latlng) {
       
  1720 		return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
       
  1721 	},
       
  1722 
       
  1723 	mouseEventToContainerPoint: function (e) { // (MouseEvent)
       
  1724 		return L.DomEvent.getMousePosition(e, this._container);
       
  1725 	},
       
  1726 
       
  1727 	mouseEventToLayerPoint: function (e) { // (MouseEvent)
       
  1728 		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
       
  1729 	},
       
  1730 
       
  1731 	mouseEventToLatLng: function (e) { // (MouseEvent)
       
  1732 		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
       
  1733 	},
       
  1734 
       
  1735 
       
  1736 	// map initialization methods
       
  1737 
       
  1738 	_initContainer: function (id) {
       
  1739 		var container = this._container = L.DomUtil.get(id);
       
  1740 
       
  1741 		if (container._leaflet) {
       
  1742 			throw new Error("Map container is already initialized.");
       
  1743 		}
       
  1744 
       
  1745 		container._leaflet = true;
       
  1746 	},
       
  1747 
       
  1748 	_initLayout: function () {
       
  1749 		var container = this._container;
       
  1750 
       
  1751 		L.DomUtil.addClass(container, 'leaflet-container');
       
  1752 
       
  1753 		if (L.Browser.touch) {
       
  1754 			L.DomUtil.addClass(container, 'leaflet-touch');
       
  1755 		}
       
  1756 
       
  1757 		if (this.options.fadeAnimation) {
       
  1758 			L.DomUtil.addClass(container, 'leaflet-fade-anim');
       
  1759 		}
       
  1760 
       
  1761 		var position = L.DomUtil.getStyle(container, 'position');
       
  1762 
       
  1763 		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
       
  1764 			container.style.position = 'relative';
       
  1765 		}
       
  1766 
       
  1767 		this._initPanes();
       
  1768 
       
  1769 		if (this._initControlPos) {
       
  1770 			this._initControlPos();
       
  1771 		}
       
  1772 	},
       
  1773 
       
  1774 	_initPanes: function () {
       
  1775 		var panes = this._panes = {};
       
  1776 
       
  1777 		this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
       
  1778 
       
  1779 		this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
       
  1780 		panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
       
  1781 		panes.shadowPane = this._createPane('leaflet-shadow-pane');
       
  1782 		panes.overlayPane = this._createPane('leaflet-overlay-pane');
       
  1783 		panes.markerPane = this._createPane('leaflet-marker-pane');
       
  1784 		panes.popupPane = this._createPane('leaflet-popup-pane');
       
  1785 
       
  1786 		var zoomHide = ' leaflet-zoom-hide';
       
  1787 
       
  1788 		if (!this.options.markerZoomAnimation) {
       
  1789 			L.DomUtil.addClass(panes.markerPane, zoomHide);
       
  1790 			L.DomUtil.addClass(panes.shadowPane, zoomHide);
       
  1791 			L.DomUtil.addClass(panes.popupPane, zoomHide);
       
  1792 		}
       
  1793 	},
       
  1794 
       
  1795 	_createPane: function (className, container) {
       
  1796 		return L.DomUtil.create('div', className, container || this._panes.objectsPane);
       
  1797 	},
       
  1798 
       
  1799 	_initLayers: function (layers) {
       
  1800 		layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
       
  1801 
       
  1802 		this._layers = {};
       
  1803 		this._zoomBoundLayers = {};
       
  1804 		this._tileLayersNum = 0;
       
  1805 
       
  1806 		var i, len;
       
  1807 
       
  1808 		for (i = 0, len = layers.length; i < len; i++) {
       
  1809 			this.addLayer(layers[i]);
       
  1810 		}
       
  1811 	},
       
  1812 
       
  1813 
       
  1814 	// private methods that modify map state
       
  1815 
       
  1816 	_resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
       
  1817 
       
  1818 		var zoomChanged = (this._zoom !== zoom);
       
  1819 
       
  1820 		if (!afterZoomAnim) {
       
  1821 			this.fire('movestart');
       
  1822 
       
  1823 			if (zoomChanged) {
       
  1824 				this.fire('zoomstart');
       
  1825 			}
       
  1826 		}
       
  1827 
       
  1828 		this._zoom = zoom;
       
  1829 
       
  1830 		this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
       
  1831 
       
  1832 		if (!preserveMapOffset) {
       
  1833 			L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
       
  1834 		} else {
       
  1835 			this._initialTopLeftPoint._add(this._getMapPanePos());
       
  1836 		}
       
  1837 
       
  1838 		this._tileLayersToLoad = this._tileLayersNum;
       
  1839 
       
  1840 		var loading = !this._loaded;
       
  1841 		this._loaded = true;
       
  1842 
       
  1843 		this.fire('viewreset', {hard: !preserveMapOffset});
       
  1844 
       
  1845 		this.fire('move');
       
  1846 
       
  1847 		if (zoomChanged || afterZoomAnim) {
       
  1848 			this.fire('zoomend');
       
  1849 		}
       
  1850 
       
  1851 		this.fire('moveend', {hard: !preserveMapOffset});
       
  1852 
       
  1853 		if (loading) {
       
  1854 			this.fire('load');
       
  1855 		}
       
  1856 	},
       
  1857 
       
  1858 	_rawPanBy: function (offset) {
       
  1859 		L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
       
  1860 	},
       
  1861 
       
  1862 	_updateZoomLevels: function () {
       
  1863 		var i,
       
  1864 			minZoom = Infinity,
       
  1865 			maxZoom = -Infinity;
       
  1866 
       
  1867 		for (i in this._zoomBoundLayers) {
       
  1868 			if (this._zoomBoundLayers.hasOwnProperty(i)) {
       
  1869 				var layer = this._zoomBoundLayers[i];
       
  1870 				if (!isNaN(layer.options.minZoom)) {
       
  1871 					minZoom = Math.min(minZoom, layer.options.minZoom);
       
  1872 				}
       
  1873 				if (!isNaN(layer.options.maxZoom)) {
       
  1874 					maxZoom = Math.max(maxZoom, layer.options.maxZoom);
       
  1875 				}
       
  1876 			}
       
  1877 		}
       
  1878 
       
  1879 		if (i === undefined) { // we have no tilelayers
       
  1880 			this._layersMaxZoom = this._layersMinZoom = undefined;
       
  1881 		} else {
       
  1882 			this._layersMaxZoom = maxZoom;
       
  1883 			this._layersMinZoom = minZoom;
       
  1884 		}
       
  1885 	},
       
  1886 
       
  1887 	// map events
       
  1888 
       
  1889 	_initEvents: function () {
       
  1890 		if (!L.DomEvent) { return; }
       
  1891 
       
  1892 		L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
       
  1893 
       
  1894 		var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
       
  1895 		              'mouseleave', 'mousemove', 'contextmenu'],
       
  1896 		    i, len;
       
  1897 
       
  1898 		for (i = 0, len = events.length; i < len; i++) {
       
  1899 			L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
       
  1900 		}
       
  1901 
       
  1902 		if (this.options.trackResize) {
       
  1903 			L.DomEvent.on(window, 'resize', this._onResize, this);
       
  1904 		}
       
  1905 	},
       
  1906 
       
  1907 	_onResize: function () {
       
  1908 		L.Util.cancelAnimFrame(this._resizeRequest);
       
  1909 		this._resizeRequest = L.Util.requestAnimFrame(
       
  1910 		        this.invalidateSize, this, false, this._container);
       
  1911 	},
       
  1912 
       
  1913 	_onMouseClick: function (e) {
       
  1914 		if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
       
  1915 
       
  1916 		this.fire('preclick');
       
  1917 		this._fireMouseEvent(e);
       
  1918 	},
       
  1919 
       
  1920 	_fireMouseEvent: function (e) {
       
  1921 		if (!this._loaded) { return; }
       
  1922 
       
  1923 		var type = e.type;
       
  1924 
       
  1925 		type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
       
  1926 
       
  1927 		if (!this.hasEventListeners(type)) { return; }
       
  1928 
       
  1929 		if (type === 'contextmenu') {
       
  1930 			L.DomEvent.preventDefault(e);
       
  1931 		}
       
  1932 
       
  1933 		var containerPoint = this.mouseEventToContainerPoint(e),
       
  1934 		    layerPoint = this.containerPointToLayerPoint(containerPoint),
       
  1935 		    latlng = this.layerPointToLatLng(layerPoint);
       
  1936 
       
  1937 		this.fire(type, {
       
  1938 			latlng: latlng,
       
  1939 			layerPoint: layerPoint,
       
  1940 			containerPoint: containerPoint,
       
  1941 			originalEvent: e
       
  1942 		});
       
  1943 	},
       
  1944 
       
  1945 	_onTileLayerLoad: function () {
       
  1946 		// TODO super-ugly, refactor!!!
       
  1947 		// clear scaled tiles after all new tiles are loaded (for performance)
       
  1948 		this._tileLayersToLoad--;
       
  1949 		if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
       
  1950 			clearTimeout(this._clearTileBgTimer);
       
  1951 			this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500);
       
  1952 		}
       
  1953 	},
       
  1954 
       
  1955 	whenReady: function (callback, context) {
       
  1956 		if (this._loaded) {
       
  1957 			callback.call(context || this, this);
       
  1958 		} else {
       
  1959 			this.on('load', callback, context);
       
  1960 		}
       
  1961 		return this;
       
  1962 	},
       
  1963 
       
  1964 
       
  1965 	// private methods for getting map state
       
  1966 
       
  1967 	_getMapPanePos: function () {
       
  1968 		return L.DomUtil.getPosition(this._mapPane);
       
  1969 	},
       
  1970 
       
  1971 	_getTopLeftPoint: function () {
       
  1972 		if (!this._loaded) {
       
  1973 			throw new Error('Set map center and zoom first.');
       
  1974 		}
       
  1975 
       
  1976 		return this._initialTopLeftPoint.subtract(this._getMapPanePos());
       
  1977 	},
       
  1978 
       
  1979 	_getNewTopLeftPoint: function (center, zoom) {
       
  1980 		var viewHalf = this.getSize()._divideBy(2);
       
  1981 		// TODO round on display, not calculation to increase precision?
       
  1982 		return this.project(center, zoom)._subtract(viewHalf)._round();
       
  1983 	},
       
  1984 
       
  1985 	_latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
       
  1986 		var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
       
  1987 		return this.project(latlng, newZoom)._subtract(topLeft);
       
  1988 	},
       
  1989 
       
  1990 	_getCenterLayerPoint: function () {
       
  1991 		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
       
  1992 	},
       
  1993 
       
  1994 	_getCenterOffset: function (center) {
       
  1995 		return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
       
  1996 	},
       
  1997 
       
  1998 	_limitZoom: function (zoom) {
       
  1999 		var min = this.getMinZoom(),
       
  2000 		    max = this.getMaxZoom();
       
  2001 
       
  2002 		return Math.max(min, Math.min(max, zoom));
       
  2003 	}
       
  2004 });
       
  2005 
       
  2006 L.map = function (id, options) {
       
  2007 	return new L.Map(id, options);
       
  2008 };
       
  2009 
       
  2010 
       
  2011 /*
       
  2012  * Mercator projection that takes into account that the Earth is not a perfect sphere.
       
  2013  * Less popular than spherical mercator; used by projections like EPSG:3395.
       
  2014  */
       
  2015 
       
  2016 L.Projection.Mercator = {
       
  2017 	MAX_LATITUDE: 85.0840591556,
       
  2018 
       
  2019 	R_MINOR: 6356752.3142,
       
  2020 	R_MAJOR: 6378137,
       
  2021 
       
  2022 	project: function (latlng) { // (LatLng) -> Point
       
  2023 		var d = L.LatLng.DEG_TO_RAD,
       
  2024 		    max = this.MAX_LATITUDE,
       
  2025 		    lat = Math.max(Math.min(max, latlng.lat), -max),
       
  2026 		    r = this.R_MAJOR,
       
  2027 		    r2 = this.R_MINOR,
       
  2028 		    x = latlng.lng * d * r,
       
  2029 		    y = lat * d,
       
  2030 		    tmp = r2 / r,
       
  2031 		    eccent = Math.sqrt(1.0 - tmp * tmp),
       
  2032 		    con = eccent * Math.sin(y);
       
  2033 
       
  2034 		con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
       
  2035 
       
  2036 		var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
       
  2037 		y = -r2 * Math.log(ts);
       
  2038 
       
  2039 		return new L.Point(x, y);
       
  2040 	},
       
  2041 
       
  2042 	unproject: function (point) { // (Point, Boolean) -> LatLng
       
  2043 		var d = L.LatLng.RAD_TO_DEG,
       
  2044 		    r = this.R_MAJOR,
       
  2045 		    r2 = this.R_MINOR,
       
  2046 		    lng = point.x * d / r,
       
  2047 		    tmp = r2 / r,
       
  2048 		    eccent = Math.sqrt(1 - (tmp * tmp)),
       
  2049 		    ts = Math.exp(- point.y / r2),
       
  2050 		    phi = (Math.PI / 2) - 2 * Math.atan(ts),
       
  2051 		    numIter = 15,
       
  2052 		    tol = 1e-7,
       
  2053 		    i = numIter,
       
  2054 		    dphi = 0.1,
       
  2055 		    con;
       
  2056 
       
  2057 		while ((Math.abs(dphi) > tol) && (--i > 0)) {
       
  2058 			con = eccent * Math.sin(phi);
       
  2059 			dphi = (Math.PI / 2) - 2 * Math.atan(ts *
       
  2060 			            Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
       
  2061 			phi += dphi;
       
  2062 		}
       
  2063 
       
  2064 		return new L.LatLng(phi * d, lng);
       
  2065 	}
       
  2066 };
       
  2067 
       
  2068 
       
  2069 
       
  2070 L.CRS.EPSG3395 = L.extend({}, L.CRS, {
       
  2071 	code: 'EPSG:3395',
       
  2072 
       
  2073 	projection: L.Projection.Mercator,
       
  2074 
       
  2075 	transformation: (function () {
       
  2076 		var m = L.Projection.Mercator,
       
  2077 		    r = m.R_MAJOR,
       
  2078 		    r2 = m.R_MINOR;
       
  2079 
       
  2080 		return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
       
  2081 	}())
       
  2082 });
       
  2083 
       
  2084 
       
  2085 /*
       
  2086  * L.TileLayer is used for standard xyz-numbered tile layers.
       
  2087  */
       
  2088 
       
  2089 L.TileLayer = L.Class.extend({
       
  2090 	includes: L.Mixin.Events,
       
  2091 
       
  2092 	options: {
       
  2093 		minZoom: 0,
       
  2094 		maxZoom: 18,
       
  2095 		tileSize: 256,
       
  2096 		subdomains: 'abc',
       
  2097 		errorTileUrl: '',
       
  2098 		attribution: '',
       
  2099 		zoomOffset: 0,
       
  2100 		opacity: 1,
       
  2101 		/* (undefined works too)
       
  2102 		zIndex: null,
       
  2103 		tms: false,
       
  2104 		continuousWorld: false,
       
  2105 		noWrap: false,
       
  2106 		zoomReverse: false,
       
  2107 		detectRetina: false,
       
  2108 		reuseTiles: false,
       
  2109 		*/
       
  2110 		unloadInvisibleTiles: L.Browser.mobile,
       
  2111 		updateWhenIdle: L.Browser.mobile
       
  2112 	},
       
  2113 
       
  2114 	initialize: function (url, options) {
       
  2115 		options = L.setOptions(this, options);
       
  2116 
       
  2117 		// detecting retina displays, adjusting tileSize and zoom levels
       
  2118 		if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
       
  2119 
       
  2120 			options.tileSize = Math.floor(options.tileSize / 2);
       
  2121 			options.zoomOffset++;
       
  2122 
       
  2123 			if (options.minZoom > 0) {
       
  2124 				options.minZoom--;
       
  2125 			}
       
  2126 			this.options.maxZoom--;
       
  2127 		}
       
  2128 
       
  2129 		this._url = url;
       
  2130 
       
  2131 		var subdomains = this.options.subdomains;
       
  2132 
       
  2133 		if (typeof subdomains === 'string') {
       
  2134 			this.options.subdomains = subdomains.split('');
       
  2135 		}
       
  2136 	},
       
  2137 
       
  2138 	onAdd: function (map) {
       
  2139 		this._map = map;
       
  2140 
       
  2141 		// create a container div for tiles
       
  2142 		this._initContainer();
       
  2143 
       
  2144 		// create an image to clone for tiles
       
  2145 		this._createTileProto();
       
  2146 
       
  2147 		// set up events
       
  2148 		map.on({
       
  2149 			'viewreset': this._resetCallback,
       
  2150 			'moveend': this._update
       
  2151 		}, this);
       
  2152 
       
  2153 		if (!this.options.updateWhenIdle) {
       
  2154 			this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
       
  2155 			map.on('move', this._limitedUpdate, this);
       
  2156 		}
       
  2157 
       
  2158 		this._reset();
       
  2159 		this._update();
       
  2160 	},
       
  2161 
       
  2162 	addTo: function (map) {
       
  2163 		map.addLayer(this);
       
  2164 		return this;
       
  2165 	},
       
  2166 
       
  2167 	onRemove: function (map) {
       
  2168 		this._container.parentNode.removeChild(this._container);
       
  2169 
       
  2170 		map.off({
       
  2171 			'viewreset': this._resetCallback,
       
  2172 			'moveend': this._update
       
  2173 		}, this);
       
  2174 
       
  2175 		if (!this.options.updateWhenIdle) {
       
  2176 			map.off('move', this._limitedUpdate, this);
       
  2177 		}
       
  2178 
       
  2179 		this._container = null;
       
  2180 		this._map = null;
       
  2181 	},
       
  2182 
       
  2183 	bringToFront: function () {
       
  2184 		var pane = this._map._panes.tilePane;
       
  2185 
       
  2186 		if (this._container) {
       
  2187 			pane.appendChild(this._container);
       
  2188 			this._setAutoZIndex(pane, Math.max);
       
  2189 		}
       
  2190 
       
  2191 		return this;
       
  2192 	},
       
  2193 
       
  2194 	bringToBack: function () {
       
  2195 		var pane = this._map._panes.tilePane;
       
  2196 
       
  2197 		if (this._container) {
       
  2198 			pane.insertBefore(this._container, pane.firstChild);
       
  2199 			this._setAutoZIndex(pane, Math.min);
       
  2200 		}
       
  2201 
       
  2202 		return this;
       
  2203 	},
       
  2204 
       
  2205 	getAttribution: function () {
       
  2206 		return this.options.attribution;
       
  2207 	},
       
  2208 
       
  2209 	setOpacity: function (opacity) {
       
  2210 		this.options.opacity = opacity;
       
  2211 
       
  2212 		if (this._map) {
       
  2213 			this._updateOpacity();
       
  2214 		}
       
  2215 
       
  2216 		return this;
       
  2217 	},
       
  2218 
       
  2219 	setZIndex: function (zIndex) {
       
  2220 		this.options.zIndex = zIndex;
       
  2221 		this._updateZIndex();
       
  2222 
       
  2223 		return this;
       
  2224 	},
       
  2225 
       
  2226 	setUrl: function (url, noRedraw) {
       
  2227 		this._url = url;
       
  2228 
       
  2229 		if (!noRedraw) {
       
  2230 			this.redraw();
       
  2231 		}
       
  2232 
       
  2233 		return this;
       
  2234 	},
       
  2235 
       
  2236 	redraw: function () {
       
  2237 		if (this._map) {
       
  2238 			this._map._panes.tilePane.empty = false;
       
  2239 			this._reset(true);
       
  2240 			this._update();
       
  2241 		}
       
  2242 		return this;
       
  2243 	},
       
  2244 
       
  2245 	_updateZIndex: function () {
       
  2246 		if (this._container && this.options.zIndex !== undefined) {
       
  2247 			this._container.style.zIndex = this.options.zIndex;
       
  2248 		}
       
  2249 	},
       
  2250 
       
  2251 	_setAutoZIndex: function (pane, compare) {
       
  2252 
       
  2253 		var layers = pane.children,
       
  2254 		    edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
       
  2255 		    zIndex, i, len;
       
  2256 
       
  2257 		for (i = 0, len = layers.length; i < len; i++) {
       
  2258 
       
  2259 			if (layers[i] !== this._container) {
       
  2260 				zIndex = parseInt(layers[i].style.zIndex, 10);
       
  2261 
       
  2262 				if (!isNaN(zIndex)) {
       
  2263 					edgeZIndex = compare(edgeZIndex, zIndex);
       
  2264 				}
       
  2265 			}
       
  2266 		}
       
  2267 
       
  2268 		this.options.zIndex = this._container.style.zIndex =
       
  2269 		        (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
       
  2270 	},
       
  2271 
       
  2272 	_updateOpacity: function () {
       
  2273 		L.DomUtil.setOpacity(this._container, this.options.opacity);
       
  2274 
       
  2275 		// stupid webkit hack to force redrawing of tiles
       
  2276 		var i,
       
  2277 		    tiles = this._tiles;
       
  2278 
       
  2279 		if (L.Browser.webkit) {
       
  2280 			for (i in tiles) {
       
  2281 				if (tiles.hasOwnProperty(i)) {
       
  2282 					tiles[i].style.webkitTransform += ' translate(0,0)';
       
  2283 				}
       
  2284 			}
       
  2285 		}
       
  2286 	},
       
  2287 
       
  2288 	_initContainer: function () {
       
  2289 		var tilePane = this._map._panes.tilePane;
       
  2290 
       
  2291 		if (!this._container || tilePane.empty) {
       
  2292 			this._container = L.DomUtil.create('div', 'leaflet-layer');
       
  2293 
       
  2294 			this._updateZIndex();
       
  2295 
       
  2296 			tilePane.appendChild(this._container);
       
  2297 
       
  2298 			if (this.options.opacity < 1) {
       
  2299 				this._updateOpacity();
       
  2300 			}
       
  2301 		}
       
  2302 	},
       
  2303 
       
  2304 	_resetCallback: function (e) {
       
  2305 		this._reset(e.hard);
       
  2306 	},
       
  2307 
       
  2308 	_reset: function (clearOldContainer) {
       
  2309 		var tiles = this._tiles;
       
  2310 
       
  2311 		for (var key in tiles) {
       
  2312 			if (tiles.hasOwnProperty(key)) {
       
  2313 				this.fire('tileunload', {tile: tiles[key]});
       
  2314 			}
       
  2315 		}
       
  2316 
       
  2317 		this._tiles = {};
       
  2318 		this._tilesToLoad = 0;
       
  2319 
       
  2320 		if (this.options.reuseTiles) {
       
  2321 			this._unusedTiles = [];
       
  2322 		}
       
  2323 
       
  2324 		if (clearOldContainer && this._container) {
       
  2325 			this._container.innerHTML = "";
       
  2326 		}
       
  2327 
       
  2328 		this._initContainer();
       
  2329 	},
       
  2330 
       
  2331 	_update: function () {
       
  2332 
       
  2333 		if (!this._map) { return; }
       
  2334 
       
  2335 		var bounds = this._map.getPixelBounds(),
       
  2336 		    zoom = this._map.getZoom(),
       
  2337 		    tileSize = this.options.tileSize;
       
  2338 
       
  2339 		if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
       
  2340 			return;
       
  2341 		}
       
  2342 
       
  2343 		var nwTilePoint = new L.Point(
       
  2344 		        Math.floor(bounds.min.x / tileSize),
       
  2345 		        Math.floor(bounds.min.y / tileSize)),
       
  2346 
       
  2347 		    seTilePoint = new L.Point(
       
  2348 		        Math.floor(bounds.max.x / tileSize),
       
  2349 		        Math.floor(bounds.max.y / tileSize)),
       
  2350 
       
  2351 		    tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
       
  2352 
       
  2353 		this._addTilesFromCenterOut(tileBounds);
       
  2354 
       
  2355 		if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
       
  2356 			this._removeOtherTiles(tileBounds);
       
  2357 		}
       
  2358 	},
       
  2359 
       
  2360 	_addTilesFromCenterOut: function (bounds) {
       
  2361 		var queue = [],
       
  2362 		    center = bounds.getCenter();
       
  2363 
       
  2364 		var j, i, point;
       
  2365 
       
  2366 		for (j = bounds.min.y; j <= bounds.max.y; j++) {
       
  2367 			for (i = bounds.min.x; i <= bounds.max.x; i++) {
       
  2368 				point = new L.Point(i, j);
       
  2369 
       
  2370 				if (this._tileShouldBeLoaded(point)) {
       
  2371 					queue.push(point);
       
  2372 				}
       
  2373 			}
       
  2374 		}
       
  2375 
       
  2376 		var tilesToLoad = queue.length;
       
  2377 
       
  2378 		if (tilesToLoad === 0) { return; }
       
  2379 
       
  2380 		// load tiles in order of their distance to center
       
  2381 		queue.sort(function (a, b) {
       
  2382 			return a.distanceTo(center) - b.distanceTo(center);
       
  2383 		});
       
  2384 
       
  2385 		var fragment = document.createDocumentFragment();
       
  2386 
       
  2387 		// if its the first batch of tiles to load
       
  2388 		if (!this._tilesToLoad) {
       
  2389 			this.fire('loading');
       
  2390 		}
       
  2391 
       
  2392 		this._tilesToLoad += tilesToLoad;
       
  2393 
       
  2394 		for (i = 0; i < tilesToLoad; i++) {
       
  2395 			this._addTile(queue[i], fragment);
       
  2396 		}
       
  2397 
       
  2398 		this._container.appendChild(fragment);
       
  2399 	},
       
  2400 
       
  2401 	_tileShouldBeLoaded: function (tilePoint) {
       
  2402 		if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
       
  2403 			return false; // already loaded
       
  2404 		}
       
  2405 
       
  2406 		if (!this.options.continuousWorld) {
       
  2407 			var limit = this._getWrapTileNum();
       
  2408 
       
  2409 			if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
       
  2410 				                        tilePoint.y < 0 || tilePoint.y >= limit) {
       
  2411 				return false; // exceeds world bounds
       
  2412 			}
       
  2413 		}
       
  2414 
       
  2415 		return true;
       
  2416 	},
       
  2417 
       
  2418 	_removeOtherTiles: function (bounds) {
       
  2419 		var kArr, x, y, key;
       
  2420 
       
  2421 		for (key in this._tiles) {
       
  2422 			if (this._tiles.hasOwnProperty(key)) {
       
  2423 				kArr = key.split(':');
       
  2424 				x = parseInt(kArr[0], 10);
       
  2425 				y = parseInt(kArr[1], 10);
       
  2426 
       
  2427 				// remove tile if it's out of bounds
       
  2428 				if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
       
  2429 					this._removeTile(key);
       
  2430 				}
       
  2431 			}
       
  2432 		}
       
  2433 	},
       
  2434 
       
  2435 	_removeTile: function (key) {
       
  2436 		var tile = this._tiles[key];
       
  2437 
       
  2438 		this.fire("tileunload", {tile: tile, url: tile.src});
       
  2439 
       
  2440 		if (this.options.reuseTiles) {
       
  2441 			L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
       
  2442 			this._unusedTiles.push(tile);
       
  2443 
       
  2444 		} else if (tile.parentNode === this._container) {
       
  2445 			this._container.removeChild(tile);
       
  2446 		}
       
  2447 
       
  2448 		// for https://github.com/CloudMade/Leaflet/issues/137
       
  2449 		if (!L.Browser.android) {
       
  2450 			tile.src = L.Util.emptyImageUrl;
       
  2451 		}
       
  2452 
       
  2453 		delete this._tiles[key];
       
  2454 	},
       
  2455 
       
  2456 	_addTile: function (tilePoint, container) {
       
  2457 		var tilePos = this._getTilePos(tilePoint);
       
  2458 
       
  2459 		// get unused tile - or create a new tile
       
  2460 		var tile = this._getTile();
       
  2461 
       
  2462 		/*
       
  2463 		Chrome 20 layouts much faster with top/left (verify with timeline, frames)
       
  2464 		Android 4 browser has display issues with top/left and requires transform instead
       
  2465 		Android 3 browser not tested
       
  2466 		Android 2 browser requires top/left or tiles disappear on load or first drag
       
  2467 		(reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
       
  2468 		(other browsers don't currently care) - see debug/hacks/jitter.html for an example
       
  2469 		*/
       
  2470 		L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
       
  2471 
       
  2472 		this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
       
  2473 
       
  2474 		this._loadTile(tile, tilePoint);
       
  2475 
       
  2476 		if (tile.parentNode !== this._container) {
       
  2477 			container.appendChild(tile);
       
  2478 		}
       
  2479 	},
       
  2480 
       
  2481 	_getZoomForUrl: function () {
       
  2482 
       
  2483 		var options = this.options,
       
  2484 		    zoom = this._map.getZoom();
       
  2485 
       
  2486 		if (options.zoomReverse) {
       
  2487 			zoom = options.maxZoom - zoom;
       
  2488 		}
       
  2489 
       
  2490 		return zoom + options.zoomOffset;
       
  2491 	},
       
  2492 
       
  2493 	_getTilePos: function (tilePoint) {
       
  2494 		var origin = this._map.getPixelOrigin(),
       
  2495 		    tileSize = this.options.tileSize;
       
  2496 
       
  2497 		return tilePoint.multiplyBy(tileSize).subtract(origin);
       
  2498 	},
       
  2499 
       
  2500 	// image-specific code (override to implement e.g. Canvas or SVG tile layer)
       
  2501 
       
  2502 	getTileUrl: function (tilePoint) {
       
  2503 		this._adjustTilePoint(tilePoint);
       
  2504 
       
  2505 		return L.Util.template(this._url, L.extend({
       
  2506 			s: this._getSubdomain(tilePoint),
       
  2507 			z: this._getZoomForUrl(),
       
  2508 			x: tilePoint.x,
       
  2509 			y: tilePoint.y
       
  2510 		}, this.options));
       
  2511 	},
       
  2512 
       
  2513 	_getWrapTileNum: function () {
       
  2514 		// TODO refactor, limit is not valid for non-standard projections
       
  2515 		return Math.pow(2, this._getZoomForUrl());
       
  2516 	},
       
  2517 
       
  2518 	_adjustTilePoint: function (tilePoint) {
       
  2519 
       
  2520 		var limit = this._getWrapTileNum();
       
  2521 
       
  2522 		// wrap tile coordinates
       
  2523 		if (!this.options.continuousWorld && !this.options.noWrap) {
       
  2524 			tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
       
  2525 		}
       
  2526 
       
  2527 		if (this.options.tms) {
       
  2528 			tilePoint.y = limit - tilePoint.y - 1;
       
  2529 		}
       
  2530 	},
       
  2531 
       
  2532 	_getSubdomain: function (tilePoint) {
       
  2533 		var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
       
  2534 		return this.options.subdomains[index];
       
  2535 	},
       
  2536 
       
  2537 	_createTileProto: function () {
       
  2538 		var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
       
  2539 		img.style.width = img.style.height = this.options.tileSize + 'px';
       
  2540 		img.galleryimg = 'no';
       
  2541 	},
       
  2542 
       
  2543 	_getTile: function () {
       
  2544 		if (this.options.reuseTiles && this._unusedTiles.length > 0) {
       
  2545 			var tile = this._unusedTiles.pop();
       
  2546 			this._resetTile(tile);
       
  2547 			return tile;
       
  2548 		}
       
  2549 		return this._createTile();
       
  2550 	},
       
  2551 
       
  2552 	// Override if data stored on a tile needs to be cleaned up before reuse
       
  2553 	_resetTile: function (/*tile*/) {},
       
  2554 
       
  2555 	_createTile: function () {
       
  2556 		var tile = this._tileImg.cloneNode(false);
       
  2557 		tile.onselectstart = tile.onmousemove = L.Util.falseFn;
       
  2558 		return tile;
       
  2559 	},
       
  2560 
       
  2561 	_loadTile: function (tile, tilePoint) {
       
  2562 		tile._layer  = this;
       
  2563 		tile.onload  = this._tileOnLoad;
       
  2564 		tile.onerror = this._tileOnError;
       
  2565 
       
  2566 		tile.src     = this.getTileUrl(tilePoint);
       
  2567 	},
       
  2568 
       
  2569     _tileLoaded: function () {
       
  2570         this._tilesToLoad--;
       
  2571         if (!this._tilesToLoad) {
       
  2572             this.fire('load');
       
  2573         }
       
  2574     },
       
  2575 
       
  2576 	_tileOnLoad: function () {
       
  2577 		var layer = this._layer;
       
  2578 
       
  2579 		//Only if we are loading an actual image
       
  2580 		if (this.src !== L.Util.emptyImageUrl) {
       
  2581 			L.DomUtil.addClass(this, 'leaflet-tile-loaded');
       
  2582 
       
  2583 			layer.fire('tileload', {
       
  2584 				tile: this,
       
  2585 				url: this.src
       
  2586 			});
       
  2587 		}
       
  2588 
       
  2589 		layer._tileLoaded();
       
  2590 	},
       
  2591 
       
  2592 	_tileOnError: function () {
       
  2593 		var layer = this._layer;
       
  2594 
       
  2595 		layer.fire('tileerror', {
       
  2596 			tile: this,
       
  2597 			url: this.src
       
  2598 		});
       
  2599 
       
  2600 		var newUrl = layer.options.errorTileUrl;
       
  2601 		if (newUrl) {
       
  2602 			this.src = newUrl;
       
  2603 		}
       
  2604 
       
  2605         layer._tileLoaded();
       
  2606     }
       
  2607 });
       
  2608 
       
  2609 L.tileLayer = function (url, options) {
       
  2610 	return new L.TileLayer(url, options);
       
  2611 };
       
  2612 
       
  2613 
       
  2614 /*
       
  2615  * L.TileLayer.WMS is used for putting WMS tile layers on the map.
       
  2616  */
       
  2617 
       
  2618 L.TileLayer.WMS = L.TileLayer.extend({
       
  2619 
       
  2620 	defaultWmsParams: {
       
  2621 		service: 'WMS',
       
  2622 		request: 'GetMap',
       
  2623 		version: '1.1.1',
       
  2624 		layers: '',
       
  2625 		styles: '',
       
  2626 		format: 'image/jpeg',
       
  2627 		transparent: false
       
  2628 	},
       
  2629 
       
  2630 	initialize: function (url, options) { // (String, Object)
       
  2631 
       
  2632 		this._url = url;
       
  2633 
       
  2634 		var wmsParams = L.extend({}, this.defaultWmsParams);
       
  2635 
       
  2636 		if (options.detectRetina && L.Browser.retina) {
       
  2637 			wmsParams.width = wmsParams.height = this.options.tileSize * 2;
       
  2638 		} else {
       
  2639 			wmsParams.width = wmsParams.height = this.options.tileSize;
       
  2640 		}
       
  2641 
       
  2642 		for (var i in options) {
       
  2643 			// all keys that are not TileLayer options go to WMS params
       
  2644 			if (!this.options.hasOwnProperty(i)) {
       
  2645 				wmsParams[i] = options[i];
       
  2646 			}
       
  2647 		}
       
  2648 
       
  2649 		this.wmsParams = wmsParams;
       
  2650 
       
  2651 		L.setOptions(this, options);
       
  2652 	},
       
  2653 
       
  2654 	onAdd: function (map) {
       
  2655 
       
  2656 		var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
       
  2657 		this.wmsParams[projectionKey] = map.options.crs.code;
       
  2658 
       
  2659 		L.TileLayer.prototype.onAdd.call(this, map);
       
  2660 	},
       
  2661 
       
  2662 	getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
       
  2663 
       
  2664 		this._adjustTilePoint(tilePoint);
       
  2665 
       
  2666 		var map = this._map,
       
  2667 		    crs = map.options.crs,
       
  2668 		    tileSize = this.options.tileSize,
       
  2669 
       
  2670 		    nwPoint = tilePoint.multiplyBy(tileSize),
       
  2671 		    sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
       
  2672 
       
  2673 		    nw = crs.project(map.unproject(nwPoint, zoom)),
       
  2674 		    se = crs.project(map.unproject(sePoint, zoom)),
       
  2675 
       
  2676 		    bbox = [nw.x, se.y, se.x, nw.y].join(','),
       
  2677 
       
  2678 		    url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
       
  2679 
       
  2680 		return url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox;
       
  2681 	},
       
  2682 
       
  2683 	setParams: function (params, noRedraw) {
       
  2684 
       
  2685 		L.extend(this.wmsParams, params);
       
  2686 
       
  2687 		if (!noRedraw) {
       
  2688 			this.redraw();
       
  2689 		}
       
  2690 
       
  2691 		return this;
       
  2692 	}
       
  2693 });
       
  2694 
       
  2695 L.tileLayer.wms = function (url, options) {
       
  2696 	return new L.TileLayer.WMS(url, options);
       
  2697 };
       
  2698 
       
  2699 
       
  2700 /*
       
  2701  * L.TileLayer.Canvas is a class that you can use as a base for creating
       
  2702  * dynamically drawn Canvas-based tile layers.
       
  2703  */
       
  2704 
       
  2705 L.TileLayer.Canvas = L.TileLayer.extend({
       
  2706 	options: {
       
  2707 		async: false
       
  2708 	},
       
  2709 
       
  2710 	initialize: function (options) {
       
  2711 		L.setOptions(this, options);
       
  2712 	},
       
  2713 
       
  2714 	redraw: function () {
       
  2715 		var tiles = this._tiles;
       
  2716 
       
  2717 		for (var i in tiles) {
       
  2718 			if (tiles.hasOwnProperty(i)) {
       
  2719 				this._redrawTile(tiles[i]);
       
  2720 			}
       
  2721 		}
       
  2722 	},
       
  2723 
       
  2724 	_redrawTile: function (tile) {
       
  2725 		this.drawTile(tile, tile._tilePoint, this._map._zoom);
       
  2726 	},
       
  2727 
       
  2728 	_createTileProto: function () {
       
  2729 		var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
       
  2730 		proto.width = proto.height = this.options.tileSize;
       
  2731 	},
       
  2732 
       
  2733 	_createTile: function () {
       
  2734 		var tile = this._canvasProto.cloneNode(false);
       
  2735 		tile.onselectstart = tile.onmousemove = L.Util.falseFn;
       
  2736 		return tile;
       
  2737 	},
       
  2738 
       
  2739 	_loadTile: function (tile, tilePoint) {
       
  2740 		tile._layer = this;
       
  2741 		tile._tilePoint = tilePoint;
       
  2742 
       
  2743 		this._redrawTile(tile);
       
  2744 
       
  2745 		if (!this.options.async) {
       
  2746 			this.tileDrawn(tile);
       
  2747 		}
       
  2748 	},
       
  2749 
       
  2750 	drawTile: function (/*tile, tilePoint*/) {
       
  2751 		// override with rendering code
       
  2752 	},
       
  2753 
       
  2754 	tileDrawn: function (tile) {
       
  2755 		this._tileOnLoad.call(tile);
       
  2756 	}
       
  2757 });
       
  2758 
       
  2759 
       
  2760 L.tileLayer.canvas = function (options) {
       
  2761 	return new L.TileLayer.Canvas(options);
       
  2762 };
       
  2763 
       
  2764 
       
  2765 /*
       
  2766  * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
       
  2767  */
       
  2768 
       
  2769 L.ImageOverlay = L.Class.extend({
       
  2770 	includes: L.Mixin.Events,
       
  2771 
       
  2772 	options: {
       
  2773 		opacity: 1
       
  2774 	},
       
  2775 
       
  2776 	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
       
  2777 		this._url = url;
       
  2778 		this._bounds = L.latLngBounds(bounds);
       
  2779 
       
  2780 		L.setOptions(this, options);
       
  2781 	},
       
  2782 
       
  2783 	onAdd: function (map) {
       
  2784 		this._map = map;
       
  2785 
       
  2786 		if (!this._image) {
       
  2787 			this._initImage();
       
  2788 		}
       
  2789 
       
  2790 		map._panes.overlayPane.appendChild(this._image);
       
  2791 
       
  2792 		map.on('viewreset', this._reset, this);
       
  2793 
       
  2794 		if (map.options.zoomAnimation && L.Browser.any3d) {
       
  2795 			map.on('zoomanim', this._animateZoom, this);
       
  2796 		}
       
  2797 
       
  2798 		this._reset();
       
  2799 	},
       
  2800 
       
  2801 	onRemove: function (map) {
       
  2802 		map.getPanes().overlayPane.removeChild(this._image);
       
  2803 
       
  2804 		map.off('viewreset', this._reset, this);
       
  2805 
       
  2806 		if (map.options.zoomAnimation) {
       
  2807 			map.off('zoomanim', this._animateZoom, this);
       
  2808 		}
       
  2809 	},
       
  2810 
       
  2811 	addTo: function (map) {
       
  2812 		map.addLayer(this);
       
  2813 		return this;
       
  2814 	},
       
  2815 
       
  2816 	setOpacity: function (opacity) {
       
  2817 		this.options.opacity = opacity;
       
  2818 		this._updateOpacity();
       
  2819 		return this;
       
  2820 	},
       
  2821 
       
  2822 	// TODO remove bringToFront/bringToBack duplication from TileLayer/Path
       
  2823 	bringToFront: function () {
       
  2824 		if (this._image) {
       
  2825 			this._map._panes.overlayPane.appendChild(this._image);
       
  2826 		}
       
  2827 		return this;
       
  2828 	},
       
  2829 
       
  2830 	bringToBack: function () {
       
  2831 		var pane = this._map._panes.overlayPane;
       
  2832 		if (this._image) {
       
  2833 			pane.insertBefore(this._image, pane.firstChild);
       
  2834 		}
       
  2835 		return this;
       
  2836 	},
       
  2837 
       
  2838 	_initImage: function () {
       
  2839 		this._image = L.DomUtil.create('img', 'leaflet-image-layer');
       
  2840 
       
  2841 		if (this._map.options.zoomAnimation && L.Browser.any3d) {
       
  2842 			L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
       
  2843 		} else {
       
  2844 			L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
       
  2845 		}
       
  2846 
       
  2847 		this._updateOpacity();
       
  2848 
       
  2849 		//TODO createImage util method to remove duplication
       
  2850 		L.extend(this._image, {
       
  2851 			galleryimg: 'no',
       
  2852 			onselectstart: L.Util.falseFn,
       
  2853 			onmousemove: L.Util.falseFn,
       
  2854 			onload: L.bind(this._onImageLoad, this),
       
  2855 			src: this._url
       
  2856 		});
       
  2857 	},
       
  2858 
       
  2859 	_animateZoom: function (e) {
       
  2860 		var map = this._map,
       
  2861 		    image = this._image,
       
  2862 		    scale = map.getZoomScale(e.zoom),
       
  2863 		    nw = this._bounds.getNorthWest(),
       
  2864 		    se = this._bounds.getSouthEast(),
       
  2865 
       
  2866 		    topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
       
  2867 		    size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
       
  2868 		    origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
       
  2869 
       
  2870 		image.style[L.DomUtil.TRANSFORM] =
       
  2871 		        L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
       
  2872 	},
       
  2873 
       
  2874 	_reset: function () {
       
  2875 		var image   = this._image,
       
  2876 		    topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
       
  2877 		    size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
       
  2878 
       
  2879 		L.DomUtil.setPosition(image, topLeft);
       
  2880 
       
  2881 		image.style.width  = size.x + 'px';
       
  2882 		image.style.height = size.y + 'px';
       
  2883 	},
       
  2884 
       
  2885 	_onImageLoad: function () {
       
  2886 		this.fire('load');
       
  2887 	},
       
  2888 
       
  2889 	_updateOpacity: function () {
       
  2890 		L.DomUtil.setOpacity(this._image, this.options.opacity);
       
  2891 	}
       
  2892 });
       
  2893 
       
  2894 L.imageOverlay = function (url, bounds, options) {
       
  2895 	return new L.ImageOverlay(url, bounds, options);
       
  2896 };
       
  2897 
       
  2898 
       
  2899 /*
       
  2900  * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
       
  2901  */
       
  2902 
       
  2903 L.Icon = L.Class.extend({
       
  2904 	options: {
       
  2905 		/*
       
  2906 		iconUrl: (String) (required)
       
  2907 		iconRetinaUrl: (String) (optional, used for retina devices if detected)
       
  2908 		iconSize: (Point) (can be set through CSS)
       
  2909 		iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
       
  2910 		popupAnchor: (Point) (if not specified, popup opens in the anchor point)
       
  2911 		shadowUrl: (Point) (no shadow by default)
       
  2912 		shadowRetinaUrl: (String) (optional, used for retina devices if detected)
       
  2913 		shadowSize: (Point)
       
  2914 		shadowAnchor: (Point)
       
  2915 		*/
       
  2916 		className: ''
       
  2917 	},
       
  2918 
       
  2919 	initialize: function (options) {
       
  2920 		L.setOptions(this, options);
       
  2921 	},
       
  2922 
       
  2923 	createIcon: function () {
       
  2924 		return this._createIcon('icon');
       
  2925 	},
       
  2926 
       
  2927 	createShadow: function () {
       
  2928 		return this._createIcon('shadow');
       
  2929 	},
       
  2930 
       
  2931 	_createIcon: function (name) {
       
  2932 		var src = this._getIconUrl(name);
       
  2933 
       
  2934 		if (!src) {
       
  2935 			if (name === 'icon') {
       
  2936 				throw new Error("iconUrl not set in Icon options (see the docs).");
       
  2937 			}
       
  2938 			return null;
       
  2939 		}
       
  2940 
       
  2941 		var img = this._createImg(src);
       
  2942 		this._setIconStyles(img, name);
       
  2943 
       
  2944 		return img;
       
  2945 	},
       
  2946 
       
  2947 	_setIconStyles: function (img, name) {
       
  2948 		var options = this.options,
       
  2949 		    size = L.point(options[name + 'Size']),
       
  2950 		    anchor;
       
  2951 
       
  2952 		if (name === 'shadow') {
       
  2953 			anchor = L.point(options.shadowAnchor || options.iconAnchor);
       
  2954 		} else {
       
  2955 			anchor = L.point(options.iconAnchor);
       
  2956 		}
       
  2957 
       
  2958 		if (!anchor && size) {
       
  2959 			anchor = size.divideBy(2, true);
       
  2960 		}
       
  2961 
       
  2962 		img.className = 'leaflet-marker-' + name + ' ' + options.className;
       
  2963 
       
  2964 		if (anchor) {
       
  2965 			img.style.marginLeft = (-anchor.x) + 'px';
       
  2966 			img.style.marginTop  = (-anchor.y) + 'px';
       
  2967 		}
       
  2968 
       
  2969 		if (size) {
       
  2970 			img.style.width  = size.x + 'px';
       
  2971 			img.style.height = size.y + 'px';
       
  2972 		}
       
  2973 	},
       
  2974 
       
  2975 	_createImg: function (src) {
       
  2976 		var el;
       
  2977 
       
  2978 		if (!L.Browser.ie6) {
       
  2979 			el = document.createElement('img');
       
  2980 			el.src = src;
       
  2981 		} else {
       
  2982 			el = document.createElement('div');
       
  2983 			el.style.filter =
       
  2984 			        'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
       
  2985 		}
       
  2986 		return el;
       
  2987 	},
       
  2988 
       
  2989 	_getIconUrl: function (name) {
       
  2990 		if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
       
  2991 			return this.options[name + 'RetinaUrl'];
       
  2992 		}
       
  2993 		return this.options[name + 'Url'];
       
  2994 	}
       
  2995 });
       
  2996 
       
  2997 L.icon = function (options) {
       
  2998 	return new L.Icon(options);
       
  2999 };
       
  3000 
       
  3001 
       
  3002 /*
       
  3003  * L.Icon.Default is the blue marker icon used by default in Leaflet.
       
  3004  */
       
  3005 
       
  3006 L.Icon.Default = L.Icon.extend({
       
  3007 
       
  3008 	options: {
       
  3009 		iconSize: new L.Point(25, 41),
       
  3010 		iconAnchor: new L.Point(12, 41),
       
  3011 		popupAnchor: new L.Point(1, -34),
       
  3012 
       
  3013 		shadowSize: new L.Point(41, 41)
       
  3014 	},
       
  3015 
       
  3016 	_getIconUrl: function (name) {
       
  3017 		var key = name + 'Url';
       
  3018 
       
  3019 		if (this.options[key]) {
       
  3020 			return this.options[key];
       
  3021 		}
       
  3022 
       
  3023 		if (L.Browser.retina && name === 'icon') {
       
  3024 			name += '@2x';
       
  3025 		}
       
  3026 
       
  3027 		var path = L.Icon.Default.imagePath;
       
  3028 
       
  3029 		if (!path) {
       
  3030 			throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
       
  3031 		}
       
  3032 
       
  3033 		return path + '/marker-' + name + '.png';
       
  3034 	}
       
  3035 });
       
  3036 
       
  3037 L.Icon.Default.imagePath = (function () {
       
  3038 	var scripts = document.getElementsByTagName('script'),
       
  3039 	    leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
       
  3040 
       
  3041 	var i, len, src, matches;
       
  3042 
       
  3043 	for (i = 0, len = scripts.length; i < len; i++) {
       
  3044 		src = scripts[i].src;
       
  3045 		matches = src.match(leafletRe);
       
  3046 
       
  3047 		if (matches) {
       
  3048 			return src.split(leafletRe)[0] + '/images';
       
  3049 		}
       
  3050 	}
       
  3051 }());
       
  3052 
       
  3053 
       
  3054 /*
       
  3055  * L.Marker is used to display clickable/draggable icons on the map.
       
  3056  */
       
  3057 
       
  3058 L.Marker = L.Class.extend({
       
  3059 
       
  3060 	includes: L.Mixin.Events,
       
  3061 
       
  3062 	options: {
       
  3063 		icon: new L.Icon.Default(),
       
  3064 		title: '',
       
  3065 		clickable: true,
       
  3066 		draggable: false,
       
  3067 		zIndexOffset: 0,
       
  3068 		opacity: 1,
       
  3069 		riseOnHover: false,
       
  3070 		riseOffset: 250
       
  3071 	},
       
  3072 
       
  3073 	initialize: function (latlng, options) {
       
  3074 		L.setOptions(this, options);
       
  3075 		this._latlng = L.latLng(latlng);
       
  3076 	},
       
  3077 
       
  3078 	onAdd: function (map) {
       
  3079 		this._map = map;
       
  3080 
       
  3081 		map.on('viewreset', this.update, this);
       
  3082 
       
  3083 		this._initIcon();
       
  3084 		this.update();
       
  3085 
       
  3086 		if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
       
  3087 			map.on('zoomanim', this._animateZoom, this);
       
  3088 		}
       
  3089 	},
       
  3090 
       
  3091 	addTo: function (map) {
       
  3092 		map.addLayer(this);
       
  3093 		return this;
       
  3094 	},
       
  3095 
       
  3096 	onRemove: function (map) {
       
  3097 		this._removeIcon();
       
  3098 
       
  3099 		this.fire('remove');
       
  3100 
       
  3101 		map.off({
       
  3102 			'viewreset': this.update,
       
  3103 			'zoomanim': this._animateZoom
       
  3104 		}, this);
       
  3105 
       
  3106 		this._map = null;
       
  3107 	},
       
  3108 
       
  3109 	getLatLng: function () {
       
  3110 		return this._latlng;
       
  3111 	},
       
  3112 
       
  3113 	setLatLng: function (latlng) {
       
  3114 		this._latlng = L.latLng(latlng);
       
  3115 
       
  3116 		this.update();
       
  3117 
       
  3118 		return this.fire('move', { latlng: this._latlng });
       
  3119 	},
       
  3120 
       
  3121 	setZIndexOffset: function (offset) {
       
  3122 		this.options.zIndexOffset = offset;
       
  3123 		this.update();
       
  3124 
       
  3125 		return this;
       
  3126 	},
       
  3127 
       
  3128 	setIcon: function (icon) {
       
  3129 		if (this._map) {
       
  3130 			this._removeIcon();
       
  3131 		}
       
  3132 
       
  3133 		this.options.icon = icon;
       
  3134 
       
  3135 		if (this._map) {
       
  3136 			this._initIcon();
       
  3137 			this.update();
       
  3138 		}
       
  3139 
       
  3140 		return this;
       
  3141 	},
       
  3142 
       
  3143 	update: function () {
       
  3144 		if (this._icon) {
       
  3145 			var pos = this._map.latLngToLayerPoint(this._latlng).round();
       
  3146 			this._setPos(pos);
       
  3147 		}
       
  3148 
       
  3149 		return this;
       
  3150 	},
       
  3151 
       
  3152 	_initIcon: function () {
       
  3153 		var options = this.options,
       
  3154 		    map = this._map,
       
  3155 		    animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
       
  3156 		    classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
       
  3157 		    needOpacityUpdate = false;
       
  3158 
       
  3159 		if (!this._icon) {
       
  3160 			this._icon = options.icon.createIcon();
       
  3161 
       
  3162 			if (options.title) {
       
  3163 				this._icon.title = options.title;
       
  3164 			}
       
  3165 
       
  3166 			this._initInteraction();
       
  3167 			needOpacityUpdate = (this.options.opacity < 1);
       
  3168 
       
  3169 			L.DomUtil.addClass(this._icon, classToAdd);
       
  3170 
       
  3171 			if (options.riseOnHover) {
       
  3172 				L.DomEvent
       
  3173 					.on(this._icon, 'mouseover', this._bringToFront, this)
       
  3174 					.on(this._icon, 'mouseout', this._resetZIndex, this);
       
  3175 			}
       
  3176 		}
       
  3177 
       
  3178 		if (!this._shadow) {
       
  3179 			this._shadow = options.icon.createShadow();
       
  3180 
       
  3181 			if (this._shadow) {
       
  3182 				L.DomUtil.addClass(this._shadow, classToAdd);
       
  3183 				needOpacityUpdate = (this.options.opacity < 1);
       
  3184 			}
       
  3185 		}
       
  3186 
       
  3187 		if (needOpacityUpdate) {
       
  3188 			this._updateOpacity();
       
  3189 		}
       
  3190 
       
  3191 		var panes = this._map._panes;
       
  3192 
       
  3193 		panes.markerPane.appendChild(this._icon);
       
  3194 
       
  3195 		if (this._shadow) {
       
  3196 			panes.shadowPane.appendChild(this._shadow);
       
  3197 		}
       
  3198 	},
       
  3199 
       
  3200 	_removeIcon: function () {
       
  3201 		var panes = this._map._panes;
       
  3202 
       
  3203 		if (this.options.riseOnHover) {
       
  3204 			L.DomEvent
       
  3205 			    .off(this._icon, 'mouseover', this._bringToFront)
       
  3206 			    .off(this._icon, 'mouseout', this._resetZIndex);
       
  3207 		}
       
  3208 
       
  3209 		panes.markerPane.removeChild(this._icon);
       
  3210 
       
  3211 		if (this._shadow) {
       
  3212 			panes.shadowPane.removeChild(this._shadow);
       
  3213 		}
       
  3214 
       
  3215 		this._icon = this._shadow = null;
       
  3216 	},
       
  3217 
       
  3218 	_setPos: function (pos) {
       
  3219 		L.DomUtil.setPosition(this._icon, pos);
       
  3220 
       
  3221 		if (this._shadow) {
       
  3222 			L.DomUtil.setPosition(this._shadow, pos);
       
  3223 		}
       
  3224 
       
  3225 		this._zIndex = pos.y + this.options.zIndexOffset;
       
  3226 
       
  3227 		this._resetZIndex();
       
  3228 	},
       
  3229 
       
  3230 	_updateZIndex: function (offset) {
       
  3231 		this._icon.style.zIndex = this._zIndex + offset;
       
  3232 	},
       
  3233 
       
  3234 	_animateZoom: function (opt) {
       
  3235 		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
       
  3236 
       
  3237 		this._setPos(pos);
       
  3238 	},
       
  3239 
       
  3240 	_initInteraction: function () {
       
  3241 
       
  3242 		if (!this.options.clickable) { return; }
       
  3243 
       
  3244 		// TODO refactor into something shared with Map/Path/etc. to DRY it up
       
  3245 
       
  3246 		var icon = this._icon,
       
  3247 		    events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
       
  3248 
       
  3249 		L.DomUtil.addClass(icon, 'leaflet-clickable');
       
  3250 		L.DomEvent.on(icon, 'click', this._onMouseClick, this);
       
  3251 
       
  3252 		for (var i = 0; i < events.length; i++) {
       
  3253 			L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
       
  3254 		}
       
  3255 
       
  3256 		if (L.Handler.MarkerDrag) {
       
  3257 			this.dragging = new L.Handler.MarkerDrag(this);
       
  3258 
       
  3259 			if (this.options.draggable) {
       
  3260 				this.dragging.enable();
       
  3261 			}
       
  3262 		}
       
  3263 	},
       
  3264 
       
  3265 	_onMouseClick: function (e) {
       
  3266 		var wasDragged = this.dragging && this.dragging.moved();
       
  3267 
       
  3268 		if (this.hasEventListeners(e.type) || wasDragged) {
       
  3269 			L.DomEvent.stopPropagation(e);
       
  3270 		}
       
  3271 
       
  3272 		if (wasDragged) { return; }
       
  3273 
       
  3274 		if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
       
  3275 
       
  3276 		this.fire(e.type, {
       
  3277 			originalEvent: e
       
  3278 		});
       
  3279 	},
       
  3280 
       
  3281 	_fireMouseEvent: function (e) {
       
  3282 
       
  3283 		this.fire(e.type, {
       
  3284 			originalEvent: e
       
  3285 		});
       
  3286 
       
  3287 		// TODO proper custom event propagation
       
  3288 		// this line will always be called if marker is in a FeatureGroup
       
  3289 		if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
       
  3290 			L.DomEvent.preventDefault(e);
       
  3291 		}
       
  3292 		if (e.type !== 'mousedown') {
       
  3293 			L.DomEvent.stopPropagation(e);
       
  3294 		}
       
  3295 	},
       
  3296 
       
  3297 	setOpacity: function (opacity) {
       
  3298 		this.options.opacity = opacity;
       
  3299 		if (this._map) {
       
  3300 			this._updateOpacity();
       
  3301 		}
       
  3302 	},
       
  3303 
       
  3304 	_updateOpacity: function () {
       
  3305 		L.DomUtil.setOpacity(this._icon, this.options.opacity);
       
  3306 		if (this._shadow) {
       
  3307 			L.DomUtil.setOpacity(this._shadow, this.options.opacity);
       
  3308 		}
       
  3309 	},
       
  3310 
       
  3311 	_bringToFront: function () {
       
  3312 		this._updateZIndex(this.options.riseOffset);
       
  3313 	},
       
  3314 
       
  3315 	_resetZIndex: function () {
       
  3316 		this._updateZIndex(0);
       
  3317 	}
       
  3318 });
       
  3319 
       
  3320 L.marker = function (latlng, options) {
       
  3321 	return new L.Marker(latlng, options);
       
  3322 };
       
  3323 
       
  3324 
       
  3325 /*
       
  3326  * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
       
  3327  * to use with L.Marker.
       
  3328  */
       
  3329 
       
  3330 L.DivIcon = L.Icon.extend({
       
  3331 	options: {
       
  3332 		iconSize: new L.Point(12, 12), // also can be set through CSS
       
  3333 		/*
       
  3334 		iconAnchor: (Point)
       
  3335 		popupAnchor: (Point)
       
  3336 		html: (String)
       
  3337 		bgPos: (Point)
       
  3338 		*/
       
  3339 		className: 'leaflet-div-icon'
       
  3340 	},
       
  3341 
       
  3342 	createIcon: function () {
       
  3343 		var div = document.createElement('div'),
       
  3344 		    options = this.options;
       
  3345 
       
  3346 		if (options.html) {
       
  3347 			div.innerHTML = options.html;
       
  3348 		}
       
  3349 
       
  3350 		if (options.bgPos) {
       
  3351 			div.style.backgroundPosition =
       
  3352 			        (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
       
  3353 		}
       
  3354 
       
  3355 		this._setIconStyles(div, 'icon');
       
  3356 		return div;
       
  3357 	},
       
  3358 
       
  3359 	createShadow: function () {
       
  3360 		return null;
       
  3361 	}
       
  3362 });
       
  3363 
       
  3364 L.divIcon = function (options) {
       
  3365 	return new L.DivIcon(options);
       
  3366 };
       
  3367 
       
  3368 
       
  3369 /*
       
  3370  * L.Popup is used for displaying popups on the map.
       
  3371  */
       
  3372 
       
  3373 L.Map.mergeOptions({
       
  3374 	closePopupOnClick: true
       
  3375 });
       
  3376 
       
  3377 L.Popup = L.Class.extend({
       
  3378 	includes: L.Mixin.Events,
       
  3379 
       
  3380 	options: {
       
  3381 		minWidth: 50,
       
  3382 		maxWidth: 300,
       
  3383 		maxHeight: null,
       
  3384 		autoPan: true,
       
  3385 		closeButton: true,
       
  3386 		offset: new L.Point(0, 6),
       
  3387 		autoPanPadding: new L.Point(5, 5),
       
  3388 		className: '',
       
  3389 		zoomAnimation: true
       
  3390 	},
       
  3391 
       
  3392 	initialize: function (options, source) {
       
  3393 		L.setOptions(this, options);
       
  3394 
       
  3395 		this._source = source;
       
  3396 		this._animated = L.Browser.any3d && this.options.zoomAnimation;
       
  3397 	},
       
  3398 
       
  3399 	onAdd: function (map) {
       
  3400 		this._map = map;
       
  3401 
       
  3402 		if (!this._container) {
       
  3403 			this._initLayout();
       
  3404 		}
       
  3405 		this._updateContent();
       
  3406 
       
  3407 		var animFade = map.options.fadeAnimation;
       
  3408 
       
  3409 		if (animFade) {
       
  3410 			L.DomUtil.setOpacity(this._container, 0);
       
  3411 		}
       
  3412 		map._panes.popupPane.appendChild(this._container);
       
  3413 
       
  3414 		map.on('viewreset', this._updatePosition, this);
       
  3415 
       
  3416 		if (this._animated) {
       
  3417 			map.on('zoomanim', this._zoomAnimation, this);
       
  3418 		}
       
  3419 
       
  3420 		if (map.options.closePopupOnClick) {
       
  3421 			map.on('preclick', this._close, this);
       
  3422 		}
       
  3423 
       
  3424 		this._update();
       
  3425 
       
  3426 		if (animFade) {
       
  3427 			L.DomUtil.setOpacity(this._container, 1);
       
  3428 		}
       
  3429 	},
       
  3430 
       
  3431 	addTo: function (map) {
       
  3432 		map.addLayer(this);
       
  3433 		return this;
       
  3434 	},
       
  3435 
       
  3436 	openOn: function (map) {
       
  3437 		map.openPopup(this);
       
  3438 		return this;
       
  3439 	},
       
  3440 
       
  3441 	onRemove: function (map) {
       
  3442 		map._panes.popupPane.removeChild(this._container);
       
  3443 
       
  3444 		L.Util.falseFn(this._container.offsetWidth); // force reflow
       
  3445 
       
  3446 		map.off({
       
  3447 			viewreset: this._updatePosition,
       
  3448 			preclick: this._close,
       
  3449 			zoomanim: this._zoomAnimation
       
  3450 		}, this);
       
  3451 
       
  3452 		if (map.options.fadeAnimation) {
       
  3453 			L.DomUtil.setOpacity(this._container, 0);
       
  3454 		}
       
  3455 
       
  3456 		this._map = null;
       
  3457 	},
       
  3458 
       
  3459 	setLatLng: function (latlng) {
       
  3460 		this._latlng = L.latLng(latlng);
       
  3461 		this._update();
       
  3462 		return this;
       
  3463 	},
       
  3464 
       
  3465 	setContent: function (content) {
       
  3466 		this._content = content;
       
  3467 		this._update();
       
  3468 		return this;
       
  3469 	},
       
  3470 
       
  3471 	_close: function () {
       
  3472 		var map = this._map;
       
  3473 
       
  3474 		if (map) {
       
  3475 			map._popup = null;
       
  3476 
       
  3477 			map
       
  3478 			    .removeLayer(this)
       
  3479 			    .fire('popupclose', {popup: this});
       
  3480 		}
       
  3481 	},
       
  3482 
       
  3483 	_initLayout: function () {
       
  3484 		var prefix = 'leaflet-popup',
       
  3485 			containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
       
  3486 			        (this._animated ? 'animated' : 'hide'),
       
  3487 			container = this._container = L.DomUtil.create('div', containerClass),
       
  3488 			closeButton;
       
  3489 
       
  3490 		if (this.options.closeButton) {
       
  3491 			closeButton = this._closeButton =
       
  3492 			        L.DomUtil.create('a', prefix + '-close-button', container);
       
  3493 			closeButton.href = '#close';
       
  3494 			closeButton.innerHTML = '&#215;';
       
  3495 
       
  3496 			L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
       
  3497 		}
       
  3498 
       
  3499 		var wrapper = this._wrapper =
       
  3500 		        L.DomUtil.create('div', prefix + '-content-wrapper', container);
       
  3501 		L.DomEvent.disableClickPropagation(wrapper);
       
  3502 
       
  3503 		this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
       
  3504 		L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
       
  3505 
       
  3506 		this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
       
  3507 		this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
       
  3508 	},
       
  3509 
       
  3510 	_update: function () {
       
  3511 		if (!this._map) { return; }
       
  3512 
       
  3513 		this._container.style.visibility = 'hidden';
       
  3514 
       
  3515 		this._updateContent();
       
  3516 		this._updateLayout();
       
  3517 		this._updatePosition();
       
  3518 
       
  3519 		this._container.style.visibility = '';
       
  3520 
       
  3521 		this._adjustPan();
       
  3522 	},
       
  3523 
       
  3524 	_updateContent: function () {
       
  3525 		if (!this._content) { return; }
       
  3526 
       
  3527 		if (typeof this._content === 'string') {
       
  3528 			this._contentNode.innerHTML = this._content;
       
  3529 		} else {
       
  3530 			while (this._contentNode.hasChildNodes()) {
       
  3531 				this._contentNode.removeChild(this._contentNode.firstChild);
       
  3532 			}
       
  3533 			this._contentNode.appendChild(this._content);
       
  3534 		}
       
  3535 		this.fire('contentupdate');
       
  3536 	},
       
  3537 
       
  3538 	_updateLayout: function () {
       
  3539 		var container = this._contentNode,
       
  3540 		    style = container.style;
       
  3541 
       
  3542 		style.width = '';
       
  3543 		style.whiteSpace = 'nowrap';
       
  3544 
       
  3545 		var width = container.offsetWidth;
       
  3546 		width = Math.min(width, this.options.maxWidth);
       
  3547 		width = Math.max(width, this.options.minWidth);
       
  3548 
       
  3549 		style.width = (width + 1) + 'px';
       
  3550 		style.whiteSpace = '';
       
  3551 
       
  3552 		style.height = '';
       
  3553 
       
  3554 		var height = container.offsetHeight,
       
  3555 		    maxHeight = this.options.maxHeight,
       
  3556 		    scrolledClass = 'leaflet-popup-scrolled';
       
  3557 
       
  3558 		if (maxHeight && height > maxHeight) {
       
  3559 			style.height = maxHeight + 'px';
       
  3560 			L.DomUtil.addClass(container, scrolledClass);
       
  3561 		} else {
       
  3562 			L.DomUtil.removeClass(container, scrolledClass);
       
  3563 		}
       
  3564 
       
  3565 		this._containerWidth = this._container.offsetWidth;
       
  3566 	},
       
  3567 
       
  3568 	_updatePosition: function () {
       
  3569 		if (!this._map) { return; }
       
  3570 
       
  3571 		var pos = this._map.latLngToLayerPoint(this._latlng),
       
  3572 		    animated = this._animated,
       
  3573 		    offset = this.options.offset;
       
  3574 
       
  3575 		if (animated) {
       
  3576 			L.DomUtil.setPosition(this._container, pos);
       
  3577 		}
       
  3578 
       
  3579 		this._containerBottom = -offset.y - (animated ? 0 : pos.y);
       
  3580 		this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
       
  3581 
       
  3582 		//Bottom position the popup in case the height of the popup changes (images loading etc)
       
  3583 		this._container.style.bottom = this._containerBottom + 'px';
       
  3584 		this._container.style.left = this._containerLeft + 'px';
       
  3585 	},
       
  3586 
       
  3587 	_zoomAnimation: function (opt) {
       
  3588 		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
       
  3589 
       
  3590 		L.DomUtil.setPosition(this._container, pos);
       
  3591 	},
       
  3592 
       
  3593 	_adjustPan: function () {
       
  3594 		if (!this.options.autoPan) { return; }
       
  3595 
       
  3596 		var map = this._map,
       
  3597 		    containerHeight = this._container.offsetHeight,
       
  3598 		    containerWidth = this._containerWidth,
       
  3599 
       
  3600 		    layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
       
  3601 
       
  3602 		if (this._animated) {
       
  3603 			layerPos._add(L.DomUtil.getPosition(this._container));
       
  3604 		}
       
  3605 
       
  3606 		var containerPos = map.layerPointToContainerPoint(layerPos),
       
  3607 		    padding = this.options.autoPanPadding,
       
  3608 		    size = map.getSize(),
       
  3609 		    dx = 0,
       
  3610 		    dy = 0;
       
  3611 
       
  3612 		if (containerPos.x < 0) {
       
  3613 			dx = containerPos.x - padding.x;
       
  3614 		}
       
  3615 		if (containerPos.x + containerWidth > size.x) {
       
  3616 			dx = containerPos.x + containerWidth - size.x + padding.x;
       
  3617 		}
       
  3618 		if (containerPos.y < 0) {
       
  3619 			dy = containerPos.y - padding.y;
       
  3620 		}
       
  3621 		if (containerPos.y + containerHeight > size.y) {
       
  3622 			dy = containerPos.y + containerHeight - size.y + padding.y;
       
  3623 		}
       
  3624 
       
  3625 		if (dx || dy) {
       
  3626 			map.panBy(new L.Point(dx, dy));
       
  3627 		}
       
  3628 	},
       
  3629 
       
  3630 	_onCloseButtonClick: function (e) {
       
  3631 		this._close();
       
  3632 		L.DomEvent.stop(e);
       
  3633 	}
       
  3634 });
       
  3635 
       
  3636 L.popup = function (options, source) {
       
  3637 	return new L.Popup(options, source);
       
  3638 };
       
  3639 
       
  3640 
       
  3641 /*
       
  3642  * Popup extension to L.Marker, adding popup-related methods.
       
  3643  */
       
  3644 
       
  3645 L.Marker.include({
       
  3646 	openPopup: function () {
       
  3647 		if (this._popup && this._map) {
       
  3648 			this._popup.setLatLng(this._latlng);
       
  3649 			this._map.openPopup(this._popup);
       
  3650 		}
       
  3651 
       
  3652 		return this;
       
  3653 	},
       
  3654 
       
  3655 	closePopup: function () {
       
  3656 		if (this._popup) {
       
  3657 			this._popup._close();
       
  3658 		}
       
  3659 		return this;
       
  3660 	},
       
  3661 
       
  3662 	bindPopup: function (content, options) {
       
  3663 		var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
       
  3664 
       
  3665 		anchor = anchor.add(L.Popup.prototype.options.offset);
       
  3666 
       
  3667 		if (options && options.offset) {
       
  3668 			anchor = anchor.add(options.offset);
       
  3669 		}
       
  3670 
       
  3671 		options = L.extend({offset: anchor}, options);
       
  3672 
       
  3673 		if (!this._popup) {
       
  3674 			this
       
  3675 			    .on('click', this.openPopup, this)
       
  3676 			    .on('remove', this.closePopup, this)
       
  3677 			    .on('move', this._movePopup, this);
       
  3678 		}
       
  3679 
       
  3680 		this._popup = new L.Popup(options, this)
       
  3681 			.setContent(content);
       
  3682 
       
  3683 		return this;
       
  3684 	},
       
  3685 
       
  3686 	unbindPopup: function () {
       
  3687 		if (this._popup) {
       
  3688 			this._popup = null;
       
  3689 			this
       
  3690 			    .off('click', this.openPopup)
       
  3691 			    .off('remove', this.closePopup)
       
  3692 			    .off('move', this._movePopup);
       
  3693 		}
       
  3694 		return this;
       
  3695 	},
       
  3696 
       
  3697 	_movePopup: function (e) {
       
  3698 		this._popup.setLatLng(e.latlng);
       
  3699 	}
       
  3700 });
       
  3701 
       
  3702 
       
  3703 /*
       
  3704  * Adds popup-related methods to L.Map.
       
  3705  */
       
  3706 
       
  3707 L.Map.include({
       
  3708 	openPopup: function (popup) {
       
  3709 		this.closePopup();
       
  3710 
       
  3711 		this._popup = popup;
       
  3712 
       
  3713 		return this
       
  3714 		    .addLayer(popup)
       
  3715 		    .fire('popupopen', {popup: this._popup});
       
  3716 	},
       
  3717 
       
  3718 	closePopup: function () {
       
  3719 		if (this._popup) {
       
  3720 			this._popup._close();
       
  3721 		}
       
  3722 		return this;
       
  3723 	}
       
  3724 });
       
  3725 
       
  3726 
       
  3727 /*
       
  3728  * L.LayerGroup is a class to combine several layers into one so that
       
  3729  * you can manipulate the group (e.g. add/remove it) as one layer.
       
  3730  */
       
  3731 
       
  3732 L.LayerGroup = L.Class.extend({
       
  3733 	initialize: function (layers) {
       
  3734 		this._layers = {};
       
  3735 
       
  3736 		var i, len;
       
  3737 
       
  3738 		if (layers) {
       
  3739 			for (i = 0, len = layers.length; i < len; i++) {
       
  3740 				this.addLayer(layers[i]);
       
  3741 			}
       
  3742 		}
       
  3743 	},
       
  3744 
       
  3745 	addLayer: function (layer) {
       
  3746 		var id = L.stamp(layer);
       
  3747 
       
  3748 		this._layers[id] = layer;
       
  3749 
       
  3750 		if (this._map) {
       
  3751 			this._map.addLayer(layer);
       
  3752 		}
       
  3753 
       
  3754 		return this;
       
  3755 	},
       
  3756 
       
  3757 	removeLayer: function (layer) {
       
  3758 		var id = L.stamp(layer);
       
  3759 
       
  3760 		delete this._layers[id];
       
  3761 
       
  3762 		if (this._map) {
       
  3763 			this._map.removeLayer(layer);
       
  3764 		}
       
  3765 
       
  3766 		return this;
       
  3767 	},
       
  3768 
       
  3769 	clearLayers: function () {
       
  3770 		this.eachLayer(this.removeLayer, this);
       
  3771 		return this;
       
  3772 	},
       
  3773 
       
  3774 	invoke: function (methodName) {
       
  3775 		var args = Array.prototype.slice.call(arguments, 1),
       
  3776 		    i, layer;
       
  3777 
       
  3778 		for (i in this._layers) {
       
  3779 			if (this._layers.hasOwnProperty(i)) {
       
  3780 				layer = this._layers[i];
       
  3781 
       
  3782 				if (layer[methodName]) {
       
  3783 					layer[methodName].apply(layer, args);
       
  3784 				}
       
  3785 			}
       
  3786 		}
       
  3787 
       
  3788 		return this;
       
  3789 	},
       
  3790 
       
  3791 	onAdd: function (map) {
       
  3792 		this._map = map;
       
  3793 		this.eachLayer(map.addLayer, map);
       
  3794 	},
       
  3795 
       
  3796 	onRemove: function (map) {
       
  3797 		this.eachLayer(map.removeLayer, map);
       
  3798 		this._map = null;
       
  3799 	},
       
  3800 
       
  3801 	addTo: function (map) {
       
  3802 		map.addLayer(this);
       
  3803 		return this;
       
  3804 	},
       
  3805 
       
  3806 	eachLayer: function (method, context) {
       
  3807 		for (var i in this._layers) {
       
  3808 			if (this._layers.hasOwnProperty(i)) {
       
  3809 				method.call(context, this._layers[i]);
       
  3810 			}
       
  3811 		}
       
  3812 	},
       
  3813 
       
  3814 	setZIndex: function (zIndex) {
       
  3815 		return this.invoke('setZIndex', zIndex);
       
  3816 	}
       
  3817 });
       
  3818 
       
  3819 L.layerGroup = function (layers) {
       
  3820 	return new L.LayerGroup(layers);
       
  3821 };
       
  3822 
       
  3823 
       
  3824 /*
       
  3825  * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
       
  3826  * shared between a group of interactive layers (like vectors or markers).
       
  3827  */
       
  3828 
       
  3829 L.FeatureGroup = L.LayerGroup.extend({
       
  3830 	includes: L.Mixin.Events,
       
  3831 
       
  3832 	statics: {
       
  3833 		EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu'
       
  3834 	},
       
  3835 
       
  3836 	addLayer: function (layer) {
       
  3837 		if (this._layers[L.stamp(layer)]) {
       
  3838 			return this;
       
  3839 		}
       
  3840 
       
  3841 		layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
       
  3842 
       
  3843 		L.LayerGroup.prototype.addLayer.call(this, layer);
       
  3844 
       
  3845 		if (this._popupContent && layer.bindPopup) {
       
  3846 			layer.bindPopup(this._popupContent, this._popupOptions);
       
  3847 		}
       
  3848 
       
  3849 		return this.fire('layeradd', {layer: layer});
       
  3850 	},
       
  3851 
       
  3852 	removeLayer: function (layer) {
       
  3853 		layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
       
  3854 
       
  3855 		L.LayerGroup.prototype.removeLayer.call(this, layer);
       
  3856 
       
  3857 
       
  3858 		if (this._popupContent) {
       
  3859 			this.invoke('unbindPopup');
       
  3860 		}
       
  3861 
       
  3862 		return this.fire('layerremove', {layer: layer});
       
  3863 	},
       
  3864 
       
  3865 	bindPopup: function (content, options) {
       
  3866 		this._popupContent = content;
       
  3867 		this._popupOptions = options;
       
  3868 		return this.invoke('bindPopup', content, options);
       
  3869 	},
       
  3870 
       
  3871 	setStyle: function (style) {
       
  3872 		return this.invoke('setStyle', style);
       
  3873 	},
       
  3874 
       
  3875 	bringToFront: function () {
       
  3876 		return this.invoke('bringToFront');
       
  3877 	},
       
  3878 
       
  3879 	bringToBack: function () {
       
  3880 		return this.invoke('bringToBack');
       
  3881 	},
       
  3882 
       
  3883 	getBounds: function () {
       
  3884 		var bounds = new L.LatLngBounds();
       
  3885 
       
  3886 		this.eachLayer(function (layer) {
       
  3887 			bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
       
  3888 		});
       
  3889 
       
  3890 		return bounds;
       
  3891 	},
       
  3892 
       
  3893 	_propagateEvent: function (e) {
       
  3894 		e.layer  = e.target;
       
  3895 		e.target = this;
       
  3896 
       
  3897 		this.fire(e.type, e);
       
  3898 	}
       
  3899 });
       
  3900 
       
  3901 L.featureGroup = function (layers) {
       
  3902 	return new L.FeatureGroup(layers);
       
  3903 };
       
  3904 
       
  3905 
       
  3906 /*
       
  3907  * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
       
  3908  */
       
  3909 
       
  3910 L.Path = L.Class.extend({
       
  3911 	includes: [L.Mixin.Events],
       
  3912 
       
  3913 	statics: {
       
  3914 		// how much to extend the clip area around the map view
       
  3915 		// (relative to its size, e.g. 0.5 is half the screen in each direction)
       
  3916 		// set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
       
  3917 		CLIP_PADDING: L.Browser.mobile ?
       
  3918 			Math.max(0, Math.min(0.5,
       
  3919 			        (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5
       
  3920 	},
       
  3921 
       
  3922 	options: {
       
  3923 		stroke: true,
       
  3924 		color: '#0033ff',
       
  3925 		dashArray: null,
       
  3926 		weight: 5,
       
  3927 		opacity: 0.5,
       
  3928 
       
  3929 		fill: false,
       
  3930 		fillColor: null, //same as color by default
       
  3931 		fillOpacity: 0.2,
       
  3932 
       
  3933 		clickable: true
       
  3934 	},
       
  3935 
       
  3936 	initialize: function (options) {
       
  3937 		L.setOptions(this, options);
       
  3938 	},
       
  3939 
       
  3940 	onAdd: function (map) {
       
  3941 		this._map = map;
       
  3942 
       
  3943 		if (!this._container) {
       
  3944 			this._initElements();
       
  3945 			this._initEvents();
       
  3946 		}
       
  3947 
       
  3948 		this.projectLatlngs();
       
  3949 		this._updatePath();
       
  3950 
       
  3951 		if (this._container) {
       
  3952 			this._map._pathRoot.appendChild(this._container);
       
  3953 		}
       
  3954 
       
  3955 		this.fire('add');
       
  3956 
       
  3957 		map.on({
       
  3958 			'viewreset': this.projectLatlngs,
       
  3959 			'moveend': this._updatePath
       
  3960 		}, this);
       
  3961 	},
       
  3962 
       
  3963 	addTo: function (map) {
       
  3964 		map.addLayer(this);
       
  3965 		return this;
       
  3966 	},
       
  3967 
       
  3968 	onRemove: function (map) {
       
  3969 		map._pathRoot.removeChild(this._container);
       
  3970 
       
  3971 		// Need to fire remove event before we set _map to null as the event hooks might need the object
       
  3972 		this.fire('remove');
       
  3973 		this._map = null;
       
  3974 
       
  3975 		if (L.Browser.vml) {
       
  3976 			this._container = null;
       
  3977 			this._stroke = null;
       
  3978 			this._fill = null;
       
  3979 		}
       
  3980 
       
  3981 		map.off({
       
  3982 			'viewreset': this.projectLatlngs,
       
  3983 			'moveend': this._updatePath
       
  3984 		}, this);
       
  3985 	},
       
  3986 
       
  3987 	projectLatlngs: function () {
       
  3988 		// do all projection stuff here
       
  3989 	},
       
  3990 
       
  3991 	setStyle: function (style) {
       
  3992 		L.setOptions(this, style);
       
  3993 
       
  3994 		if (this._container) {
       
  3995 			this._updateStyle();
       
  3996 		}
       
  3997 
       
  3998 		return this;
       
  3999 	},
       
  4000 
       
  4001 	redraw: function () {
       
  4002 		if (this._map) {
       
  4003 			this.projectLatlngs();
       
  4004 			this._updatePath();
       
  4005 		}
       
  4006 		return this;
       
  4007 	}
       
  4008 });
       
  4009 
       
  4010 L.Map.include({
       
  4011 	_updatePathViewport: function () {
       
  4012 		var p = L.Path.CLIP_PADDING,
       
  4013 		    size = this.getSize(),
       
  4014 		    panePos = L.DomUtil.getPosition(this._mapPane),
       
  4015 		    min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
       
  4016 		    max = min.add(size.multiplyBy(1 + p * 2)._round());
       
  4017 
       
  4018 		this._pathViewport = new L.Bounds(min, max);
       
  4019 	}
       
  4020 });
       
  4021 
       
  4022 
       
  4023 /*
       
  4024  * Extends L.Path with SVG-specific rendering code.
       
  4025  */
       
  4026 
       
  4027 L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
       
  4028 
       
  4029 L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
       
  4030 
       
  4031 L.Path = L.Path.extend({
       
  4032 	statics: {
       
  4033 		SVG: L.Browser.svg
       
  4034 	},
       
  4035 
       
  4036 	bringToFront: function () {
       
  4037 		var root = this._map._pathRoot,
       
  4038 		    path = this._container;
       
  4039 
       
  4040 		if (path && root.lastChild !== path) {
       
  4041 			root.appendChild(path);
       
  4042 		}
       
  4043 		return this;
       
  4044 	},
       
  4045 
       
  4046 	bringToBack: function () {
       
  4047 		var root = this._map._pathRoot,
       
  4048 		    path = this._container,
       
  4049 		    first = root.firstChild;
       
  4050 
       
  4051 		if (path && first !== path) {
       
  4052 			root.insertBefore(path, first);
       
  4053 		}
       
  4054 		return this;
       
  4055 	},
       
  4056 
       
  4057 	getPathString: function () {
       
  4058 		// form path string here
       
  4059 	},
       
  4060 
       
  4061 	_createElement: function (name) {
       
  4062 		return document.createElementNS(L.Path.SVG_NS, name);
       
  4063 	},
       
  4064 
       
  4065 	_initElements: function () {
       
  4066 		this._map._initPathRoot();
       
  4067 		this._initPath();
       
  4068 		this._initStyle();
       
  4069 	},
       
  4070 
       
  4071 	_initPath: function () {
       
  4072 		this._container = this._createElement('g');
       
  4073 
       
  4074 		this._path = this._createElement('path');
       
  4075 		this._container.appendChild(this._path);
       
  4076 	},
       
  4077 
       
  4078 	_initStyle: function () {
       
  4079 		if (this.options.stroke) {
       
  4080 			this._path.setAttribute('stroke-linejoin', 'round');
       
  4081 			this._path.setAttribute('stroke-linecap', 'round');
       
  4082 		}
       
  4083 		if (this.options.fill) {
       
  4084 			this._path.setAttribute('fill-rule', 'evenodd');
       
  4085 		}
       
  4086 		this._updateStyle();
       
  4087 	},
       
  4088 
       
  4089 	_updateStyle: function () {
       
  4090 		if (this.options.stroke) {
       
  4091 			this._path.setAttribute('stroke', this.options.color);
       
  4092 			this._path.setAttribute('stroke-opacity', this.options.opacity);
       
  4093 			this._path.setAttribute('stroke-width', this.options.weight);
       
  4094 			if (this.options.dashArray) {
       
  4095 				this._path.setAttribute('stroke-dasharray', this.options.dashArray);
       
  4096 			} else {
       
  4097 				this._path.removeAttribute('stroke-dasharray');
       
  4098 			}
       
  4099 		} else {
       
  4100 			this._path.setAttribute('stroke', 'none');
       
  4101 		}
       
  4102 		if (this.options.fill) {
       
  4103 			this._path.setAttribute('fill', this.options.fillColor || this.options.color);
       
  4104 			this._path.setAttribute('fill-opacity', this.options.fillOpacity);
       
  4105 		} else {
       
  4106 			this._path.setAttribute('fill', 'none');
       
  4107 		}
       
  4108 	},
       
  4109 
       
  4110 	_updatePath: function () {
       
  4111 		var str = this.getPathString();
       
  4112 		if (!str) {
       
  4113 			// fix webkit empty string parsing bug
       
  4114 			str = 'M0 0';
       
  4115 		}
       
  4116 		this._path.setAttribute('d', str);
       
  4117 	},
       
  4118 
       
  4119 	// TODO remove duplication with L.Map
       
  4120 	_initEvents: function () {
       
  4121 		if (this.options.clickable) {
       
  4122 			if (L.Browser.svg || !L.Browser.vml) {
       
  4123 				this._path.setAttribute('class', 'leaflet-clickable');
       
  4124 			}
       
  4125 
       
  4126 			L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
       
  4127 
       
  4128 			var events = ['dblclick', 'mousedown', 'mouseover',
       
  4129 			              'mouseout', 'mousemove', 'contextmenu'];
       
  4130 			for (var i = 0; i < events.length; i++) {
       
  4131 				L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
       
  4132 			}
       
  4133 		}
       
  4134 	},
       
  4135 
       
  4136 	_onMouseClick: function (e) {
       
  4137 		if (this._map.dragging && this._map.dragging.moved()) { return; }
       
  4138 
       
  4139 		this._fireMouseEvent(e);
       
  4140 	},
       
  4141 
       
  4142 	_fireMouseEvent: function (e) {
       
  4143 		if (!this.hasEventListeners(e.type)) { return; }
       
  4144 
       
  4145 		var map = this._map,
       
  4146 		    containerPoint = map.mouseEventToContainerPoint(e),
       
  4147 		    layerPoint = map.containerPointToLayerPoint(containerPoint),
       
  4148 		    latlng = map.layerPointToLatLng(layerPoint);
       
  4149 
       
  4150 		this.fire(e.type, {
       
  4151 			latlng: latlng,
       
  4152 			layerPoint: layerPoint,
       
  4153 			containerPoint: containerPoint,
       
  4154 			originalEvent: e
       
  4155 		});
       
  4156 
       
  4157 		if (e.type === 'contextmenu') {
       
  4158 			L.DomEvent.preventDefault(e);
       
  4159 		}
       
  4160 		if (e.type !== 'mousemove') {
       
  4161 			L.DomEvent.stopPropagation(e);
       
  4162 		}
       
  4163 	}
       
  4164 });
       
  4165 
       
  4166 L.Map.include({
       
  4167 	_initPathRoot: function () {
       
  4168 		if (!this._pathRoot) {
       
  4169 			this._pathRoot = L.Path.prototype._createElement('svg');
       
  4170 			this._panes.overlayPane.appendChild(this._pathRoot);
       
  4171 
       
  4172 			if (this.options.zoomAnimation && L.Browser.any3d) {
       
  4173 				this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
       
  4174 
       
  4175 				this.on({
       
  4176 					'zoomanim': this._animatePathZoom,
       
  4177 					'zoomend': this._endPathZoom
       
  4178 				});
       
  4179 			} else {
       
  4180 				this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
       
  4181 			}
       
  4182 
       
  4183 			this.on('moveend', this._updateSvgViewport);
       
  4184 			this._updateSvgViewport();
       
  4185 		}
       
  4186 	},
       
  4187 
       
  4188 	_animatePathZoom: function (e) {
       
  4189 		var scale = this.getZoomScale(e.zoom),
       
  4190 		    offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
       
  4191 
       
  4192 		this._pathRoot.style[L.DomUtil.TRANSFORM] =
       
  4193 		        L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
       
  4194 
       
  4195 		this._pathZooming = true;
       
  4196 	},
       
  4197 
       
  4198 	_endPathZoom: function () {
       
  4199 		this._pathZooming = false;
       
  4200 	},
       
  4201 
       
  4202 	_updateSvgViewport: function () {
       
  4203 
       
  4204 		if (this._pathZooming) {
       
  4205 			// Do not update SVGs while a zoom animation is going on otherwise the animation will break.
       
  4206 			// When the zoom animation ends we will be updated again anyway
       
  4207 			// This fixes the case where you do a momentum move and zoom while the move is still ongoing.
       
  4208 			return;
       
  4209 		}
       
  4210 
       
  4211 		this._updatePathViewport();
       
  4212 
       
  4213 		var vp = this._pathViewport,
       
  4214 		    min = vp.min,
       
  4215 		    max = vp.max,
       
  4216 		    width = max.x - min.x,
       
  4217 		    height = max.y - min.y,
       
  4218 		    root = this._pathRoot,
       
  4219 		    pane = this._panes.overlayPane;
       
  4220 
       
  4221 		// Hack to make flicker on drag end on mobile webkit less irritating
       
  4222 		if (L.Browser.mobileWebkit) {
       
  4223 			pane.removeChild(root);
       
  4224 		}
       
  4225 
       
  4226 		L.DomUtil.setPosition(root, min);
       
  4227 		root.setAttribute('width', width);
       
  4228 		root.setAttribute('height', height);
       
  4229 		root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
       
  4230 
       
  4231 		if (L.Browser.mobileWebkit) {
       
  4232 			pane.appendChild(root);
       
  4233 		}
       
  4234 	}
       
  4235 });
       
  4236 
       
  4237 
       
  4238 /*
       
  4239  * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
       
  4240  */
       
  4241 
       
  4242 L.Path.include({
       
  4243 
       
  4244 	bindPopup: function (content, options) {
       
  4245 
       
  4246 		if (!this._popup || options) {
       
  4247 			this._popup = new L.Popup(options, this);
       
  4248 		}
       
  4249 
       
  4250 		this._popup.setContent(content);
       
  4251 
       
  4252 		if (!this._popupHandlersAdded) {
       
  4253 			this
       
  4254 			    .on('click', this._openPopup, this)
       
  4255 			    .on('remove', this.closePopup, this);
       
  4256 
       
  4257 			this._popupHandlersAdded = true;
       
  4258 		}
       
  4259 
       
  4260 		return this;
       
  4261 	},
       
  4262 
       
  4263 	unbindPopup: function () {
       
  4264 		if (this._popup) {
       
  4265 			this._popup = null;
       
  4266 			this
       
  4267 			    .off('click', this._openPopup)
       
  4268 			    .off('remove', this.closePopup);
       
  4269 
       
  4270 			this._popupHandlersAdded = false;
       
  4271 		}
       
  4272 		return this;
       
  4273 	},
       
  4274 
       
  4275 	openPopup: function (latlng) {
       
  4276 
       
  4277 		if (this._popup) {
       
  4278 			// open the popup from one of the path's points if not specified
       
  4279 			latlng = latlng || this._latlng ||
       
  4280 			         this._latlngs[Math.floor(this._latlngs.length / 2)];
       
  4281 
       
  4282 			this._openPopup({latlng: latlng});
       
  4283 		}
       
  4284 
       
  4285 		return this;
       
  4286 	},
       
  4287 
       
  4288 	closePopup: function () {
       
  4289 		if (this._popup) {
       
  4290 			this._popup._close();
       
  4291 		}
       
  4292 		return this;
       
  4293 	},
       
  4294 
       
  4295 	_openPopup: function (e) {
       
  4296 		this._popup.setLatLng(e.latlng);
       
  4297 		this._map.openPopup(this._popup);
       
  4298 	}
       
  4299 });
       
  4300 
       
  4301 
       
  4302 /*
       
  4303  * Vector rendering for IE6-8 through VML.
       
  4304  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
       
  4305  */
       
  4306 
       
  4307 L.Browser.vml = !L.Browser.svg && (function () {
       
  4308 	try {
       
  4309 		var div = document.createElement('div');
       
  4310 		div.innerHTML = '<v:shape adj="1"/>';
       
  4311 
       
  4312 		var shape = div.firstChild;
       
  4313 		shape.style.behavior = 'url(#default#VML)';
       
  4314 
       
  4315 		return shape && (typeof shape.adj === 'object');
       
  4316 
       
  4317 	} catch (e) {
       
  4318 		return false;
       
  4319 	}
       
  4320 }());
       
  4321 
       
  4322 L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
       
  4323 	statics: {
       
  4324 		VML: true,
       
  4325 		CLIP_PADDING: 0.02
       
  4326 	},
       
  4327 
       
  4328 	_createElement: (function () {
       
  4329 		try {
       
  4330 			document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
       
  4331 			return function (name) {
       
  4332 				return document.createElement('<lvml:' + name + ' class="lvml">');
       
  4333 			};
       
  4334 		} catch (e) {
       
  4335 			return function (name) {
       
  4336 				return document.createElement(
       
  4337 				        '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
       
  4338 			};
       
  4339 		}
       
  4340 	}()),
       
  4341 
       
  4342 	_initPath: function () {
       
  4343 		var container = this._container = this._createElement('shape');
       
  4344 		L.DomUtil.addClass(container, 'leaflet-vml-shape');
       
  4345 		if (this.options.clickable) {
       
  4346 			L.DomUtil.addClass(container, 'leaflet-clickable');
       
  4347 		}
       
  4348 		container.coordsize = '1 1';
       
  4349 
       
  4350 		this._path = this._createElement('path');
       
  4351 		container.appendChild(this._path);
       
  4352 
       
  4353 		this._map._pathRoot.appendChild(container);
       
  4354 	},
       
  4355 
       
  4356 	_initStyle: function () {
       
  4357 		this._updateStyle();
       
  4358 	},
       
  4359 
       
  4360 	_updateStyle: function () {
       
  4361 		var stroke = this._stroke,
       
  4362 		    fill = this._fill,
       
  4363 		    options = this.options,
       
  4364 		    container = this._container;
       
  4365 
       
  4366 		container.stroked = options.stroke;
       
  4367 		container.filled = options.fill;
       
  4368 
       
  4369 		if (options.stroke) {
       
  4370 			if (!stroke) {
       
  4371 				stroke = this._stroke = this._createElement('stroke');
       
  4372 				stroke.endcap = 'round';
       
  4373 				container.appendChild(stroke);
       
  4374 			}
       
  4375 			stroke.weight = options.weight + 'px';
       
  4376 			stroke.color = options.color;
       
  4377 			stroke.opacity = options.opacity;
       
  4378 
       
  4379 			if (options.dashArray) {
       
  4380 				stroke.dashStyle = options.dashArray instanceof Array ?
       
  4381 				    options.dashArray.join(' ') :
       
  4382 				    options.dashArray.replace(/ *, */g, ' ');
       
  4383 			} else {
       
  4384 				stroke.dashStyle = '';
       
  4385 			}
       
  4386 
       
  4387 		} else if (stroke) {
       
  4388 			container.removeChild(stroke);
       
  4389 			this._stroke = null;
       
  4390 		}
       
  4391 
       
  4392 		if (options.fill) {
       
  4393 			if (!fill) {
       
  4394 				fill = this._fill = this._createElement('fill');
       
  4395 				container.appendChild(fill);
       
  4396 			}
       
  4397 			fill.color = options.fillColor || options.color;
       
  4398 			fill.opacity = options.fillOpacity;
       
  4399 
       
  4400 		} else if (fill) {
       
  4401 			container.removeChild(fill);
       
  4402 			this._fill = null;
       
  4403 		}
       
  4404 	},
       
  4405 
       
  4406 	_updatePath: function () {
       
  4407 		var style = this._container.style;
       
  4408 
       
  4409 		style.display = 'none';
       
  4410 		this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
       
  4411 		style.display = '';
       
  4412 	}
       
  4413 });
       
  4414 
       
  4415 L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
       
  4416 	_initPathRoot: function () {
       
  4417 		if (this._pathRoot) { return; }
       
  4418 
       
  4419 		var root = this._pathRoot = document.createElement('div');
       
  4420 		root.className = 'leaflet-vml-container';
       
  4421 		this._panes.overlayPane.appendChild(root);
       
  4422 
       
  4423 		this.on('moveend', this._updatePathViewport);
       
  4424 		this._updatePathViewport();
       
  4425 	}
       
  4426 });
       
  4427 
       
  4428 
       
  4429 /*
       
  4430  * Vector rendering for all browsers that support canvas.
       
  4431  */
       
  4432 
       
  4433 L.Browser.canvas = (function () {
       
  4434 	return !!document.createElement('canvas').getContext;
       
  4435 }());
       
  4436 
       
  4437 L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
       
  4438 	statics: {
       
  4439 		//CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
       
  4440 		CANVAS: true,
       
  4441 		SVG: false
       
  4442 	},
       
  4443 
       
  4444 	redraw: function () {
       
  4445 		if (this._map) {
       
  4446 			this.projectLatlngs();
       
  4447 			this._requestUpdate();
       
  4448 		}
       
  4449 		return this;
       
  4450 	},
       
  4451 
       
  4452 	setStyle: function (style) {
       
  4453 		L.setOptions(this, style);
       
  4454 
       
  4455 		if (this._map) {
       
  4456 			this._updateStyle();
       
  4457 			this._requestUpdate();
       
  4458 		}
       
  4459 		return this;
       
  4460 	},
       
  4461 
       
  4462 	onRemove: function (map) {
       
  4463 		map
       
  4464 		    .off('viewreset', this.projectLatlngs, this)
       
  4465 		    .off('moveend', this._updatePath, this);
       
  4466 
       
  4467 		if (this.options.clickable) {
       
  4468 			this._map.off('click', this._onClick, this);
       
  4469 		}
       
  4470 
       
  4471 		this._requestUpdate();
       
  4472 
       
  4473 		this._map = null;
       
  4474 	},
       
  4475 
       
  4476 	_requestUpdate: function () {
       
  4477 		if (this._map && !L.Path._updateRequest) {
       
  4478 			L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
       
  4479 		}
       
  4480 	},
       
  4481 
       
  4482 	_fireMapMoveEnd: function () {
       
  4483 		L.Path._updateRequest = null;
       
  4484 		this.fire('moveend');
       
  4485 	},
       
  4486 
       
  4487 	_initElements: function () {
       
  4488 		this._map._initPathRoot();
       
  4489 		this._ctx = this._map._canvasCtx;
       
  4490 	},
       
  4491 
       
  4492 	_updateStyle: function () {
       
  4493 		var options = this.options;
       
  4494 
       
  4495 		if (options.stroke) {
       
  4496 			this._ctx.lineWidth = options.weight;
       
  4497 			this._ctx.strokeStyle = options.color;
       
  4498 		}
       
  4499 		if (options.fill) {
       
  4500 			this._ctx.fillStyle = options.fillColor || options.color;
       
  4501 		}
       
  4502 	},
       
  4503 
       
  4504 	_drawPath: function () {
       
  4505 		var i, j, len, len2, point, drawMethod;
       
  4506 
       
  4507 		this._ctx.beginPath();
       
  4508 
       
  4509 		for (i = 0, len = this._parts.length; i < len; i++) {
       
  4510 			for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
       
  4511 				point = this._parts[i][j];
       
  4512 				drawMethod = (j === 0 ? 'move' : 'line') + 'To';
       
  4513 
       
  4514 				this._ctx[drawMethod](point.x, point.y);
       
  4515 			}
       
  4516 			// TODO refactor ugly hack
       
  4517 			if (this instanceof L.Polygon) {
       
  4518 				this._ctx.closePath();
       
  4519 			}
       
  4520 		}
       
  4521 	},
       
  4522 
       
  4523 	_checkIfEmpty: function () {
       
  4524 		return !this._parts.length;
       
  4525 	},
       
  4526 
       
  4527 	_updatePath: function () {
       
  4528 		if (this._checkIfEmpty()) { return; }
       
  4529 
       
  4530 		var ctx = this._ctx,
       
  4531 		    options = this.options;
       
  4532 
       
  4533 		this._drawPath();
       
  4534 		ctx.save();
       
  4535 		this._updateStyle();
       
  4536 
       
  4537 		if (options.fill) {
       
  4538 			ctx.globalAlpha = options.fillOpacity;
       
  4539 			ctx.fill();
       
  4540 		}
       
  4541 
       
  4542 		if (options.stroke) {
       
  4543 			ctx.globalAlpha = options.opacity;
       
  4544 			ctx.stroke();
       
  4545 		}
       
  4546 
       
  4547 		ctx.restore();
       
  4548 
       
  4549 		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
       
  4550 	},
       
  4551 
       
  4552 	_initEvents: function () {
       
  4553 		if (this.options.clickable) {
       
  4554 			// TODO hand cursor
       
  4555 			// TODO mouseover, mouseout, dblclick
       
  4556 			this._map.on('click', this._onClick, this);
       
  4557 		}
       
  4558 	},
       
  4559 
       
  4560 	_onClick: function (e) {
       
  4561 		if (this._containsPoint(e.layerPoint)) {
       
  4562 			this.fire('click', {
       
  4563 				latlng: e.latlng,
       
  4564 				layerPoint: e.layerPoint,
       
  4565 				containerPoint: e.containerPoint,
       
  4566 				originalEvent: e
       
  4567 			});
       
  4568 		}
       
  4569 	}
       
  4570 });
       
  4571 
       
  4572 L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
       
  4573 	_initPathRoot: function () {
       
  4574 		var root = this._pathRoot,
       
  4575 		    ctx;
       
  4576 
       
  4577 		if (!root) {
       
  4578 			root = this._pathRoot = document.createElement("canvas");
       
  4579 			root.style.position = 'absolute';
       
  4580 			ctx = this._canvasCtx = root.getContext('2d');
       
  4581 
       
  4582 			ctx.lineCap = "round";
       
  4583 			ctx.lineJoin = "round";
       
  4584 
       
  4585 			this._panes.overlayPane.appendChild(root);
       
  4586 
       
  4587 			if (this.options.zoomAnimation) {
       
  4588 				this._pathRoot.className = 'leaflet-zoom-animated';
       
  4589 				this.on('zoomanim', this._animatePathZoom);
       
  4590 				this.on('zoomend', this._endPathZoom);
       
  4591 			}
       
  4592 			this.on('moveend', this._updateCanvasViewport);
       
  4593 			this._updateCanvasViewport();
       
  4594 		}
       
  4595 	},
       
  4596 
       
  4597 	_updateCanvasViewport: function () {
       
  4598 		// don't redraw while zooming. See _updateSvgViewport for more details
       
  4599 		if (this._pathZooming) { return; }
       
  4600 		this._updatePathViewport();
       
  4601 
       
  4602 		var vp = this._pathViewport,
       
  4603 		    min = vp.min,
       
  4604 		    size = vp.max.subtract(min),
       
  4605 		    root = this._pathRoot;
       
  4606 
       
  4607 		//TODO check if this works properly on mobile webkit
       
  4608 		L.DomUtil.setPosition(root, min);
       
  4609 		root.width = size.x;
       
  4610 		root.height = size.y;
       
  4611 		root.getContext('2d').translate(-min.x, -min.y);
       
  4612 	}
       
  4613 });
       
  4614 
       
  4615 
       
  4616 /*
       
  4617  * L.LineUtil contains different utility functions for line segments
       
  4618  * and polylines (clipping, simplification, distances, etc.)
       
  4619  */
       
  4620 
       
  4621 /*jshint bitwise:false */ // allow bitwise oprations for this file
       
  4622 
       
  4623 L.LineUtil = {
       
  4624 
       
  4625 	// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
       
  4626 	// Improves rendering performance dramatically by lessening the number of points to draw.
       
  4627 
       
  4628 	simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
       
  4629 		if (!tolerance || !points.length) {
       
  4630 			return points.slice();
       
  4631 		}
       
  4632 
       
  4633 		var sqTolerance = tolerance * tolerance;
       
  4634 
       
  4635 		// stage 1: vertex reduction
       
  4636 		points = this._reducePoints(points, sqTolerance);
       
  4637 
       
  4638 		// stage 2: Douglas-Peucker simplification
       
  4639 		points = this._simplifyDP(points, sqTolerance);
       
  4640 
       
  4641 		return points;
       
  4642 	},
       
  4643 
       
  4644 	// distance from a point to a segment between two points
       
  4645 	pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
       
  4646 		return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
       
  4647 	},
       
  4648 
       
  4649 	closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
       
  4650 		return this._sqClosestPointOnSegment(p, p1, p2);
       
  4651 	},
       
  4652 
       
  4653 	// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
       
  4654 	_simplifyDP: function (points, sqTolerance) {
       
  4655 
       
  4656 		var len = points.length,
       
  4657 		    ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
       
  4658 		    markers = new ArrayConstructor(len);
       
  4659 
       
  4660 		markers[0] = markers[len - 1] = 1;
       
  4661 
       
  4662 		this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
       
  4663 
       
  4664 		var i,
       
  4665 		    newPoints = [];
       
  4666 
       
  4667 		for (i = 0; i < len; i++) {
       
  4668 			if (markers[i]) {
       
  4669 				newPoints.push(points[i]);
       
  4670 			}
       
  4671 		}
       
  4672 
       
  4673 		return newPoints;
       
  4674 	},
       
  4675 
       
  4676 	_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
       
  4677 
       
  4678 		var maxSqDist = 0,
       
  4679 		    index, i, sqDist;
       
  4680 
       
  4681 		for (i = first + 1; i <= last - 1; i++) {
       
  4682 			sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
       
  4683 
       
  4684 			if (sqDist > maxSqDist) {
       
  4685 				index = i;
       
  4686 				maxSqDist = sqDist;
       
  4687 			}
       
  4688 		}
       
  4689 
       
  4690 		if (maxSqDist > sqTolerance) {
       
  4691 			markers[index] = 1;
       
  4692 
       
  4693 			this._simplifyDPStep(points, markers, sqTolerance, first, index);
       
  4694 			this._simplifyDPStep(points, markers, sqTolerance, index, last);
       
  4695 		}
       
  4696 	},
       
  4697 
       
  4698 	// reduce points that are too close to each other to a single point
       
  4699 	_reducePoints: function (points, sqTolerance) {
       
  4700 		var reducedPoints = [points[0]];
       
  4701 
       
  4702 		for (var i = 1, prev = 0, len = points.length; i < len; i++) {
       
  4703 			if (this._sqDist(points[i], points[prev]) > sqTolerance) {
       
  4704 				reducedPoints.push(points[i]);
       
  4705 				prev = i;
       
  4706 			}
       
  4707 		}
       
  4708 		if (prev < len - 1) {
       
  4709 			reducedPoints.push(points[len - 1]);
       
  4710 		}
       
  4711 		return reducedPoints;
       
  4712 	},
       
  4713 
       
  4714 	// Cohen-Sutherland line clipping algorithm.
       
  4715 	// Used to avoid rendering parts of a polyline that are not currently visible.
       
  4716 
       
  4717 	clipSegment: function (a, b, bounds, useLastCode) {
       
  4718 		var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
       
  4719 		    codeB = this._getBitCode(b, bounds),
       
  4720 
       
  4721 		    codeOut, p, newCode;
       
  4722 
       
  4723 		// save 2nd code to avoid calculating it on the next segment
       
  4724 		this._lastCode = codeB;
       
  4725 
       
  4726 		while (true) {
       
  4727 			// if a,b is inside the clip window (trivial accept)
       
  4728 			if (!(codeA | codeB)) {
       
  4729 				return [a, b];
       
  4730 			// if a,b is outside the clip window (trivial reject)
       
  4731 			} else if (codeA & codeB) {
       
  4732 				return false;
       
  4733 			// other cases
       
  4734 			} else {
       
  4735 				codeOut = codeA || codeB,
       
  4736 				p = this._getEdgeIntersection(a, b, codeOut, bounds),
       
  4737 				newCode = this._getBitCode(p, bounds);
       
  4738 
       
  4739 				if (codeOut === codeA) {
       
  4740 					a = p;
       
  4741 					codeA = newCode;
       
  4742 				} else {
       
  4743 					b = p;
       
  4744 					codeB = newCode;
       
  4745 				}
       
  4746 			}
       
  4747 		}
       
  4748 	},
       
  4749 
       
  4750 	_getEdgeIntersection: function (a, b, code, bounds) {
       
  4751 		var dx = b.x - a.x,
       
  4752 		    dy = b.y - a.y,
       
  4753 		    min = bounds.min,
       
  4754 		    max = bounds.max;
       
  4755 
       
  4756 		if (code & 8) { // top
       
  4757 			return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
       
  4758 		} else if (code & 4) { // bottom
       
  4759 			return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
       
  4760 		} else if (code & 2) { // right
       
  4761 			return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
       
  4762 		} else if (code & 1) { // left
       
  4763 			return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
       
  4764 		}
       
  4765 	},
       
  4766 
       
  4767 	_getBitCode: function (/*Point*/ p, bounds) {
       
  4768 		var code = 0;
       
  4769 
       
  4770 		if (p.x < bounds.min.x) { // left
       
  4771 			code |= 1;
       
  4772 		} else if (p.x > bounds.max.x) { // right
       
  4773 			code |= 2;
       
  4774 		}
       
  4775 		if (p.y < bounds.min.y) { // bottom
       
  4776 			code |= 4;
       
  4777 		} else if (p.y > bounds.max.y) { // top
       
  4778 			code |= 8;
       
  4779 		}
       
  4780 
       
  4781 		return code;
       
  4782 	},
       
  4783 
       
  4784 	// square distance (to avoid unnecessary Math.sqrt calls)
       
  4785 	_sqDist: function (p1, p2) {
       
  4786 		var dx = p2.x - p1.x,
       
  4787 		    dy = p2.y - p1.y;
       
  4788 		return dx * dx + dy * dy;
       
  4789 	},
       
  4790 
       
  4791 	// return closest point on segment or distance to that point
       
  4792 	_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
       
  4793 		var x = p1.x,
       
  4794 		    y = p1.y,
       
  4795 		    dx = p2.x - x,
       
  4796 		    dy = p2.y - y,
       
  4797 		    dot = dx * dx + dy * dy,
       
  4798 		    t;
       
  4799 
       
  4800 		if (dot > 0) {
       
  4801 			t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
       
  4802 
       
  4803 			if (t > 1) {
       
  4804 				x = p2.x;
       
  4805 				y = p2.y;
       
  4806 			} else if (t > 0) {
       
  4807 				x += dx * t;
       
  4808 				y += dy * t;
       
  4809 			}
       
  4810 		}
       
  4811 
       
  4812 		dx = p.x - x;
       
  4813 		dy = p.y - y;
       
  4814 
       
  4815 		return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
       
  4816 	}
       
  4817 };
       
  4818 
       
  4819 
       
  4820 /*
       
  4821  * L.Polygon is used to display polylines on a map.
       
  4822  */
       
  4823 
       
  4824 L.Polyline = L.Path.extend({
       
  4825 	initialize: function (latlngs, options) {
       
  4826 		L.Path.prototype.initialize.call(this, options);
       
  4827 
       
  4828 		this._latlngs = this._convertLatLngs(latlngs);
       
  4829 	},
       
  4830 
       
  4831 	options: {
       
  4832 		// how much to simplify the polyline on each zoom level
       
  4833 		// more = better performance and smoother look, less = more accurate
       
  4834 		smoothFactor: 1.0,
       
  4835 		noClip: false
       
  4836 	},
       
  4837 
       
  4838 	projectLatlngs: function () {
       
  4839 		this._originalPoints = [];
       
  4840 
       
  4841 		for (var i = 0, len = this._latlngs.length; i < len; i++) {
       
  4842 			this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
       
  4843 		}
       
  4844 	},
       
  4845 
       
  4846 	getPathString: function () {
       
  4847 		for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
       
  4848 			str += this._getPathPartStr(this._parts[i]);
       
  4849 		}
       
  4850 		return str;
       
  4851 	},
       
  4852 
       
  4853 	getLatLngs: function () {
       
  4854 		return this._latlngs;
       
  4855 	},
       
  4856 
       
  4857 	setLatLngs: function (latlngs) {
       
  4858 		this._latlngs = this._convertLatLngs(latlngs);
       
  4859 		return this.redraw();
       
  4860 	},
       
  4861 
       
  4862 	addLatLng: function (latlng) {
       
  4863 		this._latlngs.push(L.latLng(latlng));
       
  4864 		return this.redraw();
       
  4865 	},
       
  4866 
       
  4867 	spliceLatLngs: function () { // (Number index, Number howMany)
       
  4868 		var removed = [].splice.apply(this._latlngs, arguments);
       
  4869 		this._convertLatLngs(this._latlngs);
       
  4870 		this.redraw();
       
  4871 		return removed;
       
  4872 	},
       
  4873 
       
  4874 	closestLayerPoint: function (p) {
       
  4875 		var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
       
  4876 
       
  4877 		for (var j = 0, jLen = parts.length; j < jLen; j++) {
       
  4878 			var points = parts[j];
       
  4879 			for (var i = 1, len = points.length; i < len; i++) {
       
  4880 				p1 = points[i - 1];
       
  4881 				p2 = points[i];
       
  4882 				var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
       
  4883 				if (sqDist < minDistance) {
       
  4884 					minDistance = sqDist;
       
  4885 					minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
       
  4886 				}
       
  4887 			}
       
  4888 		}
       
  4889 		if (minPoint) {
       
  4890 			minPoint.distance = Math.sqrt(minDistance);
       
  4891 		}
       
  4892 		return minPoint;
       
  4893 	},
       
  4894 
       
  4895 	getBounds: function () {
       
  4896 		var bounds = new L.LatLngBounds(),
       
  4897 		    latLngs = this.getLatLngs(),
       
  4898 		    i, len;
       
  4899 
       
  4900 		for (i = 0, len = latLngs.length; i < len; i++) {
       
  4901 			bounds.extend(latLngs[i]);
       
  4902 		}
       
  4903 
       
  4904 		return bounds;
       
  4905 	},
       
  4906 
       
  4907 	_convertLatLngs: function (latlngs) {
       
  4908 		var i, len;
       
  4909 		for (i = 0, len = latlngs.length; i < len; i++) {
       
  4910 			if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
       
  4911 				return;
       
  4912 			}
       
  4913 			latlngs[i] = L.latLng(latlngs[i]);
       
  4914 		}
       
  4915 		return latlngs;
       
  4916 	},
       
  4917 
       
  4918 	_initEvents: function () {
       
  4919 		L.Path.prototype._initEvents.call(this);
       
  4920 	},
       
  4921 
       
  4922 	_getPathPartStr: function (points) {
       
  4923 		var round = L.Path.VML;
       
  4924 
       
  4925 		for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
       
  4926 			p = points[j];
       
  4927 			if (round) {
       
  4928 				p._round();
       
  4929 			}
       
  4930 			str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
       
  4931 		}
       
  4932 		return str;
       
  4933 	},
       
  4934 
       
  4935 	_clipPoints: function () {
       
  4936 		var points = this._originalPoints,
       
  4937 		    len = points.length,
       
  4938 		    i, k, segment;
       
  4939 
       
  4940 		if (this.options.noClip) {
       
  4941 			this._parts = [points];
       
  4942 			return;
       
  4943 		}
       
  4944 
       
  4945 		this._parts = [];
       
  4946 
       
  4947 		var parts = this._parts,
       
  4948 		    vp = this._map._pathViewport,
       
  4949 		    lu = L.LineUtil;
       
  4950 
       
  4951 		for (i = 0, k = 0; i < len - 1; i++) {
       
  4952 			segment = lu.clipSegment(points[i], points[i + 1], vp, i);
       
  4953 			if (!segment) {
       
  4954 				continue;
       
  4955 			}
       
  4956 
       
  4957 			parts[k] = parts[k] || [];
       
  4958 			parts[k].push(segment[0]);
       
  4959 
       
  4960 			// if segment goes out of screen, or it's the last one, it's the end of the line part
       
  4961 			if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
       
  4962 				parts[k].push(segment[1]);
       
  4963 				k++;
       
  4964 			}
       
  4965 		}
       
  4966 	},
       
  4967 
       
  4968 	// simplify each clipped part of the polyline
       
  4969 	_simplifyPoints: function () {
       
  4970 		var parts = this._parts,
       
  4971 		    lu = L.LineUtil;
       
  4972 
       
  4973 		for (var i = 0, len = parts.length; i < len; i++) {
       
  4974 			parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
       
  4975 		}
       
  4976 	},
       
  4977 
       
  4978 	_updatePath: function () {
       
  4979 		if (!this._map) { return; }
       
  4980 
       
  4981 		this._clipPoints();
       
  4982 		this._simplifyPoints();
       
  4983 
       
  4984 		L.Path.prototype._updatePath.call(this);
       
  4985 	}
       
  4986 });
       
  4987 
       
  4988 L.polyline = function (latlngs, options) {
       
  4989 	return new L.Polyline(latlngs, options);
       
  4990 };
       
  4991 
       
  4992 
       
  4993 /*
       
  4994  * L.PolyUtil contains utility functions for polygons (clipping, etc.).
       
  4995  */
       
  4996 
       
  4997 /*jshint bitwise:false */ // allow bitwise operations here
       
  4998 
       
  4999 L.PolyUtil = {};
       
  5000 
       
  5001 /*
       
  5002  * Sutherland-Hodgeman polygon clipping algorithm.
       
  5003  * Used to avoid rendering parts of a polygon that are not currently visible.
       
  5004  */
       
  5005 L.PolyUtil.clipPolygon = function (points, bounds) {
       
  5006 	var clippedPoints,
       
  5007 	    edges = [1, 4, 2, 8],
       
  5008 	    i, j, k,
       
  5009 	    a, b,
       
  5010 	    len, edge, p,
       
  5011 	    lu = L.LineUtil;
       
  5012 
       
  5013 	for (i = 0, len = points.length; i < len; i++) {
       
  5014 		points[i]._code = lu._getBitCode(points[i], bounds);
       
  5015 	}
       
  5016 
       
  5017 	// for each edge (left, bottom, right, top)
       
  5018 	for (k = 0; k < 4; k++) {
       
  5019 		edge = edges[k];
       
  5020 		clippedPoints = [];
       
  5021 
       
  5022 		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
       
  5023 			a = points[i];
       
  5024 			b = points[j];
       
  5025 
       
  5026 			// if a is inside the clip window
       
  5027 			if (!(a._code & edge)) {
       
  5028 				// if b is outside the clip window (a->b goes out of screen)
       
  5029 				if (b._code & edge) {
       
  5030 					p = lu._getEdgeIntersection(b, a, edge, bounds);
       
  5031 					p._code = lu._getBitCode(p, bounds);
       
  5032 					clippedPoints.push(p);
       
  5033 				}
       
  5034 				clippedPoints.push(a);
       
  5035 
       
  5036 			// else if b is inside the clip window (a->b enters the screen)
       
  5037 			} else if (!(b._code & edge)) {
       
  5038 				p = lu._getEdgeIntersection(b, a, edge, bounds);
       
  5039 				p._code = lu._getBitCode(p, bounds);
       
  5040 				clippedPoints.push(p);
       
  5041 			}
       
  5042 		}
       
  5043 		points = clippedPoints;
       
  5044 	}
       
  5045 
       
  5046 	return points;
       
  5047 };
       
  5048 
       
  5049 
       
  5050 /*
       
  5051  * L.Polygon is used to display polygons on a map.
       
  5052  */
       
  5053 
       
  5054 L.Polygon = L.Polyline.extend({
       
  5055 	options: {
       
  5056 		fill: true
       
  5057 	},
       
  5058 
       
  5059 	initialize: function (latlngs, options) {
       
  5060 		L.Polyline.prototype.initialize.call(this, latlngs, options);
       
  5061 
       
  5062 		if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
       
  5063 			this._latlngs = this._convertLatLngs(latlngs[0]);
       
  5064 			this._holes = latlngs.slice(1);
       
  5065 		}
       
  5066 	},
       
  5067 
       
  5068 	projectLatlngs: function () {
       
  5069 		L.Polyline.prototype.projectLatlngs.call(this);
       
  5070 
       
  5071 		// project polygon holes points
       
  5072 		// TODO move this logic to Polyline to get rid of duplication
       
  5073 		this._holePoints = [];
       
  5074 
       
  5075 		if (!this._holes) { return; }
       
  5076 
       
  5077 		var i, j, len, len2;
       
  5078 
       
  5079 		for (i = 0, len = this._holes.length; i < len; i++) {
       
  5080 			this._holePoints[i] = [];
       
  5081 
       
  5082 			for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
       
  5083 				this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
       
  5084 			}
       
  5085 		}
       
  5086 	},
       
  5087 
       
  5088 	_clipPoints: function () {
       
  5089 		var points = this._originalPoints,
       
  5090 		    newParts = [];
       
  5091 
       
  5092 		this._parts = [points].concat(this._holePoints);
       
  5093 
       
  5094 		if (this.options.noClip) { return; }
       
  5095 
       
  5096 		for (var i = 0, len = this._parts.length; i < len; i++) {
       
  5097 			var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
       
  5098 			if (clipped.length) {
       
  5099 				newParts.push(clipped);
       
  5100 			}
       
  5101 		}
       
  5102 
       
  5103 		this._parts = newParts;
       
  5104 	},
       
  5105 
       
  5106 	_getPathPartStr: function (points) {
       
  5107 		var str = L.Polyline.prototype._getPathPartStr.call(this, points);
       
  5108 		return str + (L.Browser.svg ? 'z' : 'x');
       
  5109 	}
       
  5110 });
       
  5111 
       
  5112 L.polygon = function (latlngs, options) {
       
  5113 	return new L.Polygon(latlngs, options);
       
  5114 };
       
  5115 
       
  5116 
       
  5117 /*
       
  5118  * Contains L.MultiPolyline and L.MultiPolygon layers.
       
  5119  */
       
  5120 
       
  5121 (function () {
       
  5122 	function createMulti(Klass) {
       
  5123 
       
  5124 		return L.FeatureGroup.extend({
       
  5125 
       
  5126 			initialize: function (latlngs, options) {
       
  5127 				this._layers = {};
       
  5128 				this._options = options;
       
  5129 				this.setLatLngs(latlngs);
       
  5130 			},
       
  5131 
       
  5132 			setLatLngs: function (latlngs) {
       
  5133 				var i = 0,
       
  5134 				    len = latlngs.length;
       
  5135 
       
  5136 				this.eachLayer(function (layer) {
       
  5137 					if (i < len) {
       
  5138 						layer.setLatLngs(latlngs[i++]);
       
  5139 					} else {
       
  5140 						this.removeLayer(layer);
       
  5141 					}
       
  5142 				}, this);
       
  5143 
       
  5144 				while (i < len) {
       
  5145 					this.addLayer(new Klass(latlngs[i++], this._options));
       
  5146 				}
       
  5147 
       
  5148 				return this;
       
  5149 			}
       
  5150 		});
       
  5151 	}
       
  5152 
       
  5153 	L.MultiPolyline = createMulti(L.Polyline);
       
  5154 	L.MultiPolygon = createMulti(L.Polygon);
       
  5155 
       
  5156 	L.multiPolyline = function (latlngs, options) {
       
  5157 		return new L.MultiPolyline(latlngs, options);
       
  5158 	};
       
  5159 
       
  5160 	L.multiPolygon = function (latlngs, options) {
       
  5161 		return new L.MultiPolygon(latlngs, options);
       
  5162 	};
       
  5163 }());
       
  5164 
       
  5165 
       
  5166 /*
       
  5167  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
       
  5168  */
       
  5169 
       
  5170 L.Rectangle = L.Polygon.extend({
       
  5171 	initialize: function (latLngBounds, options) {
       
  5172 		L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
       
  5173 	},
       
  5174 
       
  5175 	setBounds: function (latLngBounds) {
       
  5176 		this.setLatLngs(this._boundsToLatLngs(latLngBounds));
       
  5177 	},
       
  5178 
       
  5179 	_boundsToLatLngs: function (latLngBounds) {
       
  5180 		latLngBounds = L.latLngBounds(latLngBounds);
       
  5181 		return [
       
  5182 			latLngBounds.getSouthWest(),
       
  5183 			latLngBounds.getNorthWest(),
       
  5184 			latLngBounds.getNorthEast(),
       
  5185 			latLngBounds.getSouthEast()
       
  5186 		];
       
  5187 	}
       
  5188 });
       
  5189 
       
  5190 L.rectangle = function (latLngBounds, options) {
       
  5191 	return new L.Rectangle(latLngBounds, options);
       
  5192 };
       
  5193 
       
  5194 
       
  5195 /*
       
  5196  * L.Circle is a circle overlay (with a certain radius in meters).
       
  5197  */
       
  5198 
       
  5199 L.Circle = L.Path.extend({
       
  5200 	initialize: function (latlng, radius, options) {
       
  5201 		L.Path.prototype.initialize.call(this, options);
       
  5202 
       
  5203 		this._latlng = L.latLng(latlng);
       
  5204 		this._mRadius = radius;
       
  5205 	},
       
  5206 
       
  5207 	options: {
       
  5208 		fill: true
       
  5209 	},
       
  5210 
       
  5211 	setLatLng: function (latlng) {
       
  5212 		this._latlng = L.latLng(latlng);
       
  5213 		return this.redraw();
       
  5214 	},
       
  5215 
       
  5216 	setRadius: function (radius) {
       
  5217 		this._mRadius = radius;
       
  5218 		return this.redraw();
       
  5219 	},
       
  5220 
       
  5221 	projectLatlngs: function () {
       
  5222 		var lngRadius = this._getLngRadius(),
       
  5223 		    latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius),
       
  5224 		    point2 = this._map.latLngToLayerPoint(latlng2);
       
  5225 
       
  5226 		this._point = this._map.latLngToLayerPoint(this._latlng);
       
  5227 		this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
       
  5228 	},
       
  5229 
       
  5230 	getBounds: function () {
       
  5231 		var lngRadius = this._getLngRadius(),
       
  5232 		    latRadius = (this._mRadius / 40075017) * 360,
       
  5233 		    latlng = this._latlng,
       
  5234 		    sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
       
  5235 		    ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
       
  5236 
       
  5237 		return new L.LatLngBounds(sw, ne);
       
  5238 	},
       
  5239 
       
  5240 	getLatLng: function () {
       
  5241 		return this._latlng;
       
  5242 	},
       
  5243 
       
  5244 	getPathString: function () {
       
  5245 		var p = this._point,
       
  5246 		    r = this._radius;
       
  5247 
       
  5248 		if (this._checkIfEmpty()) {
       
  5249 			return '';
       
  5250 		}
       
  5251 
       
  5252 		if (L.Browser.svg) {
       
  5253 			return "M" + p.x + "," + (p.y - r) +
       
  5254 			       "A" + r + "," + r + ",0,1,1," +
       
  5255 			       (p.x - 0.1) + "," + (p.y - r) + " z";
       
  5256 		} else {
       
  5257 			p._round();
       
  5258 			r = Math.round(r);
       
  5259 			return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
       
  5260 		}
       
  5261 	},
       
  5262 
       
  5263 	getRadius: function () {
       
  5264 		return this._mRadius;
       
  5265 	},
       
  5266 
       
  5267 	// TODO Earth hardcoded, move into projection code!
       
  5268 
       
  5269 	_getLatRadius: function () {
       
  5270 		return (this._mRadius / 40075017) * 360;
       
  5271 	},
       
  5272 
       
  5273 	_getLngRadius: function () {
       
  5274 		return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
       
  5275 	},
       
  5276 
       
  5277 	_checkIfEmpty: function () {
       
  5278 		if (!this._map) {
       
  5279 			return false;
       
  5280 		}
       
  5281 		var vp = this._map._pathViewport,
       
  5282 		    r = this._radius,
       
  5283 		    p = this._point;
       
  5284 
       
  5285 		return p.x - r > vp.max.x || p.y - r > vp.max.y ||
       
  5286 		       p.x + r < vp.min.x || p.y + r < vp.min.y;
       
  5287 	}
       
  5288 });
       
  5289 
       
  5290 L.circle = function (latlng, radius, options) {
       
  5291 	return new L.Circle(latlng, radius, options);
       
  5292 };
       
  5293 
       
  5294 
       
  5295 /*
       
  5296  * L.CircleMarker is a circle overlay with a permanent pixel radius.
       
  5297  */
       
  5298 
       
  5299 L.CircleMarker = L.Circle.extend({
       
  5300 	options: {
       
  5301 		radius: 10,
       
  5302 		weight: 2
       
  5303 	},
       
  5304 
       
  5305 	initialize: function (latlng, options) {
       
  5306 		L.Circle.prototype.initialize.call(this, latlng, null, options);
       
  5307 		this._radius = this.options.radius;
       
  5308 	},
       
  5309 
       
  5310 	projectLatlngs: function () {
       
  5311 		this._point = this._map.latLngToLayerPoint(this._latlng);
       
  5312 	},
       
  5313 	
       
  5314 	_updateStyle : function () {
       
  5315 		L.Circle.prototype._updateStyle.call(this);
       
  5316 		this.setRadius(this.options.radius);
       
  5317 	},
       
  5318 
       
  5319 	setRadius: function (radius) {
       
  5320 		this.options.radius = this._radius = radius;
       
  5321 		return this.redraw();
       
  5322 	}
       
  5323 });
       
  5324 
       
  5325 L.circleMarker = function (latlng, options) {
       
  5326 	return new L.CircleMarker(latlng, options);
       
  5327 };
       
  5328 
       
  5329 
       
  5330 /*
       
  5331  * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
       
  5332  */
       
  5333 
       
  5334 L.Polyline.include(!L.Path.CANVAS ? {} : {
       
  5335 	_containsPoint: function (p, closed) {
       
  5336 		var i, j, k, len, len2, dist, part,
       
  5337 		    w = this.options.weight / 2;
       
  5338 
       
  5339 		if (L.Browser.touch) {
       
  5340 			w += 10; // polyline click tolerance on touch devices
       
  5341 		}
       
  5342 
       
  5343 		for (i = 0, len = this._parts.length; i < len; i++) {
       
  5344 			part = this._parts[i];
       
  5345 			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
       
  5346 				if (!closed && (j === 0)) {
       
  5347 					continue;
       
  5348 				}
       
  5349 
       
  5350 				dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
       
  5351 
       
  5352 				if (dist <= w) {
       
  5353 					return true;
       
  5354 				}
       
  5355 			}
       
  5356 		}
       
  5357 		return false;
       
  5358 	}
       
  5359 });
       
  5360 
       
  5361 
       
  5362 /*
       
  5363  * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
       
  5364  */
       
  5365 
       
  5366 L.Polygon.include(!L.Path.CANVAS ? {} : {
       
  5367 	_containsPoint: function (p) {
       
  5368 		var inside = false,
       
  5369 		    part, p1, p2,
       
  5370 		    i, j, k,
       
  5371 		    len, len2;
       
  5372 
       
  5373 		// TODO optimization: check if within bounds first
       
  5374 
       
  5375 		if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
       
  5376 			// click on polygon border
       
  5377 			return true;
       
  5378 		}
       
  5379 
       
  5380 		// ray casting algorithm for detecting if point is in polygon
       
  5381 
       
  5382 		for (i = 0, len = this._parts.length; i < len; i++) {
       
  5383 			part = this._parts[i];
       
  5384 
       
  5385 			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
       
  5386 				p1 = part[j];
       
  5387 				p2 = part[k];
       
  5388 
       
  5389 				if (((p1.y > p.y) !== (p2.y > p.y)) &&
       
  5390 						(p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
       
  5391 					inside = !inside;
       
  5392 				}
       
  5393 			}
       
  5394 		}
       
  5395 
       
  5396 		return inside;
       
  5397 	}
       
  5398 });
       
  5399 
       
  5400 
       
  5401 /*
       
  5402  * Extends L.Circle with Canvas-specific code.
       
  5403  */
       
  5404 
       
  5405 L.Circle.include(!L.Path.CANVAS ? {} : {
       
  5406 	_drawPath: function () {
       
  5407 		var p = this._point;
       
  5408 		this._ctx.beginPath();
       
  5409 		this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
       
  5410 	},
       
  5411 
       
  5412 	_containsPoint: function (p) {
       
  5413 		var center = this._point,
       
  5414 		    w2 = this.options.stroke ? this.options.weight / 2 : 0;
       
  5415 
       
  5416 		return (p.distanceTo(center) <= this._radius + w2);
       
  5417 	}
       
  5418 });
       
  5419 
       
  5420 
       
  5421 /*
       
  5422  * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
       
  5423  */
       
  5424 
       
  5425 L.GeoJSON = L.FeatureGroup.extend({
       
  5426 
       
  5427 	initialize: function (geojson, options) {
       
  5428 		L.setOptions(this, options);
       
  5429 
       
  5430 		this._layers = {};
       
  5431 
       
  5432 		if (geojson) {
       
  5433 			this.addData(geojson);
       
  5434 		}
       
  5435 	},
       
  5436 
       
  5437 	addData: function (geojson) {
       
  5438 		var features = L.Util.isArray(geojson) ? geojson : geojson.features,
       
  5439 		    i, len;
       
  5440 
       
  5441 		if (features) {
       
  5442 			for (i = 0, len = features.length; i < len; i++) {
       
  5443 				// Only add this if geometry or geometries are set and not null
       
  5444 				if (features[i].geometries || features[i].geometry || features[i].features) {
       
  5445 					this.addData(features[i]);
       
  5446 				}
       
  5447 			}
       
  5448 			return this;
       
  5449 		}
       
  5450 
       
  5451 		var options = this.options;
       
  5452 
       
  5453 		if (options.filter && !options.filter(geojson)) { return; }
       
  5454 
       
  5455 		var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
       
  5456 		layer.feature = geojson;
       
  5457 
       
  5458 		layer.defaultOptions = layer.options;
       
  5459 		this.resetStyle(layer);
       
  5460 
       
  5461 		if (options.onEachFeature) {
       
  5462 			options.onEachFeature(geojson, layer);
       
  5463 		}
       
  5464 
       
  5465 		return this.addLayer(layer);
       
  5466 	},
       
  5467 
       
  5468 	resetStyle: function (layer) {
       
  5469 		var style = this.options.style;
       
  5470 		if (style) {
       
  5471 			// reset any custom styles
       
  5472 			L.Util.extend(layer.options, layer.defaultOptions);
       
  5473 
       
  5474 			this._setLayerStyle(layer, style);
       
  5475 		}
       
  5476 	},
       
  5477 
       
  5478 	setStyle: function (style) {
       
  5479 		this.eachLayer(function (layer) {
       
  5480 			this._setLayerStyle(layer, style);
       
  5481 		}, this);
       
  5482 	},
       
  5483 
       
  5484 	_setLayerStyle: function (layer, style) {
       
  5485 		if (typeof style === 'function') {
       
  5486 			style = style(layer.feature);
       
  5487 		}
       
  5488 		if (layer.setStyle) {
       
  5489 			layer.setStyle(style);
       
  5490 		}
       
  5491 	}
       
  5492 });
       
  5493 
       
  5494 L.extend(L.GeoJSON, {
       
  5495 	geometryToLayer: function (geojson, pointToLayer) {
       
  5496 		var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
       
  5497 		    coords = geometry.coordinates,
       
  5498 		    layers = [],
       
  5499 		    latlng, latlngs, i, len, layer;
       
  5500 
       
  5501 		switch (geometry.type) {
       
  5502 		case 'Point':
       
  5503 			latlng = this.coordsToLatLng(coords);
       
  5504 			return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
       
  5505 
       
  5506 		case 'MultiPoint':
       
  5507 			for (i = 0, len = coords.length; i < len; i++) {
       
  5508 				latlng = this.coordsToLatLng(coords[i]);
       
  5509 				layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
       
  5510 				layers.push(layer);
       
  5511 			}
       
  5512 			return new L.FeatureGroup(layers);
       
  5513 
       
  5514 		case 'LineString':
       
  5515 			latlngs = this.coordsToLatLngs(coords);
       
  5516 			return new L.Polyline(latlngs);
       
  5517 
       
  5518 		case 'Polygon':
       
  5519 			latlngs = this.coordsToLatLngs(coords, 1);
       
  5520 			return new L.Polygon(latlngs);
       
  5521 
       
  5522 		case 'MultiLineString':
       
  5523 			latlngs = this.coordsToLatLngs(coords, 1);
       
  5524 			return new L.MultiPolyline(latlngs);
       
  5525 
       
  5526 		case 'MultiPolygon':
       
  5527 			latlngs = this.coordsToLatLngs(coords, 2);
       
  5528 			return new L.MultiPolygon(latlngs);
       
  5529 
       
  5530 		case 'GeometryCollection':
       
  5531 			for (i = 0, len = geometry.geometries.length; i < len; i++) {
       
  5532 				layer = this.geometryToLayer({
       
  5533 					geometry: geometry.geometries[i],
       
  5534 					type: 'Feature',
       
  5535 					properties: geojson.properties
       
  5536 				}, pointToLayer);
       
  5537 				layers.push(layer);
       
  5538 			}
       
  5539 			return new L.FeatureGroup(layers);
       
  5540 
       
  5541 		default:
       
  5542 			throw new Error('Invalid GeoJSON object.');
       
  5543 		}
       
  5544 	},
       
  5545 
       
  5546 	coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
       
  5547 		var lat = parseFloat(coords[reverse ? 0 : 1]),
       
  5548 		    lng = parseFloat(coords[reverse ? 1 : 0]);
       
  5549 
       
  5550 		return new L.LatLng(lat, lng);
       
  5551 	},
       
  5552 
       
  5553 	coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
       
  5554 		var latlng,
       
  5555 		    latlngs = [],
       
  5556 		    i, len;
       
  5557 
       
  5558 		for (i = 0, len = coords.length; i < len; i++) {
       
  5559 			latlng = levelsDeep ?
       
  5560 			        this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
       
  5561 			        this.coordsToLatLng(coords[i], reverse);
       
  5562 
       
  5563 			latlngs.push(latlng);
       
  5564 		}
       
  5565 
       
  5566 		return latlngs;
       
  5567 	}
       
  5568 });
       
  5569 
       
  5570 L.geoJson = function (geojson, options) {
       
  5571 	return new L.GeoJSON(geojson, options);
       
  5572 };
       
  5573 
       
  5574 
       
  5575 /*
       
  5576  * L.DomEvent contains functions for working with DOM events.
       
  5577  */
       
  5578 
       
  5579 L.DomEvent = {
       
  5580 	/* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
       
  5581 	addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
       
  5582 
       
  5583 		var id = L.stamp(fn),
       
  5584 		    key = '_leaflet_' + type + id,
       
  5585 		    handler, originalHandler, newType;
       
  5586 
       
  5587 		if (obj[key]) { return this; }
       
  5588 
       
  5589 		handler = function (e) {
       
  5590 			return fn.call(context || obj, e || L.DomEvent._getEvent());
       
  5591 		};
       
  5592 
       
  5593 		if (L.Browser.msTouch && type.indexOf('touch') === 0) {
       
  5594 			return this.addMsTouchListener(obj, type, handler, id);
       
  5595 		}
       
  5596 		if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
       
  5597 			this.addDoubleTapListener(obj, handler, id);
       
  5598 		}
       
  5599 
       
  5600 		if ('addEventListener' in obj) {
       
  5601 
       
  5602 			if (type === 'mousewheel') {
       
  5603 				obj.addEventListener('DOMMouseScroll', handler, false);
       
  5604 				obj.addEventListener(type, handler, false);
       
  5605 
       
  5606 			} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
       
  5607 
       
  5608 				originalHandler = handler;
       
  5609 				newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
       
  5610 
       
  5611 				handler = function (e) {
       
  5612 					if (!L.DomEvent._checkMouse(obj, e)) { return; }
       
  5613 					return originalHandler(e);
       
  5614 				};
       
  5615 
       
  5616 				obj.addEventListener(newType, handler, false);
       
  5617 
       
  5618 			} else {
       
  5619 				obj.addEventListener(type, handler, false);
       
  5620 			}
       
  5621 
       
  5622 		} else if ('attachEvent' in obj) {
       
  5623 			obj.attachEvent("on" + type, handler);
       
  5624 		}
       
  5625 
       
  5626 		obj[key] = handler;
       
  5627 
       
  5628 		return this;
       
  5629 	},
       
  5630 
       
  5631 	removeListener: function (obj, type, fn) {  // (HTMLElement, String, Function)
       
  5632 
       
  5633 		var id = L.stamp(fn),
       
  5634 		    key = '_leaflet_' + type + id,
       
  5635 		    handler = obj[key];
       
  5636 
       
  5637 		if (!handler) { return; }
       
  5638 
       
  5639 		if (L.Browser.msTouch && type.indexOf('touch') === 0) {
       
  5640 			this.removeMsTouchListener(obj, type, id);
       
  5641 		} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
       
  5642 			this.removeDoubleTapListener(obj, id);
       
  5643 
       
  5644 		} else if ('removeEventListener' in obj) {
       
  5645 
       
  5646 			if (type === 'mousewheel') {
       
  5647 				obj.removeEventListener('DOMMouseScroll', handler, false);
       
  5648 				obj.removeEventListener(type, handler, false);
       
  5649 
       
  5650 			} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
       
  5651 				obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
       
  5652 			} else {
       
  5653 				obj.removeEventListener(type, handler, false);
       
  5654 			}
       
  5655 		} else if ('detachEvent' in obj) {
       
  5656 			obj.detachEvent("on" + type, handler);
       
  5657 		}
       
  5658 
       
  5659 		obj[key] = null;
       
  5660 
       
  5661 		return this;
       
  5662 	},
       
  5663 
       
  5664 	stopPropagation: function (e) {
       
  5665 
       
  5666 		if (e.stopPropagation) {
       
  5667 			e.stopPropagation();
       
  5668 		} else {
       
  5669 			e.cancelBubble = true;
       
  5670 		}
       
  5671 		return this;
       
  5672 	},
       
  5673 
       
  5674 	disableClickPropagation: function (el) {
       
  5675 
       
  5676 		var stop = L.DomEvent.stopPropagation;
       
  5677 
       
  5678 		for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
       
  5679 			L.DomEvent.addListener(el, L.Draggable.START[i], stop);
       
  5680 		}
       
  5681 
       
  5682 		return L.DomEvent
       
  5683 			.addListener(el, 'click', stop)
       
  5684 			.addListener(el, 'dblclick', stop);
       
  5685 	},
       
  5686 
       
  5687 	preventDefault: function (e) {
       
  5688 
       
  5689 		if (e.preventDefault) {
       
  5690 			e.preventDefault();
       
  5691 		} else {
       
  5692 			e.returnValue = false;
       
  5693 		}
       
  5694 		return this;
       
  5695 	},
       
  5696 
       
  5697 	stop: function (e) {
       
  5698 		return L.DomEvent.preventDefault(e).stopPropagation(e);
       
  5699 	},
       
  5700 
       
  5701 	getMousePosition: function (e, container) {
       
  5702 
       
  5703 		var body = document.body,
       
  5704 		    docEl = document.documentElement,
       
  5705 		    x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
       
  5706 		    y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
       
  5707 		    pos = new L.Point(x, y);
       
  5708 
       
  5709 		return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
       
  5710 	},
       
  5711 
       
  5712 	getWheelDelta: function (e) {
       
  5713 
       
  5714 		var delta = 0;
       
  5715 
       
  5716 		if (e.wheelDelta) {
       
  5717 			delta = e.wheelDelta / 120;
       
  5718 		}
       
  5719 		if (e.detail) {
       
  5720 			delta = -e.detail / 3;
       
  5721 		}
       
  5722 		return delta;
       
  5723 	},
       
  5724 
       
  5725 	// check if element really left/entered the event target (for mouseenter/mouseleave)
       
  5726 	_checkMouse: function (el, e) {
       
  5727 
       
  5728 		var related = e.relatedTarget;
       
  5729 
       
  5730 		if (!related) { return true; }
       
  5731 
       
  5732 		try {
       
  5733 			while (related && (related !== el)) {
       
  5734 				related = related.parentNode;
       
  5735 			}
       
  5736 		} catch (err) {
       
  5737 			return false;
       
  5738 		}
       
  5739 		return (related !== el);
       
  5740 	},
       
  5741 
       
  5742 	_getEvent: function () { // evil magic for IE
       
  5743 		/*jshint noarg:false */
       
  5744 		var e = window.event;
       
  5745 		if (!e) {
       
  5746 			var caller = arguments.callee.caller;
       
  5747 			while (caller) {
       
  5748 				e = caller['arguments'][0];
       
  5749 				if (e && window.Event === e.constructor) {
       
  5750 					break;
       
  5751 				}
       
  5752 				caller = caller.caller;
       
  5753 			}
       
  5754 		}
       
  5755 		return e;
       
  5756 	}
       
  5757 };
       
  5758 
       
  5759 L.DomEvent.on = L.DomEvent.addListener;
       
  5760 L.DomEvent.off = L.DomEvent.removeListener;
       
  5761 
       
  5762 
       
  5763 /*
       
  5764  * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
       
  5765  */
       
  5766 
       
  5767 L.Draggable = L.Class.extend({
       
  5768 	includes: L.Mixin.Events,
       
  5769 
       
  5770 	statics: {
       
  5771 		START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
       
  5772 		END: {
       
  5773 			mousedown: 'mouseup',
       
  5774 			touchstart: 'touchend',
       
  5775 			MSPointerDown: 'touchend'
       
  5776 		},
       
  5777 		MOVE: {
       
  5778 			mousedown: 'mousemove',
       
  5779 			touchstart: 'touchmove',
       
  5780 			MSPointerDown: 'touchmove'
       
  5781 		},
       
  5782 		TAP_TOLERANCE: 15
       
  5783 	},
       
  5784 
       
  5785 	initialize: function (element, dragStartTarget, longPress) {
       
  5786 		this._element = element;
       
  5787 		this._dragStartTarget = dragStartTarget || element;
       
  5788 		this._longPress = longPress && !L.Browser.msTouch;
       
  5789 	},
       
  5790 
       
  5791 	enable: function () {
       
  5792 		if (this._enabled) { return; }
       
  5793 
       
  5794 		for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
       
  5795 			L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
       
  5796 		}
       
  5797 		this._enabled = true;
       
  5798 	},
       
  5799 
       
  5800 	disable: function () {
       
  5801 		if (!this._enabled) { return; }
       
  5802 
       
  5803 		for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
       
  5804 			L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
       
  5805 		}
       
  5806 		this._enabled = false;
       
  5807 		this._moved = false;
       
  5808 	},
       
  5809 
       
  5810 	_onDown: function (e) {
       
  5811 		if ((!L.Browser.touch && e.shiftKey) ||
       
  5812 		    ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
       
  5813 
       
  5814 		L.DomEvent.preventDefault(e);
       
  5815 		L.DomEvent.stopPropagation(e);
       
  5816 
       
  5817 		if (L.Draggable._disabled) { return; }
       
  5818 
       
  5819 		this._simulateClick = true;
       
  5820 
       
  5821 		if (e.touches && e.touches.length > 1) {
       
  5822 			this._simulateClick = false;
       
  5823 			clearTimeout(this._longPressTimeout);
       
  5824 			return;
       
  5825 		}
       
  5826 
       
  5827 		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
       
  5828 		    el = first.target;
       
  5829 
       
  5830 		if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
       
  5831 			L.DomUtil.addClass(el, 'leaflet-active');
       
  5832 		}
       
  5833 
       
  5834 		this._moved = false;
       
  5835 		if (this._moving) { return; }
       
  5836 
       
  5837 		this._startPoint = new L.Point(first.clientX, first.clientY);
       
  5838 		this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
       
  5839 
       
  5840 		//Touch contextmenu event emulation
       
  5841 		if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) {
       
  5842 			this._longPressTimeout = setTimeout(L.bind(function () {
       
  5843 				var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
       
  5844 
       
  5845 				if (dist < L.Draggable.TAP_TOLERANCE) {
       
  5846 					this._simulateClick = false;
       
  5847 					this._onUp();
       
  5848 					this._simulateEvent('contextmenu', first);
       
  5849 				}
       
  5850 			}, this), 1000);
       
  5851 		}
       
  5852 
       
  5853 		L.DomEvent.on(document, L.Draggable.MOVE[e.type], this._onMove, this);
       
  5854 		L.DomEvent.on(document, L.Draggable.END[e.type], this._onUp, this);
       
  5855 	},
       
  5856 
       
  5857 	_onMove: function (e) {
       
  5858 		if (e.touches && e.touches.length > 1) { return; }
       
  5859 
       
  5860 		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
       
  5861 		    newPoint = new L.Point(first.clientX, first.clientY),
       
  5862 		    diffVec = newPoint.subtract(this._startPoint);
       
  5863 
       
  5864 		if (!diffVec.x && !diffVec.y) { return; }
       
  5865 
       
  5866 		L.DomEvent.preventDefault(e);
       
  5867 
       
  5868 		if (!this._moved) {
       
  5869 			this.fire('dragstart');
       
  5870 			this._moved = true;
       
  5871 
       
  5872 			this._startPos = L.DomUtil.getPosition(this._element).subtract(diffVec);
       
  5873 
       
  5874 			if (!L.Browser.touch) {
       
  5875 				L.DomUtil.disableTextSelection();
       
  5876 				this._setMovingCursor();
       
  5877 			}
       
  5878 		}
       
  5879 
       
  5880 		this._newPos = this._startPos.add(diffVec);
       
  5881 		this._moving = true;
       
  5882 
       
  5883 		L.Util.cancelAnimFrame(this._animRequest);
       
  5884 		this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
       
  5885 	},
       
  5886 
       
  5887 	_updatePosition: function () {
       
  5888 		this.fire('predrag');
       
  5889 		L.DomUtil.setPosition(this._element, this._newPos);
       
  5890 		this.fire('drag');
       
  5891 	},
       
  5892 
       
  5893 	_onUp: function (e) {
       
  5894 		var simulateClickTouch;
       
  5895 		clearTimeout(this._longPressTimeout);
       
  5896 		if (this._simulateClick && e.changedTouches) {
       
  5897 			var first = e.changedTouches[0],
       
  5898 			    el = first.target,
       
  5899 			    dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
       
  5900 
       
  5901 			if (el.tagName.toLowerCase() === 'a') {
       
  5902 				L.DomUtil.removeClass(el, 'leaflet-active');
       
  5903 			}
       
  5904 
       
  5905 			if (dist < L.Draggable.TAP_TOLERANCE) {
       
  5906 				simulateClickTouch = first;
       
  5907 			}
       
  5908 		}
       
  5909 
       
  5910 		if (!L.Browser.touch) {
       
  5911 			L.DomUtil.enableTextSelection();
       
  5912 			this._restoreCursor();
       
  5913 		}
       
  5914 
       
  5915 		for (var i in L.Draggable.MOVE) {
       
  5916 			if (L.Draggable.MOVE.hasOwnProperty(i)) {
       
  5917 				L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove);
       
  5918 				L.DomEvent.off(document, L.Draggable.END[i], this._onUp);
       
  5919 			}
       
  5920 		}
       
  5921 
       
  5922 		if (this._moved) {
       
  5923 			// ensure drag is not fired after dragend
       
  5924 			L.Util.cancelAnimFrame(this._animRequest);
       
  5925 
       
  5926 			this.fire('dragend');
       
  5927 		}
       
  5928 		this._moving = false;
       
  5929 
       
  5930 		if (simulateClickTouch) {
       
  5931 			this._moved = false;
       
  5932 			this._simulateEvent('click', simulateClickTouch);
       
  5933 		}
       
  5934 	},
       
  5935 
       
  5936 	_setMovingCursor: function () {
       
  5937 		L.DomUtil.addClass(document.body, 'leaflet-dragging');
       
  5938 	},
       
  5939 
       
  5940 	_restoreCursor: function () {
       
  5941 		L.DomUtil.removeClass(document.body, 'leaflet-dragging');
       
  5942 	},
       
  5943 
       
  5944 	_simulateEvent: function (type, e) {
       
  5945 		var simulatedEvent = document.createEvent('MouseEvents');
       
  5946 
       
  5947 		simulatedEvent.initMouseEvent(
       
  5948 		        type, true, true, window, 1,
       
  5949 		        e.screenX, e.screenY,
       
  5950 		        e.clientX, e.clientY,
       
  5951 		        false, false, false, false, 0, null);
       
  5952 
       
  5953 		e.target.dispatchEvent(simulatedEvent);
       
  5954 	}
       
  5955 });
       
  5956 
       
  5957 
       
  5958 /*
       
  5959 	L.Handler is a base class for handler classes that are used internally to inject
       
  5960 	interaction features like dragging to classes like Map and Marker.
       
  5961 */
       
  5962 
       
  5963 L.Handler = L.Class.extend({
       
  5964 	initialize: function (map) {
       
  5965 		this._map = map;
       
  5966 	},
       
  5967 
       
  5968 	enable: function () {
       
  5969 		if (this._enabled) { return; }
       
  5970 
       
  5971 		this._enabled = true;
       
  5972 		this.addHooks();
       
  5973 	},
       
  5974 
       
  5975 	disable: function () {
       
  5976 		if (!this._enabled) { return; }
       
  5977 
       
  5978 		this._enabled = false;
       
  5979 		this.removeHooks();
       
  5980 	},
       
  5981 
       
  5982 	enabled: function () {
       
  5983 		return !!this._enabled;
       
  5984 	}
       
  5985 });
       
  5986 
       
  5987 
       
  5988 /*
       
  5989  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
       
  5990  */
       
  5991 
       
  5992 L.Map.mergeOptions({
       
  5993 	dragging: true,
       
  5994 
       
  5995 	inertia: !L.Browser.android23,
       
  5996 	inertiaDeceleration: 3400, // px/s^2
       
  5997 	inertiaMaxSpeed: Infinity, // px/s
       
  5998 	inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
       
  5999 	easeLinearity: 0.25,
       
  6000 
       
  6001 	longPress: true,
       
  6002 
       
  6003 	// TODO refactor, move to CRS
       
  6004 	worldCopyJump: false
       
  6005 });
       
  6006 
       
  6007 L.Map.Drag = L.Handler.extend({
       
  6008 	addHooks: function () {
       
  6009 		if (!this._draggable) {
       
  6010 			var map = this._map;
       
  6011 
       
  6012 			this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress);
       
  6013 
       
  6014 			this._draggable.on({
       
  6015 				'dragstart': this._onDragStart,
       
  6016 				'drag': this._onDrag,
       
  6017 				'dragend': this._onDragEnd
       
  6018 			}, this);
       
  6019 
       
  6020 			if (map.options.worldCopyJump) {
       
  6021 				this._draggable.on('predrag', this._onPreDrag, this);
       
  6022 				map.on('viewreset', this._onViewReset, this);
       
  6023 			}
       
  6024 		}
       
  6025 		this._draggable.enable();
       
  6026 	},
       
  6027 
       
  6028 	removeHooks: function () {
       
  6029 		this._draggable.disable();
       
  6030 	},
       
  6031 
       
  6032 	moved: function () {
       
  6033 		return this._draggable && this._draggable._moved;
       
  6034 	},
       
  6035 
       
  6036 	_onDragStart: function () {
       
  6037 		var map = this._map;
       
  6038 
       
  6039 		if (map._panAnim) {
       
  6040 			map._panAnim.stop();
       
  6041 		}
       
  6042 
       
  6043 		map
       
  6044 		    .fire('movestart')
       
  6045 		    .fire('dragstart');
       
  6046 
       
  6047 		if (map.options.inertia) {
       
  6048 			this._positions = [];
       
  6049 			this._times = [];
       
  6050 		}
       
  6051 	},
       
  6052 
       
  6053 	_onDrag: function () {
       
  6054 		if (this._map.options.inertia) {
       
  6055 			var time = this._lastTime = +new Date(),
       
  6056 			    pos = this._lastPos = this._draggable._newPos;
       
  6057 
       
  6058 			this._positions.push(pos);
       
  6059 			this._times.push(time);
       
  6060 
       
  6061 			if (time - this._times[0] > 200) {
       
  6062 				this._positions.shift();
       
  6063 				this._times.shift();
       
  6064 			}
       
  6065 		}
       
  6066 
       
  6067 		this._map
       
  6068 		    .fire('move')
       
  6069 		    .fire('drag');
       
  6070 	},
       
  6071 
       
  6072 	_onViewReset: function () {
       
  6073 		// TODO fix hardcoded Earth values
       
  6074 		var pxCenter = this._map.getSize()._divideBy(2),
       
  6075 		    pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
       
  6076 
       
  6077 		this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
       
  6078 		this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
       
  6079 	},
       
  6080 
       
  6081 	_onPreDrag: function () {
       
  6082 		// TODO refactor to be able to adjust map pane position after zoom
       
  6083 		var worldWidth = this._worldWidth,
       
  6084 		    halfWidth = Math.round(worldWidth / 2),
       
  6085 		    dx = this._initialWorldOffset,
       
  6086 		    x = this._draggable._newPos.x,
       
  6087 		    newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
       
  6088 		    newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
       
  6089 		    newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
       
  6090 
       
  6091 		this._draggable._newPos.x = newX;
       
  6092 	},
       
  6093 
       
  6094 	_onDragEnd: function () {
       
  6095 		var map = this._map,
       
  6096 		    options = map.options,
       
  6097 		    delay = +new Date() - this._lastTime,
       
  6098 
       
  6099 		    noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
       
  6100 
       
  6101 		if (noInertia) {
       
  6102 			map.fire('moveend');
       
  6103 
       
  6104 		} else {
       
  6105 
       
  6106 			var direction = this._lastPos.subtract(this._positions[0]),
       
  6107 			    duration = (this._lastTime + delay - this._times[0]) / 1000,
       
  6108 			    ease = options.easeLinearity,
       
  6109 
       
  6110 			    speedVector = direction.multiplyBy(ease / duration),
       
  6111 			    speed = speedVector.distanceTo(new L.Point(0, 0)),
       
  6112 
       
  6113 			    limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
       
  6114 			    limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
       
  6115 
       
  6116 			    decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
       
  6117 			    offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
       
  6118 
       
  6119 			L.Util.requestAnimFrame(function () {
       
  6120 				map.panBy(offset, decelerationDuration, ease);
       
  6121 			});
       
  6122 		}
       
  6123 
       
  6124 		map.fire('dragend');
       
  6125 
       
  6126 		if (options.maxBounds) {
       
  6127 			// TODO predrag validation instead of animation
       
  6128 			L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container);
       
  6129 		}
       
  6130 	},
       
  6131 
       
  6132 	_panInsideMaxBounds: function () {
       
  6133 		this.panInsideBounds(this.options.maxBounds);
       
  6134 	}
       
  6135 });
       
  6136 
       
  6137 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
       
  6138 
       
  6139 
       
  6140 /*
       
  6141  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
       
  6142  */
       
  6143 
       
  6144 L.Map.mergeOptions({
       
  6145 	doubleClickZoom: true
       
  6146 });
       
  6147 
       
  6148 L.Map.DoubleClickZoom = L.Handler.extend({
       
  6149 	addHooks: function () {
       
  6150 		this._map.on('dblclick', this._onDoubleClick);
       
  6151 	},
       
  6152 
       
  6153 	removeHooks: function () {
       
  6154 		this._map.off('dblclick', this._onDoubleClick);
       
  6155 	},
       
  6156 
       
  6157 	_onDoubleClick: function (e) {
       
  6158 		this.setView(e.latlng, this._zoom + 1);
       
  6159 	}
       
  6160 });
       
  6161 
       
  6162 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
       
  6163 
       
  6164 
       
  6165 /*
       
  6166  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
       
  6167  */
       
  6168 
       
  6169 L.Map.mergeOptions({
       
  6170 	scrollWheelZoom: true
       
  6171 });
       
  6172 
       
  6173 L.Map.ScrollWheelZoom = L.Handler.extend({
       
  6174 	addHooks: function () {
       
  6175 		L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
       
  6176 		this._delta = 0;
       
  6177 	},
       
  6178 
       
  6179 	removeHooks: function () {
       
  6180 		L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
       
  6181 	},
       
  6182 
       
  6183 	_onWheelScroll: function (e) {
       
  6184 		var delta = L.DomEvent.getWheelDelta(e);
       
  6185 
       
  6186 		this._delta += delta;
       
  6187 		this._lastMousePos = this._map.mouseEventToContainerPoint(e);
       
  6188 
       
  6189 		if (!this._startTime) {
       
  6190 			this._startTime = +new Date();
       
  6191 		}
       
  6192 
       
  6193 		var left = Math.max(40 - (+new Date() - this._startTime), 0);
       
  6194 
       
  6195 		clearTimeout(this._timer);
       
  6196 		this._timer = setTimeout(L.bind(this._performZoom, this), left);
       
  6197 
       
  6198 		L.DomEvent.preventDefault(e);
       
  6199 		L.DomEvent.stopPropagation(e);
       
  6200 	},
       
  6201 
       
  6202 	_performZoom: function () {
       
  6203 		var map = this._map,
       
  6204 		    delta = this._delta,
       
  6205 		    zoom = map.getZoom();
       
  6206 
       
  6207 		delta = delta > 0 ? Math.ceil(delta) : Math.round(delta);
       
  6208 		delta = Math.max(Math.min(delta, 4), -4);
       
  6209 		delta = map._limitZoom(zoom + delta) - zoom;
       
  6210 
       
  6211 		this._delta = 0;
       
  6212 
       
  6213 		this._startTime = null;
       
  6214 
       
  6215 		if (!delta) { return; }
       
  6216 
       
  6217 		var newZoom = zoom + delta,
       
  6218 		    newCenter = this._getCenterForScrollWheelZoom(newZoom);
       
  6219 
       
  6220 		map.setView(newCenter, newZoom);
       
  6221 	},
       
  6222 
       
  6223 	_getCenterForScrollWheelZoom: function (newZoom) {
       
  6224 		var map = this._map,
       
  6225 		    scale = map.getZoomScale(newZoom),
       
  6226 		    viewHalf = map.getSize()._divideBy(2),
       
  6227 		    centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale),
       
  6228 		    newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset);
       
  6229 
       
  6230 		return map.unproject(newCenterPoint);
       
  6231 	}
       
  6232 });
       
  6233 
       
  6234 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
       
  6235 
       
  6236 
       
  6237 /*
       
  6238  * Extends the event handling code with double tap support for mobile browsers.
       
  6239  */
       
  6240 
       
  6241 L.extend(L.DomEvent, {
       
  6242 
       
  6243 	_touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
       
  6244 	_touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
       
  6245 
       
  6246 	// inspired by Zepto touch code by Thomas Fuchs
       
  6247 	addDoubleTapListener: function (obj, handler, id) {
       
  6248 		var last,
       
  6249 		    doubleTap = false,
       
  6250 		    delay = 250,
       
  6251 		    touch,
       
  6252 		    pre = '_leaflet_',
       
  6253 		    touchstart = this._touchstart,
       
  6254 		    touchend = this._touchend,
       
  6255 		    trackedTouches = [];
       
  6256 
       
  6257 		function onTouchStart(e) {
       
  6258 			var count;
       
  6259 			if (L.Browser.msTouch) {
       
  6260 				trackedTouches.push(e.pointerId);
       
  6261 				count = trackedTouches.length;
       
  6262 			} else {
       
  6263 				count = e.touches.length;
       
  6264 			}
       
  6265 			if (count > 1) {
       
  6266 				return;
       
  6267 			}
       
  6268 
       
  6269 			var now = Date.now(),
       
  6270 				delta = now - (last || now);
       
  6271 
       
  6272 			touch = e.touches ? e.touches[0] : e;
       
  6273 			doubleTap = (delta > 0 && delta <= delay);
       
  6274 			last = now;
       
  6275 		}
       
  6276 
       
  6277 		function onTouchEnd(e) {
       
  6278 			/*jshint forin:false */
       
  6279 			if (L.Browser.msTouch) {
       
  6280 				var idx = trackedTouches.indexOf(e.pointerId);
       
  6281 				if (idx === -1) {
       
  6282 					return;
       
  6283 				}
       
  6284 				trackedTouches.splice(idx, 1);
       
  6285 			}
       
  6286 
       
  6287 			if (doubleTap) {
       
  6288 				if (L.Browser.msTouch) {
       
  6289 					//Work around .type being readonly with MSPointer* events
       
  6290 					var newTouch = { },
       
  6291 						prop;
       
  6292 
       
  6293 					for (var i in touch) {
       
  6294 						prop = touch[i];
       
  6295 						if (typeof prop === 'function') {
       
  6296 							newTouch[i] = prop.bind(touch);
       
  6297 						} else {
       
  6298 							newTouch[i] = prop;
       
  6299 						}
       
  6300 					}
       
  6301 					touch = newTouch;
       
  6302 				}
       
  6303 				touch.type = 'dblclick';
       
  6304 				handler(touch);
       
  6305 				last = null;
       
  6306 			}
       
  6307 		}
       
  6308 		obj[pre + touchstart + id] = onTouchStart;
       
  6309 		obj[pre + touchend + id] = onTouchEnd;
       
  6310 
       
  6311 		//On msTouch we need to listen on the document otherwise a drag starting on the map and moving off screen will not come through to us
       
  6312 		// so we will lose track of how many touches are ongoing
       
  6313 		var endElement = L.Browser.msTouch ? document.documentElement : obj;
       
  6314 
       
  6315 		obj.addEventListener(touchstart, onTouchStart, false);
       
  6316 		endElement.addEventListener(touchend, onTouchEnd, false);
       
  6317 		if (L.Browser.msTouch) {
       
  6318 			endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
       
  6319 		}
       
  6320 		return this;
       
  6321 	},
       
  6322 
       
  6323 	removeDoubleTapListener: function (obj, id) {
       
  6324 		var pre = '_leaflet_';
       
  6325 		obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
       
  6326 		(L.Browser.msTouch ? document.documentElement : obj).removeEventListener(this._touchend, obj[pre + this._touchend + id], false);
       
  6327 		if (L.Browser.msTouch) {
       
  6328 			document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
       
  6329 		}
       
  6330 		return this;
       
  6331 	}
       
  6332 });
       
  6333 
       
  6334 
       
  6335 /*
       
  6336  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
       
  6337  */
       
  6338 
       
  6339 L.extend(L.DomEvent, {
       
  6340 
       
  6341 	_msTouches: [],
       
  6342 	_msDocumentListener: false,
       
  6343 
       
  6344 	// Provides a touch events wrapper for msPointer events.
       
  6345 	// Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
       
  6346 
       
  6347 	addMsTouchListener: function (obj, type, handler, id) {
       
  6348 
       
  6349 		switch (type) {
       
  6350 		case 'touchstart':
       
  6351 			return this.addMsTouchListenerStart(obj, type, handler, id);
       
  6352 		case 'touchend':
       
  6353 			return this.addMsTouchListenerEnd(obj, type, handler, id);
       
  6354 		case 'touchmove':
       
  6355 			return this.addMsTouchListenerMove(obj, type, handler, id);
       
  6356 		default:
       
  6357 			throw 'Unknown touch event type';
       
  6358 		}
       
  6359 	},
       
  6360 
       
  6361 	addMsTouchListenerStart: function (obj, type, handler, id) {
       
  6362 		var pre = '_leaflet_',
       
  6363 		    touches = this._msTouches;
       
  6364 
       
  6365 		var cb = function (e) {
       
  6366 
       
  6367 			var alreadyInArray = false;
       
  6368 			for (var i = 0; i < touches.length; i++) {
       
  6369 				if (touches[i].pointerId === e.pointerId) {
       
  6370 					alreadyInArray = true;
       
  6371 					break;
       
  6372 				}
       
  6373 			}
       
  6374 			if (!alreadyInArray) {
       
  6375 				touches.push(e);
       
  6376 			}
       
  6377 
       
  6378 			e.touches = touches.slice();
       
  6379 			e.changedTouches = [e];
       
  6380 
       
  6381 			handler(e);
       
  6382 		};
       
  6383 
       
  6384 		obj[pre + 'touchstart' + id] = cb;
       
  6385 		obj.addEventListener('MSPointerDown', cb, false);
       
  6386 
       
  6387 		// need to also listen for end events to keep the _msTouches list accurate
       
  6388 		// this needs to be on the body and never go away
       
  6389 		if (!this._msDocumentListener) {
       
  6390 			var internalCb = function (e) {
       
  6391 				for (var i = 0; i < touches.length; i++) {
       
  6392 					if (touches[i].pointerId === e.pointerId) {
       
  6393 						touches.splice(i, 1);
       
  6394 						break;
       
  6395 					}
       
  6396 				}
       
  6397 			};
       
  6398 			//We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
       
  6399 			document.documentElement.addEventListener('MSPointerUp', internalCb, false);
       
  6400 			document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
       
  6401 
       
  6402 			this._msDocumentListener = true;
       
  6403 		}
       
  6404 
       
  6405 		return this;
       
  6406 	},
       
  6407 
       
  6408 	addMsTouchListenerMove: function (obj, type, handler, id) {
       
  6409 		var pre = '_leaflet_',
       
  6410 		    touches = this._msTouches;
       
  6411 
       
  6412 		function cb(e) {
       
  6413 
       
  6414 			// don't fire touch moves when mouse isn't down
       
  6415 			if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
       
  6416 
       
  6417 			for (var i = 0; i < touches.length; i++) {
       
  6418 				if (touches[i].pointerId === e.pointerId) {
       
  6419 					touches[i] = e;
       
  6420 					break;
       
  6421 				}
       
  6422 			}
       
  6423 
       
  6424 			e.touches = touches.slice();
       
  6425 			e.changedTouches = [e];
       
  6426 
       
  6427 			handler(e);
       
  6428 		}
       
  6429 
       
  6430 		obj[pre + 'touchmove' + id] = cb;
       
  6431 		obj.addEventListener('MSPointerMove', cb, false);
       
  6432 
       
  6433 		return this;
       
  6434 	},
       
  6435 
       
  6436 	addMsTouchListenerEnd: function (obj, type, handler, id) {
       
  6437 		var pre = '_leaflet_',
       
  6438 		    touches = this._msTouches;
       
  6439 
       
  6440 		var cb = function (e) {
       
  6441 			for (var i = 0; i < touches.length; i++) {
       
  6442 				if (touches[i].pointerId === e.pointerId) {
       
  6443 					touches.splice(i, 1);
       
  6444 					break;
       
  6445 				}
       
  6446 			}
       
  6447 
       
  6448 			e.touches = touches.slice();
       
  6449 			e.changedTouches = [e];
       
  6450 
       
  6451 			handler(e);
       
  6452 		};
       
  6453 
       
  6454 		obj[pre + 'touchend' + id] = cb;
       
  6455 		obj.addEventListener('MSPointerUp', cb, false);
       
  6456 		obj.addEventListener('MSPointerCancel', cb, false);
       
  6457 
       
  6458 		return this;
       
  6459 	},
       
  6460 
       
  6461 	removeMsTouchListener: function (obj, type, id) {
       
  6462 		var pre = '_leaflet_',
       
  6463 		    cb = obj[pre + type + id];
       
  6464 
       
  6465 		switch (type) {
       
  6466 		case 'touchstart':
       
  6467 			obj.removeEventListener('MSPointerDown', cb, false);
       
  6468 			break;
       
  6469 		case 'touchmove':
       
  6470 			obj.removeEventListener('MSPointerMove', cb, false);
       
  6471 			break;
       
  6472 		case 'touchend':
       
  6473 			obj.removeEventListener('MSPointerUp', cb, false);
       
  6474 			obj.removeEventListener('MSPointerCancel', cb, false);
       
  6475 			break;
       
  6476 		}
       
  6477 
       
  6478 		return this;
       
  6479 	}
       
  6480 });
       
  6481 
       
  6482 
       
  6483 /*
       
  6484  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
       
  6485  */
       
  6486 
       
  6487 L.Map.mergeOptions({
       
  6488 	touchZoom: L.Browser.touch && !L.Browser.android23
       
  6489 });
       
  6490 
       
  6491 L.Map.TouchZoom = L.Handler.extend({
       
  6492 	addHooks: function () {
       
  6493 		L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
       
  6494 	},
       
  6495 
       
  6496 	removeHooks: function () {
       
  6497 		L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
       
  6498 	},
       
  6499 
       
  6500 	_onTouchStart: function (e) {
       
  6501 		var map = this._map;
       
  6502 
       
  6503 		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
       
  6504 
       
  6505 		var p1 = map.mouseEventToLayerPoint(e.touches[0]),
       
  6506 		    p2 = map.mouseEventToLayerPoint(e.touches[1]),
       
  6507 		    viewCenter = map._getCenterLayerPoint();
       
  6508 
       
  6509 		this._startCenter = p1.add(p2)._divideBy(2);
       
  6510 		this._startDist = p1.distanceTo(p2);
       
  6511 
       
  6512 		this._moved = false;
       
  6513 		this._zooming = true;
       
  6514 
       
  6515 		this._centerOffset = viewCenter.subtract(this._startCenter);
       
  6516 
       
  6517 		if (map._panAnim) {
       
  6518 			map._panAnim.stop();
       
  6519 		}
       
  6520 
       
  6521 		L.DomEvent
       
  6522 		    .on(document, 'touchmove', this._onTouchMove, this)
       
  6523 		    .on(document, 'touchend', this._onTouchEnd, this);
       
  6524 
       
  6525 		L.DomEvent.preventDefault(e);
       
  6526 	},
       
  6527 
       
  6528 	_onTouchMove: function (e) {
       
  6529 		if (!e.touches || e.touches.length !== 2) { return; }
       
  6530 
       
  6531 		var map = this._map;
       
  6532 
       
  6533 		var p1 = map.mouseEventToLayerPoint(e.touches[0]),
       
  6534 		    p2 = map.mouseEventToLayerPoint(e.touches[1]);
       
  6535 
       
  6536 		this._scale = p1.distanceTo(p2) / this._startDist;
       
  6537 		this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
       
  6538 
       
  6539 		if (this._scale === 1) { return; }
       
  6540 
       
  6541 		if (!this._moved) {
       
  6542 			L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
       
  6543 
       
  6544 			map
       
  6545 			    .fire('movestart')
       
  6546 			    .fire('zoomstart')
       
  6547 			    ._prepareTileBg();
       
  6548 
       
  6549 			this._moved = true;
       
  6550 		}
       
  6551 
       
  6552 		L.Util.cancelAnimFrame(this._animRequest);
       
  6553 		this._animRequest = L.Util.requestAnimFrame(
       
  6554 		        this._updateOnMove, this, true, this._map._container);
       
  6555 
       
  6556 		L.DomEvent.preventDefault(e);
       
  6557 	},
       
  6558 
       
  6559 	_updateOnMove: function () {
       
  6560 		var map = this._map,
       
  6561 		    origin = this._getScaleOrigin(),
       
  6562 		    center = map.layerPointToLatLng(origin);
       
  6563 
       
  6564 		map.fire('zoomanim', {
       
  6565 			center: center,
       
  6566 			zoom: map.getScaleZoom(this._scale)
       
  6567 		});
       
  6568 
       
  6569 		// Used 2 translates instead of transform-origin because of a very strange bug -
       
  6570 		// it didn't count the origin on the first touch-zoom but worked correctly afterwards
       
  6571 
       
  6572 		map._tileBg.style[L.DomUtil.TRANSFORM] =
       
  6573 		        L.DomUtil.getTranslateString(this._delta) + ' ' +
       
  6574 		        L.DomUtil.getScaleString(this._scale, this._startCenter);
       
  6575 	},
       
  6576 
       
  6577 	_onTouchEnd: function () {
       
  6578 		if (!this._moved || !this._zooming) { return; }
       
  6579 
       
  6580 		var map = this._map;
       
  6581 
       
  6582 		this._zooming = false;
       
  6583 		L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
       
  6584 
       
  6585 		L.DomEvent
       
  6586 		    .off(document, 'touchmove', this._onTouchMove)
       
  6587 		    .off(document, 'touchend', this._onTouchEnd);
       
  6588 
       
  6589 		var origin = this._getScaleOrigin(),
       
  6590 		    center = map.layerPointToLatLng(origin),
       
  6591 
       
  6592 		    oldZoom = map.getZoom(),
       
  6593 		    floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
       
  6594 		    roundZoomDelta = (floatZoomDelta > 0 ?
       
  6595 		            Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
       
  6596 
       
  6597 		    zoom = map._limitZoom(oldZoom + roundZoomDelta);
       
  6598 
       
  6599 		map.fire('zoomanim', {
       
  6600 			center: center,
       
  6601 			zoom: zoom
       
  6602 		});
       
  6603 
       
  6604 		map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
       
  6605 	},
       
  6606 
       
  6607 	_getScaleOrigin: function () {
       
  6608 		var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
       
  6609 		return this._startCenter.add(centerOffset);
       
  6610 	}
       
  6611 });
       
  6612 
       
  6613 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
       
  6614 
       
  6615 
       
  6616 /*
       
  6617  * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
       
  6618   * (zoom to a selected bounding box), enabled by default.
       
  6619  */
       
  6620 
       
  6621 L.Map.mergeOptions({
       
  6622 	boxZoom: true
       
  6623 });
       
  6624 
       
  6625 L.Map.BoxZoom = L.Handler.extend({
       
  6626 	initialize: function (map) {
       
  6627 		this._map = map;
       
  6628 		this._container = map._container;
       
  6629 		this._pane = map._panes.overlayPane;
       
  6630 	},
       
  6631 
       
  6632 	addHooks: function () {
       
  6633 		L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
       
  6634 	},
       
  6635 
       
  6636 	removeHooks: function () {
       
  6637 		L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
       
  6638 	},
       
  6639 
       
  6640 	_onMouseDown: function (e) {
       
  6641 		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
       
  6642 
       
  6643 		L.DomUtil.disableTextSelection();
       
  6644 
       
  6645 		this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
       
  6646 
       
  6647 		this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
       
  6648 		L.DomUtil.setPosition(this._box, this._startLayerPoint);
       
  6649 
       
  6650 		//TODO refactor: move cursor to styles
       
  6651 		this._container.style.cursor = 'crosshair';
       
  6652 
       
  6653 		L.DomEvent
       
  6654 		    .on(document, 'mousemove', this._onMouseMove, this)
       
  6655 		    .on(document, 'mouseup', this._onMouseUp, this)
       
  6656 		    .preventDefault(e);
       
  6657 
       
  6658 		this._map.fire("boxzoomstart");
       
  6659 	},
       
  6660 
       
  6661 	_onMouseMove: function (e) {
       
  6662 		var startPoint = this._startLayerPoint,
       
  6663 		    box = this._box,
       
  6664 
       
  6665 		    layerPoint = this._map.mouseEventToLayerPoint(e),
       
  6666 		    offset = layerPoint.subtract(startPoint),
       
  6667 
       
  6668 		    newPos = new L.Point(
       
  6669 		        Math.min(layerPoint.x, startPoint.x),
       
  6670 		        Math.min(layerPoint.y, startPoint.y));
       
  6671 
       
  6672 		L.DomUtil.setPosition(box, newPos);
       
  6673 
       
  6674 		// TODO refactor: remove hardcoded 4 pixels
       
  6675 		box.style.width  = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
       
  6676 		box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
       
  6677 	},
       
  6678 
       
  6679 	_onMouseUp: function (e) {
       
  6680 		this._pane.removeChild(this._box);
       
  6681 		this._container.style.cursor = '';
       
  6682 
       
  6683 		L.DomUtil.enableTextSelection();
       
  6684 
       
  6685 		L.DomEvent
       
  6686 		    .off(document, 'mousemove', this._onMouseMove)
       
  6687 		    .off(document, 'mouseup', this._onMouseUp);
       
  6688 
       
  6689 		var map = this._map,
       
  6690 		    layerPoint = map.mouseEventToLayerPoint(e);
       
  6691 
       
  6692 		if (this._startLayerPoint.equals(layerPoint)) { return; }
       
  6693 
       
  6694 		var bounds = new L.LatLngBounds(
       
  6695 		        map.layerPointToLatLng(this._startLayerPoint),
       
  6696 		        map.layerPointToLatLng(layerPoint));
       
  6697 
       
  6698 		map.fitBounds(bounds);
       
  6699 
       
  6700 		map.fire("boxzoomend", {
       
  6701 			boxZoomBounds: bounds
       
  6702 		});
       
  6703 	}
       
  6704 });
       
  6705 
       
  6706 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
       
  6707 
       
  6708 
       
  6709 /*
       
  6710  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
       
  6711  */
       
  6712 
       
  6713 L.Map.mergeOptions({
       
  6714 	keyboard: true,
       
  6715 	keyboardPanOffset: 80,
       
  6716 	keyboardZoomOffset: 1
       
  6717 });
       
  6718 
       
  6719 L.Map.Keyboard = L.Handler.extend({
       
  6720 
       
  6721 	keyCodes: {
       
  6722 		left:    [37],
       
  6723 		right:   [39],
       
  6724 		down:    [40],
       
  6725 		up:      [38],
       
  6726 		zoomIn:  [187, 107, 61],
       
  6727 		zoomOut: [189, 109, 173]
       
  6728 	},
       
  6729 
       
  6730 	initialize: function (map) {
       
  6731 		this._map = map;
       
  6732 
       
  6733 		this._setPanOffset(map.options.keyboardPanOffset);
       
  6734 		this._setZoomOffset(map.options.keyboardZoomOffset);
       
  6735 	},
       
  6736 
       
  6737 	addHooks: function () {
       
  6738 		var container = this._map._container;
       
  6739 
       
  6740 		// make the container focusable by tabbing
       
  6741 		if (container.tabIndex === -1) {
       
  6742 			container.tabIndex = "0";
       
  6743 		}
       
  6744 
       
  6745 		L.DomEvent
       
  6746 		    .on(container, 'focus', this._onFocus, this)
       
  6747 		    .on(container, 'blur', this._onBlur, this)
       
  6748 		    .on(container, 'mousedown', this._onMouseDown, this);
       
  6749 
       
  6750 		this._map
       
  6751 		    .on('focus', this._addHooks, this)
       
  6752 		    .on('blur', this._removeHooks, this);
       
  6753 	},
       
  6754 
       
  6755 	removeHooks: function () {
       
  6756 		this._removeHooks();
       
  6757 
       
  6758 		var container = this._map._container;
       
  6759 
       
  6760 		L.DomEvent
       
  6761 		    .off(container, 'focus', this._onFocus, this)
       
  6762 		    .off(container, 'blur', this._onBlur, this)
       
  6763 		    .off(container, 'mousedown', this._onMouseDown, this);
       
  6764 
       
  6765 		this._map
       
  6766 		    .off('focus', this._addHooks, this)
       
  6767 		    .off('blur', this._removeHooks, this);
       
  6768 	},
       
  6769 
       
  6770 	_onMouseDown: function () {
       
  6771 		if (!this._focused) {
       
  6772 			this._map._container.focus();
       
  6773 		}
       
  6774 	},
       
  6775 
       
  6776 	_onFocus: function () {
       
  6777 		this._focused = true;
       
  6778 		this._map.fire('focus');
       
  6779 	},
       
  6780 
       
  6781 	_onBlur: function () {
       
  6782 		this._focused = false;
       
  6783 		this._map.fire('blur');
       
  6784 	},
       
  6785 
       
  6786 	_setPanOffset: function (pan) {
       
  6787 		var keys = this._panKeys = {},
       
  6788 		    codes = this.keyCodes,
       
  6789 		    i, len;
       
  6790 
       
  6791 		for (i = 0, len = codes.left.length; i < len; i++) {
       
  6792 			keys[codes.left[i]] = [-1 * pan, 0];
       
  6793 		}
       
  6794 		for (i = 0, len = codes.right.length; i < len; i++) {
       
  6795 			keys[codes.right[i]] = [pan, 0];
       
  6796 		}
       
  6797 		for (i = 0, len = codes.down.length; i < len; i++) {
       
  6798 			keys[codes.down[i]] = [0, pan];
       
  6799 		}
       
  6800 		for (i = 0, len = codes.up.length; i < len; i++) {
       
  6801 			keys[codes.up[i]] = [0, -1 * pan];
       
  6802 		}
       
  6803 	},
       
  6804 
       
  6805 	_setZoomOffset: function (zoom) {
       
  6806 		var keys = this._zoomKeys = {},
       
  6807 		    codes = this.keyCodes,
       
  6808 		    i, len;
       
  6809 
       
  6810 		for (i = 0, len = codes.zoomIn.length; i < len; i++) {
       
  6811 			keys[codes.zoomIn[i]] = zoom;
       
  6812 		}
       
  6813 		for (i = 0, len = codes.zoomOut.length; i < len; i++) {
       
  6814 			keys[codes.zoomOut[i]] = -zoom;
       
  6815 		}
       
  6816 	},
       
  6817 
       
  6818 	_addHooks: function () {
       
  6819 		L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
       
  6820 	},
       
  6821 
       
  6822 	_removeHooks: function () {
       
  6823 		L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
       
  6824 	},
       
  6825 
       
  6826 	_onKeyDown: function (e) {
       
  6827 		var key = e.keyCode,
       
  6828 		    map = this._map;
       
  6829 
       
  6830 		if (this._panKeys.hasOwnProperty(key)) {
       
  6831 			map.panBy(this._panKeys[key]);
       
  6832 
       
  6833 			if (map.options.maxBounds) {
       
  6834 				map.panInsideBounds(map.options.maxBounds);
       
  6835 			}
       
  6836 
       
  6837 		} else if (this._zoomKeys.hasOwnProperty(key)) {
       
  6838 			map.setZoom(map.getZoom() + this._zoomKeys[key]);
       
  6839 
       
  6840 		} else {
       
  6841 			return;
       
  6842 		}
       
  6843 
       
  6844 		L.DomEvent.stop(e);
       
  6845 	}
       
  6846 });
       
  6847 
       
  6848 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
       
  6849 
       
  6850 
       
  6851 /*
       
  6852  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
       
  6853  */
       
  6854 
       
  6855 L.Handler.MarkerDrag = L.Handler.extend({
       
  6856 	initialize: function (marker) {
       
  6857 		this._marker = marker;
       
  6858 	},
       
  6859 
       
  6860 	addHooks: function () {
       
  6861 		var icon = this._marker._icon;
       
  6862 		if (!this._draggable) {
       
  6863 			this._draggable = new L.Draggable(icon, icon)
       
  6864 			    .on('dragstart', this._onDragStart, this)
       
  6865 			    .on('drag', this._onDrag, this)
       
  6866 			    .on('dragend', this._onDragEnd, this);
       
  6867 		}
       
  6868 		this._draggable.enable();
       
  6869 	},
       
  6870 
       
  6871 	removeHooks: function () {
       
  6872 		this._draggable.disable();
       
  6873 	},
       
  6874 
       
  6875 	moved: function () {
       
  6876 		return this._draggable && this._draggable._moved;
       
  6877 	},
       
  6878 
       
  6879 	_onDragStart: function () {
       
  6880 		this._marker
       
  6881 		    .closePopup()
       
  6882 		    .fire('movestart')
       
  6883 		    .fire('dragstart');
       
  6884 	},
       
  6885 
       
  6886 	_onDrag: function () {
       
  6887 		var marker = this._marker,
       
  6888 		    shadow = marker._shadow,
       
  6889 		    iconPos = L.DomUtil.getPosition(marker._icon),
       
  6890 		    latlng = marker._map.layerPointToLatLng(iconPos);
       
  6891 
       
  6892 		// update shadow position
       
  6893 		if (shadow) {
       
  6894 			L.DomUtil.setPosition(shadow, iconPos);
       
  6895 		}
       
  6896 
       
  6897 		marker._latlng = latlng;
       
  6898 
       
  6899 		marker
       
  6900 		    .fire('move', {latlng: latlng})
       
  6901 		    .fire('drag');
       
  6902 	},
       
  6903 
       
  6904 	_onDragEnd: function () {
       
  6905 		this._marker
       
  6906 		    .fire('moveend')
       
  6907 		    .fire('dragend');
       
  6908 	}
       
  6909 });
       
  6910 
       
  6911 
       
  6912 /*
       
  6913  * L.Handler.PolyEdit is an editing handler for polylines and polygons.
       
  6914  */
       
  6915 
       
  6916 L.Handler.PolyEdit = L.Handler.extend({
       
  6917 	options: {
       
  6918 		icon: new L.DivIcon({
       
  6919 			iconSize: new L.Point(8, 8),
       
  6920 			className: 'leaflet-div-icon leaflet-editing-icon'
       
  6921 		})
       
  6922 	},
       
  6923 
       
  6924 	initialize: function (poly, options) {
       
  6925 		this._poly = poly;
       
  6926 		L.setOptions(this, options);
       
  6927 	},
       
  6928 
       
  6929 	addHooks: function () {
       
  6930 		if (this._poly._map) {
       
  6931 			if (!this._markerGroup) {
       
  6932 				this._initMarkers();
       
  6933 			}
       
  6934 			this._poly._map.addLayer(this._markerGroup);
       
  6935 		}
       
  6936 	},
       
  6937 
       
  6938 	removeHooks: function () {
       
  6939 		if (this._poly._map) {
       
  6940 			this._poly._map.removeLayer(this._markerGroup);
       
  6941 			delete this._markerGroup;
       
  6942 			delete this._markers;
       
  6943 		}
       
  6944 	},
       
  6945 
       
  6946 	updateMarkers: function () {
       
  6947 		this._markerGroup.clearLayers();
       
  6948 		this._initMarkers();
       
  6949 	},
       
  6950 
       
  6951 	_initMarkers: function () {
       
  6952 		if (!this._markerGroup) {
       
  6953 			this._markerGroup = new L.LayerGroup();
       
  6954 		}
       
  6955 		this._markers = [];
       
  6956 
       
  6957 		var latlngs = this._poly._latlngs,
       
  6958 		    i, j, len, marker;
       
  6959 
       
  6960 		// TODO refactor holes implementation in Polygon to support it here
       
  6961 
       
  6962 		for (i = 0, len = latlngs.length; i < len; i++) {
       
  6963 
       
  6964 			marker = this._createMarker(latlngs[i], i);
       
  6965 			marker.on('click', this._onMarkerClick, this);
       
  6966 			this._markers.push(marker);
       
  6967 		}
       
  6968 
       
  6969 		var markerLeft, markerRight;
       
  6970 
       
  6971 		for (i = 0, j = len - 1; i < len; j = i++) {
       
  6972 			if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
       
  6973 				continue;
       
  6974 			}
       
  6975 
       
  6976 			markerLeft = this._markers[j];
       
  6977 			markerRight = this._markers[i];
       
  6978 
       
  6979 			this._createMiddleMarker(markerLeft, markerRight);
       
  6980 			this._updatePrevNext(markerLeft, markerRight);
       
  6981 		}
       
  6982 	},
       
  6983 
       
  6984 	_createMarker: function (latlng, index) {
       
  6985 		var marker = new L.Marker(latlng, {
       
  6986 			draggable: true,
       
  6987 			icon: this.options.icon
       
  6988 		});
       
  6989 
       
  6990 		marker._origLatLng = latlng;
       
  6991 		marker._index = index;
       
  6992 
       
  6993 		marker.on('drag', this._onMarkerDrag, this);
       
  6994 		marker.on('dragend', this._fireEdit, this);
       
  6995 
       
  6996 		this._markerGroup.addLayer(marker);
       
  6997 
       
  6998 		return marker;
       
  6999 	},
       
  7000 
       
  7001 	_fireEdit: function () {
       
  7002 		this._poly.fire('edit');
       
  7003 	},
       
  7004 
       
  7005 	_onMarkerDrag: function (e) {
       
  7006 		var marker = e.target;
       
  7007 
       
  7008 		L.extend(marker._origLatLng, marker._latlng);
       
  7009 
       
  7010 		if (marker._middleLeft) {
       
  7011 			marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
       
  7012 		}
       
  7013 		if (marker._middleRight) {
       
  7014 			marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
       
  7015 		}
       
  7016 
       
  7017 		this._poly.redraw();
       
  7018 	},
       
  7019 
       
  7020 	_onMarkerClick: function (e) {
       
  7021 		// we want to remove the marker on click, but if latlng count < 3, polyline would be invalid
       
  7022 		if (this._poly._latlngs.length < 3) { return; }
       
  7023 
       
  7024 		var marker = e.target,
       
  7025 		    i = marker._index;
       
  7026 
       
  7027 		// remove the marker
       
  7028 		this._markerGroup.removeLayer(marker);
       
  7029 		this._markers.splice(i, 1);
       
  7030 		this._poly.spliceLatLngs(i, 1);
       
  7031 		this._updateIndexes(i, -1);
       
  7032 
       
  7033 		// update prev/next links of adjacent markers
       
  7034 		this._updatePrevNext(marker._prev, marker._next);
       
  7035 
       
  7036 		// remove ghost markers near the removed marker
       
  7037 		if (marker._middleLeft) {
       
  7038 			this._markerGroup.removeLayer(marker._middleLeft);
       
  7039 		}
       
  7040 		if (marker._middleRight) {
       
  7041 			this._markerGroup.removeLayer(marker._middleRight);
       
  7042 		}
       
  7043 
       
  7044 		// create a ghost marker in place of the removed one
       
  7045 		if (marker._prev && marker._next) {
       
  7046 			this._createMiddleMarker(marker._prev, marker._next);
       
  7047 
       
  7048 		} else if (!marker._prev) {
       
  7049 			marker._next._middleLeft = null;
       
  7050 
       
  7051 		} else if (!marker._next) {
       
  7052 			marker._prev._middleRight = null;
       
  7053 		}
       
  7054 
       
  7055 		this._poly.fire('edit');
       
  7056 	},
       
  7057 
       
  7058 	_updateIndexes: function (index, delta) {
       
  7059 		this._markerGroup.eachLayer(function (marker) {
       
  7060 			if (marker._index > index) {
       
  7061 				marker._index += delta;
       
  7062 			}
       
  7063 		});
       
  7064 	},
       
  7065 
       
  7066 	_createMiddleMarker: function (marker1, marker2) {
       
  7067 		var latlng = this._getMiddleLatLng(marker1, marker2),
       
  7068 		    marker = this._createMarker(latlng),
       
  7069 		    onClick,
       
  7070 		    onDragStart,
       
  7071 		    onDragEnd;
       
  7072 
       
  7073 		marker.setOpacity(0.6);
       
  7074 
       
  7075 		marker1._middleRight = marker2._middleLeft = marker;
       
  7076 
       
  7077 		onDragStart = function () {
       
  7078 			var i = marker2._index;
       
  7079 
       
  7080 			marker._index = i;
       
  7081 
       
  7082 			marker
       
  7083 			    .off('click', onClick)
       
  7084 			    .on('click', this._onMarkerClick, this);
       
  7085 
       
  7086 			latlng.lat = marker.getLatLng().lat;
       
  7087 			latlng.lng = marker.getLatLng().lng;
       
  7088 			this._poly.spliceLatLngs(i, 0, latlng);
       
  7089 			this._markers.splice(i, 0, marker);
       
  7090 
       
  7091 			marker.setOpacity(1);
       
  7092 
       
  7093 			this._updateIndexes(i, 1);
       
  7094 			marker2._index++;
       
  7095 			this._updatePrevNext(marker1, marker);
       
  7096 			this._updatePrevNext(marker, marker2);
       
  7097 		};
       
  7098 
       
  7099 		onDragEnd = function () {
       
  7100 			marker.off('dragstart', onDragStart, this);
       
  7101 			marker.off('dragend', onDragEnd, this);
       
  7102 
       
  7103 			this._createMiddleMarker(marker1, marker);
       
  7104 			this._createMiddleMarker(marker, marker2);
       
  7105 		};
       
  7106 
       
  7107 		onClick = function () {
       
  7108 			onDragStart.call(this);
       
  7109 			onDragEnd.call(this);
       
  7110 			this._poly.fire('edit');
       
  7111 		};
       
  7112 
       
  7113 		marker
       
  7114 		    .on('click', onClick, this)
       
  7115 		    .on('dragstart', onDragStart, this)
       
  7116 		    .on('dragend', onDragEnd, this);
       
  7117 
       
  7118 		this._markerGroup.addLayer(marker);
       
  7119 	},
       
  7120 
       
  7121 	_updatePrevNext: function (marker1, marker2) {
       
  7122 		if (marker1) {
       
  7123 			marker1._next = marker2;
       
  7124 		}
       
  7125 		if (marker2) {
       
  7126 			marker2._prev = marker1;
       
  7127 		}
       
  7128 	},
       
  7129 
       
  7130 	_getMiddleLatLng: function (marker1, marker2) {
       
  7131 		var map = this._poly._map,
       
  7132 		    p1 = map.latLngToLayerPoint(marker1.getLatLng()),
       
  7133 		    p2 = map.latLngToLayerPoint(marker2.getLatLng());
       
  7134 
       
  7135 		return map.layerPointToLatLng(p1._add(p2)._divideBy(2));
       
  7136 	}
       
  7137 });
       
  7138 
       
  7139 L.Polyline.addInitHook(function () {
       
  7140 
       
  7141 	if (L.Handler.PolyEdit) {
       
  7142 		this.editing = new L.Handler.PolyEdit(this);
       
  7143 
       
  7144 		if (this.options.editable) {
       
  7145 			this.editing.enable();
       
  7146 		}
       
  7147 	}
       
  7148 
       
  7149 	this.on('add', function () {
       
  7150 		if (this.editing && this.editing.enabled()) {
       
  7151 			this.editing.addHooks();
       
  7152 		}
       
  7153 	});
       
  7154 
       
  7155 	this.on('remove', function () {
       
  7156 		if (this.editing && this.editing.enabled()) {
       
  7157 			this.editing.removeHooks();
       
  7158 		}
       
  7159 	});
       
  7160 });
       
  7161 
       
  7162 
       
  7163 /*
       
  7164  * L.Control is a base class for implementing map controls. Handles positioning.
       
  7165  * All other controls extend from this class.
       
  7166  */
       
  7167 
       
  7168 L.Control = L.Class.extend({
       
  7169 	options: {
       
  7170 		position: 'topright'
       
  7171 	},
       
  7172 
       
  7173 	initialize: function (options) {
       
  7174 		L.setOptions(this, options);
       
  7175 	},
       
  7176 
       
  7177 	getPosition: function () {
       
  7178 		return this.options.position;
       
  7179 	},
       
  7180 
       
  7181 	setPosition: function (position) {
       
  7182 		var map = this._map;
       
  7183 
       
  7184 		if (map) {
       
  7185 			map.removeControl(this);
       
  7186 		}
       
  7187 
       
  7188 		this.options.position = position;
       
  7189 
       
  7190 		if (map) {
       
  7191 			map.addControl(this);
       
  7192 		}
       
  7193 
       
  7194 		return this;
       
  7195 	},
       
  7196 
       
  7197 	addTo: function (map) {
       
  7198 		this._map = map;
       
  7199 
       
  7200 		var container = this._container = this.onAdd(map),
       
  7201 		    pos = this.getPosition(),
       
  7202 		    corner = map._controlCorners[pos];
       
  7203 
       
  7204 		L.DomUtil.addClass(container, 'leaflet-control');
       
  7205 
       
  7206 		if (pos.indexOf('bottom') !== -1) {
       
  7207 			corner.insertBefore(container, corner.firstChild);
       
  7208 		} else {
       
  7209 			corner.appendChild(container);
       
  7210 		}
       
  7211 
       
  7212 		return this;
       
  7213 	},
       
  7214 
       
  7215 	removeFrom: function (map) {
       
  7216 		var pos = this.getPosition(),
       
  7217 		    corner = map._controlCorners[pos];
       
  7218 
       
  7219 		corner.removeChild(this._container);
       
  7220 		this._map = null;
       
  7221 
       
  7222 		if (this.onRemove) {
       
  7223 			this.onRemove(map);
       
  7224 		}
       
  7225 
       
  7226 		return this;
       
  7227 	}
       
  7228 });
       
  7229 
       
  7230 L.control = function (options) {
       
  7231 	return new L.Control(options);
       
  7232 };
       
  7233 
       
  7234 
       
  7235 /*
       
  7236  * Adds control-related methods to L.Map.
       
  7237  */
       
  7238 
       
  7239 L.Map.include({
       
  7240 	addControl: function (control) {
       
  7241 		control.addTo(this);
       
  7242 		return this;
       
  7243 	},
       
  7244 
       
  7245 	removeControl: function (control) {
       
  7246 		control.removeFrom(this);
       
  7247 		return this;
       
  7248 	},
       
  7249 
       
  7250 	_initControlPos: function () {
       
  7251 		var corners = this._controlCorners = {},
       
  7252 		    l = 'leaflet-',
       
  7253 		    container = this._controlContainer =
       
  7254 		            L.DomUtil.create('div', l + 'control-container', this._container);
       
  7255 
       
  7256 		function createCorner(vSide, hSide) {
       
  7257 			var className = l + vSide + ' ' + l + hSide;
       
  7258 
       
  7259 			corners[vSide + hSide] = L.DomUtil.create('div', className, container);
       
  7260 		}
       
  7261 
       
  7262 		createCorner('top', 'left');
       
  7263 		createCorner('top', 'right');
       
  7264 		createCorner('bottom', 'left');
       
  7265 		createCorner('bottom', 'right');
       
  7266 	}
       
  7267 });
       
  7268 
       
  7269 
       
  7270 /*
       
  7271  * L.Control.Zoom is used for the default zoom buttons on the map.
       
  7272  */
       
  7273 
       
  7274 L.Control.Zoom = L.Control.extend({
       
  7275 	options: {
       
  7276 		position: 'topleft'
       
  7277 	},
       
  7278 
       
  7279 	onAdd: function (map) {
       
  7280 		var zoomName = 'leaflet-control-zoom',
       
  7281 		    barName = 'leaflet-bar',
       
  7282 		    partName = barName + '-part',
       
  7283 		    container = L.DomUtil.create('div', zoomName + ' ' + barName);
       
  7284 
       
  7285 		this._map = map;
       
  7286 
       
  7287 		this._zoomInButton = this._createButton('+', 'Zoom in',
       
  7288 		        zoomName + '-in ' +
       
  7289 		        partName + ' ' +
       
  7290 		        partName + '-top',
       
  7291 		        container, this._zoomIn,  this);
       
  7292 
       
  7293 		this._zoomOutButton = this._createButton('-', 'Zoom out',
       
  7294 		        zoomName + '-out ' +
       
  7295 		        partName + ' ' +
       
  7296 		        partName + '-bottom',
       
  7297 		        container, this._zoomOut, this);
       
  7298 
       
  7299 		map.on('zoomend', this._updateDisabled, this);
       
  7300 
       
  7301 		return container;
       
  7302 	},
       
  7303 
       
  7304 	onRemove: function (map) {
       
  7305 		map.off('zoomend', this._updateDisabled, this);
       
  7306 	},
       
  7307 
       
  7308 	_zoomIn: function (e) {
       
  7309 		this._map.zoomIn(e.shiftKey ? 3 : 1);
       
  7310 	},
       
  7311 
       
  7312 	_zoomOut: function (e) {
       
  7313 		this._map.zoomOut(e.shiftKey ? 3 : 1);
       
  7314 	},
       
  7315 
       
  7316 	_createButton: function (html, title, className, container, fn, context) {
       
  7317 		var link = L.DomUtil.create('a', className, container);
       
  7318 		link.innerHTML = html;
       
  7319 		link.href = '#';
       
  7320 		link.title = title;
       
  7321 
       
  7322 		var stop = L.DomEvent.stopPropagation;
       
  7323 
       
  7324 		L.DomEvent
       
  7325 		    .on(link, 'click', stop)
       
  7326 		    .on(link, 'mousedown', stop)
       
  7327 		    .on(link, 'dblclick', stop)
       
  7328 		    .on(link, 'click', L.DomEvent.preventDefault)
       
  7329 		    .on(link, 'click', fn, context);
       
  7330 
       
  7331 		return link;
       
  7332 	},
       
  7333 
       
  7334 	_updateDisabled: function () {
       
  7335 		var map = this._map,
       
  7336 			className = 'leaflet-control-zoom-disabled';
       
  7337 
       
  7338 		L.DomUtil.removeClass(this._zoomInButton, className);
       
  7339 		L.DomUtil.removeClass(this._zoomOutButton, className);
       
  7340 
       
  7341 		if (map._zoom === map.getMinZoom()) {
       
  7342 			L.DomUtil.addClass(this._zoomOutButton, className);
       
  7343 		}
       
  7344 		if (map._zoom === map.getMaxZoom()) {
       
  7345 			L.DomUtil.addClass(this._zoomInButton, className);
       
  7346 		}
       
  7347 	}
       
  7348 });
       
  7349 
       
  7350 L.Map.mergeOptions({
       
  7351 	zoomControl: true
       
  7352 });
       
  7353 
       
  7354 L.Map.addInitHook(function () {
       
  7355 	if (this.options.zoomControl) {
       
  7356 		this.zoomControl = new L.Control.Zoom();
       
  7357 		this.addControl(this.zoomControl);
       
  7358 	}
       
  7359 });
       
  7360 
       
  7361 L.control.zoom = function (options) {
       
  7362 	return new L.Control.Zoom(options);
       
  7363 };
       
  7364 
       
  7365 
       
  7366 
       
  7367 /*
       
  7368  * L.Control.Attribution is used for displaying attribution on the map (added by default).
       
  7369  */
       
  7370 
       
  7371 L.Control.Attribution = L.Control.extend({
       
  7372 	options: {
       
  7373 		position: 'bottomright',
       
  7374 		prefix: 'Powered by <a href="http://leafletjs.com">Leaflet</a>'
       
  7375 	},
       
  7376 
       
  7377 	initialize: function (options) {
       
  7378 		L.setOptions(this, options);
       
  7379 
       
  7380 		this._attributions = {};
       
  7381 	},
       
  7382 
       
  7383 	onAdd: function (map) {
       
  7384 		this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
       
  7385 		L.DomEvent.disableClickPropagation(this._container);
       
  7386 
       
  7387 		map
       
  7388 		    .on('layeradd', this._onLayerAdd, this)
       
  7389 		    .on('layerremove', this._onLayerRemove, this);
       
  7390 
       
  7391 		this._update();
       
  7392 
       
  7393 		return this._container;
       
  7394 	},
       
  7395 
       
  7396 	onRemove: function (map) {
       
  7397 		map
       
  7398 		    .off('layeradd', this._onLayerAdd)
       
  7399 		    .off('layerremove', this._onLayerRemove);
       
  7400 
       
  7401 	},
       
  7402 
       
  7403 	setPrefix: function (prefix) {
       
  7404 		this.options.prefix = prefix;
       
  7405 		this._update();
       
  7406 		return this;
       
  7407 	},
       
  7408 
       
  7409 	addAttribution: function (text) {
       
  7410 		if (!text) { return; }
       
  7411 
       
  7412 		if (!this._attributions[text]) {
       
  7413 			this._attributions[text] = 0;
       
  7414 		}
       
  7415 		this._attributions[text]++;
       
  7416 
       
  7417 		this._update();
       
  7418 
       
  7419 		return this;
       
  7420 	},
       
  7421 
       
  7422 	removeAttribution: function (text) {
       
  7423 		if (!text) { return; }
       
  7424 
       
  7425 		this._attributions[text]--;
       
  7426 		this._update();
       
  7427 
       
  7428 		return this;
       
  7429 	},
       
  7430 
       
  7431 	_update: function () {
       
  7432 		if (!this._map) { return; }
       
  7433 
       
  7434 		var attribs = [];
       
  7435 
       
  7436 		for (var i in this._attributions) {
       
  7437 			if (this._attributions.hasOwnProperty(i) && this._attributions[i]) {
       
  7438 				attribs.push(i);
       
  7439 			}
       
  7440 		}
       
  7441 
       
  7442 		var prefixAndAttribs = [];
       
  7443 
       
  7444 		if (this.options.prefix) {
       
  7445 			prefixAndAttribs.push(this.options.prefix);
       
  7446 		}
       
  7447 		if (attribs.length) {
       
  7448 			prefixAndAttribs.push(attribs.join(', '));
       
  7449 		}
       
  7450 
       
  7451 		this._container.innerHTML = prefixAndAttribs.join(' &#8212; ');
       
  7452 	},
       
  7453 
       
  7454 	_onLayerAdd: function (e) {
       
  7455 		if (e.layer.getAttribution) {
       
  7456 			this.addAttribution(e.layer.getAttribution());
       
  7457 		}
       
  7458 	},
       
  7459 
       
  7460 	_onLayerRemove: function (e) {
       
  7461 		if (e.layer.getAttribution) {
       
  7462 			this.removeAttribution(e.layer.getAttribution());
       
  7463 		}
       
  7464 	}
       
  7465 });
       
  7466 
       
  7467 L.Map.mergeOptions({
       
  7468 	attributionControl: true
       
  7469 });
       
  7470 
       
  7471 L.Map.addInitHook(function () {
       
  7472 	if (this.options.attributionControl) {
       
  7473 		this.attributionControl = (new L.Control.Attribution()).addTo(this);
       
  7474 	}
       
  7475 });
       
  7476 
       
  7477 L.control.attribution = function (options) {
       
  7478 	return new L.Control.Attribution(options);
       
  7479 };
       
  7480 
       
  7481 
       
  7482 /*
       
  7483  * L.Control.Scale is used for displaying metric/imperial scale on the map.
       
  7484  */
       
  7485 
       
  7486 L.Control.Scale = L.Control.extend({
       
  7487 	options: {
       
  7488 		position: 'bottomleft',
       
  7489 		maxWidth: 100,
       
  7490 		metric: true,
       
  7491 		imperial: true,
       
  7492 		updateWhenIdle: false
       
  7493 	},
       
  7494 
       
  7495 	onAdd: function (map) {
       
  7496 		this._map = map;
       
  7497 
       
  7498 		var className = 'leaflet-control-scale',
       
  7499 		    container = L.DomUtil.create('div', className),
       
  7500 		    options = this.options;
       
  7501 
       
  7502 		this._addScales(options, className, container);
       
  7503 
       
  7504 		map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
       
  7505 		map.whenReady(this._update, this);
       
  7506 
       
  7507 		return container;
       
  7508 	},
       
  7509 
       
  7510 	onRemove: function (map) {
       
  7511 		map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
       
  7512 	},
       
  7513 
       
  7514 	_addScales: function (options, className, container) {
       
  7515 		if (options.metric) {
       
  7516 			this._mScale = L.DomUtil.create('div', className + '-line', container);
       
  7517 		}
       
  7518 		if (options.imperial) {
       
  7519 			this._iScale = L.DomUtil.create('div', className + '-line', container);
       
  7520 		}
       
  7521 	},
       
  7522 
       
  7523 	_update: function () {
       
  7524 		var bounds = this._map.getBounds(),
       
  7525 		    centerLat = bounds.getCenter().lat,
       
  7526 		    halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
       
  7527 		    dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
       
  7528 
       
  7529 		    size = this._map.getSize(),
       
  7530 		    options = this.options,
       
  7531 		    maxMeters = 0;
       
  7532 
       
  7533 		if (size.x > 0) {
       
  7534 			maxMeters = dist * (options.maxWidth / size.x);
       
  7535 		}
       
  7536 
       
  7537 		this._updateScales(options, maxMeters);
       
  7538 	},
       
  7539 
       
  7540 	_updateScales: function (options, maxMeters) {
       
  7541 		if (options.metric && maxMeters) {
       
  7542 			this._updateMetric(maxMeters);
       
  7543 		}
       
  7544 
       
  7545 		if (options.imperial && maxMeters) {
       
  7546 			this._updateImperial(maxMeters);
       
  7547 		}
       
  7548 	},
       
  7549 
       
  7550 	_updateMetric: function (maxMeters) {
       
  7551 		var meters = this._getRoundNum(maxMeters);
       
  7552 
       
  7553 		this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
       
  7554 		this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
       
  7555 	},
       
  7556 
       
  7557 	_updateImperial: function (maxMeters) {
       
  7558 		var maxFeet = maxMeters * 3.2808399,
       
  7559 		    scale = this._iScale,
       
  7560 		    maxMiles, miles, feet;
       
  7561 
       
  7562 		if (maxFeet > 5280) {
       
  7563 			maxMiles = maxFeet / 5280;
       
  7564 			miles = this._getRoundNum(maxMiles);
       
  7565 
       
  7566 			scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
       
  7567 			scale.innerHTML = miles + ' mi';
       
  7568 
       
  7569 		} else {
       
  7570 			feet = this._getRoundNum(maxFeet);
       
  7571 
       
  7572 			scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
       
  7573 			scale.innerHTML = feet + ' ft';
       
  7574 		}
       
  7575 	},
       
  7576 
       
  7577 	_getScaleWidth: function (ratio) {
       
  7578 		return Math.round(this.options.maxWidth * ratio) - 10;
       
  7579 	},
       
  7580 
       
  7581 	_getRoundNum: function (num) {
       
  7582 		var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
       
  7583 		    d = num / pow10;
       
  7584 
       
  7585 		d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
       
  7586 
       
  7587 		return pow10 * d;
       
  7588 	}
       
  7589 });
       
  7590 
       
  7591 L.control.scale = function (options) {
       
  7592 	return new L.Control.Scale(options);
       
  7593 };
       
  7594 
       
  7595 
       
  7596 /*
       
  7597  * L.Control.Layers is a control to allow users to switch between different layers on the map.
       
  7598  */
       
  7599 
       
  7600 L.Control.Layers = L.Control.extend({
       
  7601 	options: {
       
  7602 		collapsed: true,
       
  7603 		position: 'topright',
       
  7604 		autoZIndex: true
       
  7605 	},
       
  7606 
       
  7607 	initialize: function (baseLayers, overlays, options) {
       
  7608 		L.setOptions(this, options);
       
  7609 
       
  7610 		this._layers = {};
       
  7611 		this._lastZIndex = 0;
       
  7612 		this._handlingClick = false;
       
  7613 
       
  7614 		for (var i in baseLayers) {
       
  7615 			if (baseLayers.hasOwnProperty(i)) {
       
  7616 				this._addLayer(baseLayers[i], i);
       
  7617 			}
       
  7618 		}
       
  7619 
       
  7620 		for (i in overlays) {
       
  7621 			if (overlays.hasOwnProperty(i)) {
       
  7622 				this._addLayer(overlays[i], i, true);
       
  7623 			}
       
  7624 		}
       
  7625 	},
       
  7626 
       
  7627 	onAdd: function (map) {
       
  7628 		this._initLayout();
       
  7629 		this._update();
       
  7630 
       
  7631 		map
       
  7632 		    .on('layeradd', this._onLayerChange, this)
       
  7633 		    .on('layerremove', this._onLayerChange, this);
       
  7634 
       
  7635 		return this._container;
       
  7636 	},
       
  7637 
       
  7638 	onRemove: function (map) {
       
  7639 		map
       
  7640 		    .off('layeradd', this._onLayerChange)
       
  7641 		    .off('layerremove', this._onLayerChange);
       
  7642 	},
       
  7643 
       
  7644 	addBaseLayer: function (layer, name) {
       
  7645 		this._addLayer(layer, name);
       
  7646 		this._update();
       
  7647 		return this;
       
  7648 	},
       
  7649 
       
  7650 	addOverlay: function (layer, name) {
       
  7651 		this._addLayer(layer, name, true);
       
  7652 		this._update();
       
  7653 		return this;
       
  7654 	},
       
  7655 
       
  7656 	removeLayer: function (layer) {
       
  7657 		var id = L.stamp(layer);
       
  7658 		delete this._layers[id];
       
  7659 		this._update();
       
  7660 		return this;
       
  7661 	},
       
  7662 
       
  7663 	_initLayout: function () {
       
  7664 		var className = 'leaflet-control-layers',
       
  7665 		    container = this._container = L.DomUtil.create('div', className);
       
  7666 
       
  7667 		if (!L.Browser.touch) {
       
  7668 			L.DomEvent.disableClickPropagation(container);
       
  7669 			L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
       
  7670 		} else {
       
  7671 			L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
       
  7672 		}
       
  7673 
       
  7674 		var form = this._form = L.DomUtil.create('form', className + '-list');
       
  7675 
       
  7676 		if (this.options.collapsed) {
       
  7677 			L.DomEvent
       
  7678 			    .on(container, 'mouseover', this._expand, this)
       
  7679 			    .on(container, 'mouseout', this._collapse, this);
       
  7680 
       
  7681 			var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
       
  7682 			link.href = '#';
       
  7683 			link.title = 'Layers';
       
  7684 
       
  7685 			if (L.Browser.touch) {
       
  7686 				L.DomEvent
       
  7687 				    .on(link, 'click', L.DomEvent.stopPropagation)
       
  7688 				    .on(link, 'click', L.DomEvent.preventDefault)
       
  7689 				    .on(link, 'click', this._expand, this);
       
  7690 			}
       
  7691 			else {
       
  7692 				L.DomEvent.on(link, 'focus', this._expand, this);
       
  7693 			}
       
  7694 
       
  7695 			this._map.on('movestart', this._collapse, this);
       
  7696 			// TODO keyboard accessibility
       
  7697 		} else {
       
  7698 			this._expand();
       
  7699 		}
       
  7700 
       
  7701 		this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
       
  7702 		this._separator = L.DomUtil.create('div', className + '-separator', form);
       
  7703 		this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
       
  7704 
       
  7705 		container.appendChild(form);
       
  7706 	},
       
  7707 
       
  7708 	_addLayer: function (layer, name, overlay) {
       
  7709 		var id = L.stamp(layer);
       
  7710 
       
  7711 		this._layers[id] = {
       
  7712 			layer: layer,
       
  7713 			name: name,
       
  7714 			overlay: overlay
       
  7715 		};
       
  7716 
       
  7717 		if (this.options.autoZIndex && layer.setZIndex) {
       
  7718 			this._lastZIndex++;
       
  7719 			layer.setZIndex(this._lastZIndex);
       
  7720 		}
       
  7721 	},
       
  7722 
       
  7723 	_update: function () {
       
  7724 		if (!this._container) {
       
  7725 			return;
       
  7726 		}
       
  7727 
       
  7728 		this._baseLayersList.innerHTML = '';
       
  7729 		this._overlaysList.innerHTML = '';
       
  7730 
       
  7731 		var baseLayersPresent = false,
       
  7732 		    overlaysPresent = false;
       
  7733 
       
  7734 		for (var i in this._layers) {
       
  7735 			if (this._layers.hasOwnProperty(i)) {
       
  7736 				var obj = this._layers[i];
       
  7737 				this._addItem(obj);
       
  7738 				overlaysPresent = overlaysPresent || obj.overlay;
       
  7739 				baseLayersPresent = baseLayersPresent || !obj.overlay;
       
  7740 			}
       
  7741 		}
       
  7742 
       
  7743 		this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
       
  7744 	},
       
  7745 
       
  7746 	_onLayerChange: function (e) {
       
  7747 		var id = L.stamp(e.layer);
       
  7748 
       
  7749 		if (this._layers[id] && !this._handlingClick) {
       
  7750 			this._update();
       
  7751 		}
       
  7752 	},
       
  7753 
       
  7754 	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
       
  7755 	_createRadioElement: function (name, checked) {
       
  7756 
       
  7757 		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
       
  7758 		if (checked) {
       
  7759 			radioHtml += ' checked="checked"';
       
  7760 		}
       
  7761 		radioHtml += '/>';
       
  7762 
       
  7763 		var radioFragment = document.createElement('div');
       
  7764 		radioFragment.innerHTML = radioHtml;
       
  7765 
       
  7766 		return radioFragment.firstChild;
       
  7767 	},
       
  7768 
       
  7769 	_addItem: function (obj) {
       
  7770 		var label = document.createElement('label'),
       
  7771 		    input,
       
  7772 		    checked = this._map.hasLayer(obj.layer);
       
  7773 
       
  7774 		if (obj.overlay) {
       
  7775 			input = document.createElement('input');
       
  7776 			input.type = 'checkbox';
       
  7777 			input.className = 'leaflet-control-layers-selector';
       
  7778 			input.defaultChecked = checked;
       
  7779 		} else {
       
  7780 			input = this._createRadioElement('leaflet-base-layers', checked);
       
  7781 		}
       
  7782 
       
  7783 		input.layerId = L.stamp(obj.layer);
       
  7784 
       
  7785 		L.DomEvent.on(input, 'click', this._onInputClick, this);
       
  7786 
       
  7787 		var name = document.createElement('span');
       
  7788 		name.innerHTML = ' ' + obj.name;
       
  7789 
       
  7790 		label.appendChild(input);
       
  7791 		label.appendChild(name);
       
  7792 
       
  7793 		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
       
  7794 		container.appendChild(label);
       
  7795 
       
  7796 		return label;
       
  7797 	},
       
  7798 
       
  7799 	_onInputClick: function () {
       
  7800 		var i, input, obj,
       
  7801 		    inputs = this._form.getElementsByTagName('input'),
       
  7802 		    inputsLen = inputs.length,
       
  7803 		    baseLayer;
       
  7804 
       
  7805 		this._handlingClick = true;
       
  7806 
       
  7807 		for (i = 0; i < inputsLen; i++) {
       
  7808 			input = inputs[i];
       
  7809 			obj = this._layers[input.layerId];
       
  7810 
       
  7811 			if (input.checked && !this._map.hasLayer(obj.layer)) {
       
  7812 				this._map.addLayer(obj.layer);
       
  7813 				if (!obj.overlay) {
       
  7814 					baseLayer = obj.layer;
       
  7815 				}
       
  7816 			} else if (!input.checked && this._map.hasLayer(obj.layer)) {
       
  7817 				this._map.removeLayer(obj.layer);
       
  7818 			}
       
  7819 		}
       
  7820 
       
  7821 		if (baseLayer) {
       
  7822 			this._map.setZoom(this._map.getZoom());
       
  7823 			this._map.fire('baselayerchange', {layer: baseLayer});
       
  7824 		}
       
  7825 
       
  7826 		this._handlingClick = false;
       
  7827 	},
       
  7828 
       
  7829 	_expand: function () {
       
  7830 		L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
       
  7831 	},
       
  7832 
       
  7833 	_collapse: function () {
       
  7834 		this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
       
  7835 	}
       
  7836 });
       
  7837 
       
  7838 L.control.layers = function (baseLayers, overlays, options) {
       
  7839 	return new L.Control.Layers(baseLayers, overlays, options);
       
  7840 };
       
  7841 
       
  7842 
       
  7843 /*
       
  7844  * L.PosAnimation is used by Leaflet internally for pan animations.
       
  7845  */
       
  7846 
       
  7847 L.PosAnimation = L.Class.extend({
       
  7848 	includes: L.Mixin.Events,
       
  7849 
       
  7850 	run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
       
  7851 		this.stop();
       
  7852 
       
  7853 		this._el = el;
       
  7854 		this._inProgress = true;
       
  7855 
       
  7856 		this.fire('start');
       
  7857 
       
  7858 		el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
       
  7859 		        's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
       
  7860 
       
  7861 		L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
       
  7862 		L.DomUtil.setPosition(el, newPos);
       
  7863 
       
  7864 		// toggle reflow, Chrome flickers for some reason if you don't do this
       
  7865 		L.Util.falseFn(el.offsetWidth);
       
  7866 
       
  7867 		// there's no native way to track value updates of transitioned properties, so we imitate this
       
  7868 		this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50);
       
  7869 	},
       
  7870 
       
  7871 	stop: function () {
       
  7872 		if (!this._inProgress) { return; }
       
  7873 
       
  7874 		// if we just removed the transition property, the element would jump to its final position,
       
  7875 		// so we need to make it stay at the current position
       
  7876 
       
  7877 		L.DomUtil.setPosition(this._el, this._getPos());
       
  7878 		this._onTransitionEnd();
       
  7879 		L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
       
  7880 	},
       
  7881 
       
  7882 	// you can't easily get intermediate values of properties animated with CSS3 Transitions,
       
  7883 	// we need to parse computed style (in case of transform it returns matrix string)
       
  7884 
       
  7885 	_transformRe: /(-?[\d\.]+), (-?[\d\.]+)\)/,
       
  7886 
       
  7887 	_getPos: function () {
       
  7888 		var left, top, matches,
       
  7889 		    el = this._el,
       
  7890 		    style = window.getComputedStyle(el);
       
  7891 
       
  7892 		if (L.Browser.any3d) {
       
  7893 			matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
       
  7894 			left = parseFloat(matches[1]);
       
  7895 			top  = parseFloat(matches[2]);
       
  7896 		} else {
       
  7897 			left = parseFloat(style.left);
       
  7898 			top  = parseFloat(style.top);
       
  7899 		}
       
  7900 
       
  7901 		return new L.Point(left, top, true);
       
  7902 	},
       
  7903 
       
  7904 	_onTransitionEnd: function () {
       
  7905 		L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
       
  7906 
       
  7907 		if (!this._inProgress) { return; }
       
  7908 		this._inProgress = false;
       
  7909 
       
  7910 		this._el.style[L.DomUtil.TRANSITION] = '';
       
  7911 
       
  7912 		clearInterval(this._stepTimer);
       
  7913 
       
  7914 		this.fire('step').fire('end');
       
  7915 	}
       
  7916 
       
  7917 });
       
  7918 
       
  7919 
       
  7920 /*
       
  7921  * Extends L.Map to handle panning animations.
       
  7922  */
       
  7923 
       
  7924 L.Map.include({
       
  7925 
       
  7926 	setView: function (center, zoom, forceReset) {
       
  7927 		zoom = this._limitZoom(zoom);
       
  7928 
       
  7929 		var zoomChanged = (this._zoom !== zoom);
       
  7930 
       
  7931 		if (this._loaded && !forceReset && this._layers) {
       
  7932 
       
  7933 			if (this._panAnim) {
       
  7934 				this._panAnim.stop();
       
  7935 			}
       
  7936 
       
  7937 			var done = (zoomChanged ?
       
  7938 			        this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
       
  7939 			        this._panByIfClose(center));
       
  7940 
       
  7941 			// exit if animated pan or zoom started
       
  7942 			if (done) {
       
  7943 				clearTimeout(this._sizeTimer);
       
  7944 				return this;
       
  7945 			}
       
  7946 		}
       
  7947 
       
  7948 		// reset the map view
       
  7949 		this._resetView(center, zoom);
       
  7950 
       
  7951 		return this;
       
  7952 	},
       
  7953 
       
  7954 	panBy: function (offset, duration, easeLinearity) {
       
  7955 		offset = L.point(offset);
       
  7956 
       
  7957 		if (!(offset.x || offset.y)) {
       
  7958 			return this;
       
  7959 		}
       
  7960 
       
  7961 		if (!this._panAnim) {
       
  7962 			this._panAnim = new L.PosAnimation();
       
  7963 
       
  7964 			this._panAnim.on({
       
  7965 				'step': this._onPanTransitionStep,
       
  7966 				'end': this._onPanTransitionEnd
       
  7967 			}, this);
       
  7968 		}
       
  7969 
       
  7970 		this.fire('movestart');
       
  7971 
       
  7972 		L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
       
  7973 
       
  7974 		var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset)._round();
       
  7975 		this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
       
  7976 
       
  7977 		return this;
       
  7978 	},
       
  7979 
       
  7980 	_onPanTransitionStep: function () {
       
  7981 		this.fire('move');
       
  7982 	},
       
  7983 
       
  7984 	_onPanTransitionEnd: function () {
       
  7985 		L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
       
  7986 		this.fire('moveend');
       
  7987 	},
       
  7988 
       
  7989 	_panByIfClose: function (center) {
       
  7990 		// difference between the new and current centers in pixels
       
  7991 		var offset = this._getCenterOffset(center)._floor();
       
  7992 
       
  7993 		if (this._offsetIsWithinView(offset)) {
       
  7994 			this.panBy(offset);
       
  7995 			return true;
       
  7996 		}
       
  7997 		return false;
       
  7998 	},
       
  7999 
       
  8000 	_offsetIsWithinView: function (offset, multiplyFactor) {
       
  8001 		var m = multiplyFactor || 1,
       
  8002 		    size = this.getSize();
       
  8003 
       
  8004 		return (Math.abs(offset.x) <= size.x * m) &&
       
  8005 		       (Math.abs(offset.y) <= size.y * m);
       
  8006 	}
       
  8007 });
       
  8008 
       
  8009 
       
  8010 /*
       
  8011  * L.PosAnimation fallback implementation that powers Leaflet pan animations
       
  8012  * in browsers that don't support CSS3 Transitions.
       
  8013  */
       
  8014 
       
  8015 L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
       
  8016 
       
  8017 	run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
       
  8018 		this.stop();
       
  8019 
       
  8020 		this._el = el;
       
  8021 		this._inProgress = true;
       
  8022 		this._duration = duration || 0.25;
       
  8023 		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
       
  8024 
       
  8025 		this._startPos = L.DomUtil.getPosition(el);
       
  8026 		this._offset = newPos.subtract(this._startPos);
       
  8027 		this._startTime = +new Date();
       
  8028 
       
  8029 		this.fire('start');
       
  8030 
       
  8031 		this._animate();
       
  8032 	},
       
  8033 
       
  8034 	stop: function () {
       
  8035 		if (!this._inProgress) { return; }
       
  8036 
       
  8037 		this._step();
       
  8038 		this._complete();
       
  8039 	},
       
  8040 
       
  8041 	_animate: function () {
       
  8042 		// animation loop
       
  8043 		this._animId = L.Util.requestAnimFrame(this._animate, this);
       
  8044 		this._step();
       
  8045 	},
       
  8046 
       
  8047 	_step: function () {
       
  8048 		var elapsed = (+new Date()) - this._startTime,
       
  8049 		    duration = this._duration * 1000;
       
  8050 
       
  8051 		if (elapsed < duration) {
       
  8052 			this._runFrame(this._easeOut(elapsed / duration));
       
  8053 		} else {
       
  8054 			this._runFrame(1);
       
  8055 			this._complete();
       
  8056 		}
       
  8057 	},
       
  8058 
       
  8059 	_runFrame: function (progress) {
       
  8060 		var pos = this._startPos.add(this._offset.multiplyBy(progress));
       
  8061 		L.DomUtil.setPosition(this._el, pos);
       
  8062 
       
  8063 		this.fire('step');
       
  8064 	},
       
  8065 
       
  8066 	_complete: function () {
       
  8067 		L.Util.cancelAnimFrame(this._animId);
       
  8068 
       
  8069 		this._inProgress = false;
       
  8070 		this.fire('end');
       
  8071 	},
       
  8072 
       
  8073 	_easeOut: function (t) {
       
  8074 		return 1 - Math.pow(1 - t, this._easeOutPower);
       
  8075 	}
       
  8076 });
       
  8077 
       
  8078 
       
  8079 /*
       
  8080  * Extends L.Map to handle zoom animations.
       
  8081  */
       
  8082 
       
  8083 L.Map.mergeOptions({
       
  8084 	zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
       
  8085 });
       
  8086 
       
  8087 if (L.DomUtil.TRANSITION) {
       
  8088 	L.Map.addInitHook(function () {
       
  8089 		L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
       
  8090 	});
       
  8091 }
       
  8092 
       
  8093 L.Map.include(!L.DomUtil.TRANSITION ? {} : {
       
  8094 
       
  8095 	_zoomToIfClose: function (center, zoom) {
       
  8096 
       
  8097 		if (this._animatingZoom) { return true; }
       
  8098 
       
  8099 		if (!this.options.zoomAnimation) { return false; }
       
  8100 
       
  8101 		var scale = this.getZoomScale(zoom),
       
  8102 		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
       
  8103 
       
  8104 		// if offset does not exceed half of the view
       
  8105 		if (!this._offsetIsWithinView(offset, 1)) { return false; }
       
  8106 
       
  8107 		L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
       
  8108 
       
  8109 		this
       
  8110 		    .fire('movestart')
       
  8111 		    .fire('zoomstart');
       
  8112 
       
  8113 		this.fire('zoomanim', {
       
  8114 			center: center,
       
  8115 			zoom: zoom
       
  8116 		});
       
  8117 
       
  8118 		var origin = this._getCenterLayerPoint().add(offset);
       
  8119 
       
  8120 		this._prepareTileBg();
       
  8121 		this._runAnimation(center, zoom, scale, origin);
       
  8122 
       
  8123 		return true;
       
  8124 	},
       
  8125 
       
  8126 	_catchTransitionEnd: function () {
       
  8127 		if (this._animatingZoom) {
       
  8128 			this._onZoomTransitionEnd();
       
  8129 		}
       
  8130 	},
       
  8131 
       
  8132 	_runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
       
  8133 		this._animateToCenter = center;
       
  8134 		this._animateToZoom = zoom;
       
  8135 		this._animatingZoom = true;
       
  8136 
       
  8137 		if (L.Draggable) {
       
  8138 			L.Draggable._disabled = true;
       
  8139 		}
       
  8140 
       
  8141 		var transform = L.DomUtil.TRANSFORM,
       
  8142 		    tileBg = this._tileBg;
       
  8143 
       
  8144 		clearTimeout(this._clearTileBgTimer);
       
  8145 
       
  8146 		L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
       
  8147 
       
  8148 		var scaleStr = L.DomUtil.getScaleString(scale, origin),
       
  8149 		    oldTransform = tileBg.style[transform];
       
  8150 
       
  8151 		tileBg.style[transform] = backwardsTransform ?
       
  8152 		        oldTransform + ' ' + scaleStr :
       
  8153 		        scaleStr + ' ' + oldTransform;
       
  8154 	},
       
  8155 
       
  8156 	_prepareTileBg: function () {
       
  8157 		var tilePane = this._tilePane,
       
  8158 		    tileBg = this._tileBg;
       
  8159 
       
  8160 		// If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
       
  8161 		if (tileBg && this._getLoadedTilesPercentage(tileBg) > 0.5 &&
       
  8162 			          this._getLoadedTilesPercentage(tilePane) < 0.5) {
       
  8163 
       
  8164 			tilePane.style.visibility = 'hidden';
       
  8165 			tilePane.empty = true;
       
  8166 			this._stopLoadingImages(tilePane);
       
  8167 			return;
       
  8168 		}
       
  8169 
       
  8170 		if (!tileBg) {
       
  8171 			tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
       
  8172 			tileBg.style.zIndex = 1;
       
  8173 		}
       
  8174 
       
  8175 		// prepare the background pane to become the main tile pane
       
  8176 		tileBg.style[L.DomUtil.TRANSFORM] = '';
       
  8177 		tileBg.style.visibility = 'hidden';
       
  8178 
       
  8179 		// tells tile layers to reinitialize their containers
       
  8180 		tileBg.empty = true; //new FG
       
  8181 		tilePane.empty = false; //new BG
       
  8182 
       
  8183 		//Switch out the current layer to be the new bg layer (And vice-versa)
       
  8184 		this._tilePane = this._panes.tilePane = tileBg;
       
  8185 		var newTileBg = this._tileBg = tilePane;
       
  8186 
       
  8187 		L.DomUtil.addClass(newTileBg, 'leaflet-zoom-animated');
       
  8188 
       
  8189 		this._stopLoadingImages(newTileBg);
       
  8190 	},
       
  8191 
       
  8192 	_getLoadedTilesPercentage: function (container) {
       
  8193 		var tiles = container.getElementsByTagName('img'),
       
  8194 		    i, len, count = 0;
       
  8195 
       
  8196 		for (i = 0, len = tiles.length; i < len; i++) {
       
  8197 			if (tiles[i].complete) {
       
  8198 				count++;
       
  8199 			}
       
  8200 		}
       
  8201 		return count / len;
       
  8202 	},
       
  8203 
       
  8204 	// stops loading all tiles in the background layer
       
  8205 	_stopLoadingImages: function (container) {
       
  8206 		var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
       
  8207 		    i, len, tile;
       
  8208 
       
  8209 		for (i = 0, len = tiles.length; i < len; i++) {
       
  8210 			tile = tiles[i];
       
  8211 
       
  8212 			if (!tile.complete) {
       
  8213 				tile.onload = L.Util.falseFn;
       
  8214 				tile.onerror = L.Util.falseFn;
       
  8215 				tile.src = L.Util.emptyImageUrl;
       
  8216 
       
  8217 				tile.parentNode.removeChild(tile);
       
  8218 			}
       
  8219 		}
       
  8220 	},
       
  8221 
       
  8222 	_onZoomTransitionEnd: function () {
       
  8223 		this._restoreTileFront();
       
  8224 
       
  8225 		L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
       
  8226 		L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
       
  8227 		this._animatingZoom = false;
       
  8228 		this._resetView(this._animateToCenter, this._animateToZoom, true, true);
       
  8229 
       
  8230 		if (L.Draggable) {
       
  8231 			L.Draggable._disabled = false;
       
  8232 		}
       
  8233 	},
       
  8234 
       
  8235 	_restoreTileFront: function () {
       
  8236 		this._tilePane.innerHTML = '';
       
  8237 		this._tilePane.style.visibility = '';
       
  8238 		this._tilePane.style.zIndex = 2;
       
  8239 		this._tileBg.style.zIndex = 1;
       
  8240 	},
       
  8241 
       
  8242 	_clearTileBg: function () {
       
  8243 		if (!this._animatingZoom && !this.touchZoom._zooming) {
       
  8244 			this._tileBg.innerHTML = '';
       
  8245 		}
       
  8246 	}
       
  8247 });
       
  8248 
       
  8249 
       
  8250 /*
       
  8251  * Provides L.Map with convenient shortcuts for using browser geolocation features.
       
  8252  */
       
  8253 
       
  8254 L.Map.include({
       
  8255 	_defaultLocateOptions: {
       
  8256 		watch: false,
       
  8257 		setView: false,
       
  8258 		maxZoom: Infinity,
       
  8259 		timeout: 10000,
       
  8260 		maximumAge: 0,
       
  8261 		enableHighAccuracy: false
       
  8262 	},
       
  8263 
       
  8264 	locate: function (/*Object*/ options) {
       
  8265 
       
  8266 		options = this._locationOptions = L.extend(this._defaultLocateOptions, options);
       
  8267 
       
  8268 		if (!navigator.geolocation) {
       
  8269 			this._handleGeolocationError({
       
  8270 				code: 0,
       
  8271 				message: "Geolocation not supported."
       
  8272 			});
       
  8273 			return this;
       
  8274 		}
       
  8275 
       
  8276 		var onResponse = L.bind(this._handleGeolocationResponse, this),
       
  8277 			onError = L.bind(this._handleGeolocationError, this);
       
  8278 
       
  8279 		if (options.watch) {
       
  8280 			this._locationWatchId =
       
  8281 			        navigator.geolocation.watchPosition(onResponse, onError, options);
       
  8282 		} else {
       
  8283 			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
       
  8284 		}
       
  8285 		return this;
       
  8286 	},
       
  8287 
       
  8288 	stopLocate: function () {
       
  8289 		if (navigator.geolocation) {
       
  8290 			navigator.geolocation.clearWatch(this._locationWatchId);
       
  8291 		}
       
  8292 		return this;
       
  8293 	},
       
  8294 
       
  8295 	_handleGeolocationError: function (error) {
       
  8296 		var c = error.code,
       
  8297 		    message = error.message ||
       
  8298 		            (c === 1 ? "permission denied" :
       
  8299 		            (c === 2 ? "position unavailable" : "timeout"));
       
  8300 
       
  8301 		if (this._locationOptions.setView && !this._loaded) {
       
  8302 			this.fitWorld();
       
  8303 		}
       
  8304 
       
  8305 		this.fire('locationerror', {
       
  8306 			code: c,
       
  8307 			message: "Geolocation error: " + message + "."
       
  8308 		});
       
  8309 	},
       
  8310 
       
  8311 	_handleGeolocationResponse: function (pos) {
       
  8312 		var latAccuracy = 180 * pos.coords.accuracy / 4e7,
       
  8313 		    lngAccuracy = latAccuracy * 2,
       
  8314 
       
  8315 		    lat = pos.coords.latitude,
       
  8316 		    lng = pos.coords.longitude,
       
  8317 		    latlng = new L.LatLng(lat, lng),
       
  8318 
       
  8319 		    sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
       
  8320 		    ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
       
  8321 		    bounds = new L.LatLngBounds(sw, ne),
       
  8322 
       
  8323 		    options = this._locationOptions;
       
  8324 
       
  8325 		if (options.setView) {
       
  8326 			var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
       
  8327 			this.setView(latlng, zoom);
       
  8328 		}
       
  8329 
       
  8330 		this.fire('locationfound', {
       
  8331 			latlng: latlng,
       
  8332 			bounds: bounds,
       
  8333 			accuracy: pos.coords.accuracy
       
  8334 		});
       
  8335 	}
       
  8336 });
       
  8337 
       
  8338 
       
  8339 }(this, document));