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