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