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