leaflet-rails 0.4.0.alpha6 → 0.4.2.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin, Akshay Joshi
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are
5
+ permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of
8
+ conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
11
+ of conditions and the following disclaimer in the documentation and/or other materials
12
+ provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
15
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
17
+ COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
21
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,5 +1,5 @@
1
1
  module Leaflet
2
2
  module Rails
3
- VERSION = "0.4.0.alpha6"
3
+ VERSION = "0.4.2.beta1"
4
4
  end
5
5
  end
@@ -1,13 +1,13 @@
1
1
  /*
2
2
  Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin
3
- Leaflet is a modern open-source JavaScript library for interactive maps.
3
+ Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.
4
4
  http://leaflet.cloudmade.com
5
5
  */
6
- (function () {
6
+ (function (window, undefined) {
7
7
 
8
8
  var L, originalL;
9
9
 
10
- if (typeof exports !== 'undefined') {
10
+ if (typeof exports !== undefined + '') {
11
11
  L = exports;
12
12
  } else {
13
13
  originalL = window.L;
@@ -21,7 +21,7 @@ if (typeof exports !== 'undefined') {
21
21
  window.L = L;
22
22
  }
23
23
 
24
- L.version = '0.4';
24
+ L.version = '0.4.2';
25
25
 
26
26
 
27
27
  /*
@@ -57,45 +57,6 @@ L.Util = {
57
57
  };
58
58
  }()),
59
59
 
60
-
61
- // TODO refactor: remove repetition
62
-
63
- requestAnimFrame: (function () {
64
- function timeoutDefer(callback) {
65
- window.setTimeout(callback, 1000 / 60);
66
- }
67
-
68
- var requestFn = window.requestAnimationFrame ||
69
- window.webkitRequestAnimationFrame ||
70
- window.mozRequestAnimationFrame ||
71
- window.oRequestAnimationFrame ||
72
- window.msRequestAnimationFrame ||
73
- timeoutDefer;
74
-
75
- return function (callback, context, immediate, contextEl) {
76
- callback = context ? L.Util.bind(callback, context) : callback;
77
- if (immediate && requestFn === timeoutDefer) {
78
- callback();
79
- } else {
80
- return requestFn.call(window, callback, contextEl);
81
- }
82
- };
83
- }()),
84
-
85
- cancelAnimFrame: (function () {
86
- var requestFn = window.cancelAnimationFrame ||
87
- window.webkitCancelRequestAnimationFrame ||
88
- window.mozCancelRequestAnimationFrame ||
89
- window.oCancelRequestAnimationFrame ||
90
- window.msCancelRequestAnimationFrame ||
91
- clearTimeout;
92
-
93
- return function (handle) {
94
- if (!handle) { return; }
95
- return requestFn.call(window, handle);
96
- };
97
- }()),
98
-
99
60
  limitExecByInterval: function (fn, time, context) {
100
61
  var lock, execOnUnlock;
101
62
 
@@ -131,6 +92,10 @@ L.Util = {
131
92
  return Math.round(num * pow) / pow;
132
93
  },
133
94
 
95
+ splitWords: function (str) {
96
+ return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
97
+ },
98
+
134
99
  setOptions: function (obj, options) {
135
100
  obj.options = L.Util.extend({}, obj.options, options);
136
101
  return obj.options;
@@ -159,6 +124,52 @@ L.Util = {
159
124
  emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
160
125
  };
161
126
 
127
+ (function () {
128
+
129
+ function getPrefixed(name) {
130
+ var i, fn,
131
+ prefixes = ['webkit', 'moz', 'o', 'ms'];
132
+
133
+ for (i = 0; i < prefixes.length && !fn; i++) {
134
+ fn = window[prefixes[i] + name];
135
+ }
136
+
137
+ return fn;
138
+ }
139
+
140
+ function timeoutDefer(fn) {
141
+ return window.setTimeout(fn, 1000 / 60);
142
+ }
143
+
144
+ var requestFn = window.requestAnimationFrame ||
145
+ getPrefixed('RequestAnimationFrame') || timeoutDefer;
146
+
147
+ var cancelFn = window.cancelAnimationFrame ||
148
+ getPrefixed('CancelAnimationFrame') ||
149
+ getPrefixed('CancelRequestAnimationFrame') ||
150
+ function (id) {
151
+ window.clearTimeout(id);
152
+ };
153
+
154
+
155
+ L.Util.requestAnimFrame = function (fn, context, immediate, element) {
156
+ fn = L.Util.bind(fn, context);
157
+
158
+ if (immediate && requestFn === timeoutDefer) {
159
+ fn();
160
+ } else {
161
+ return requestFn.call(window, fn, element);
162
+ }
163
+ };
164
+
165
+ L.Util.cancelAnimFrame = function (id) {
166
+ if (id) {
167
+ cancelFn.call(window, id);
168
+ }
169
+ };
170
+
171
+ }());
172
+
162
173
 
163
174
  /*
164
175
  * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
@@ -228,42 +239,80 @@ L.Class.mergeOptions = function (options) {
228
239
  * L.Mixin.Events adds custom events functionality to Leaflet classes
229
240
  */
230
241
 
242
+ var key = '_leaflet_events';
243
+
231
244
  L.Mixin = {};
232
245
 
233
246
  L.Mixin.Events = {
234
- addEventListener: function (/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
235
- var events = this._leaflet_events = this._leaflet_events || {};
236
- events[type] = events[type] || [];
237
- events[type].push({
238
- action: fn,
239
- context: context || this
240
- });
247
+
248
+ addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
249
+ var events = this[key] = this[key] || {},
250
+ type, i, len;
251
+
252
+ // Types can be a map of types/handlers
253
+ if (typeof types === 'object') {
254
+ for (type in types) {
255
+ if (types.hasOwnProperty(type)) {
256
+ this.addEventListener(type, types[type], fn);
257
+ }
258
+ }
259
+
260
+ return this;
261
+ }
262
+
263
+ types = L.Util.splitWords(types);
264
+
265
+ for (i = 0, len = types.length; i < len; i++) {
266
+ events[types[i]] = events[types[i]] || [];
267
+ events[types[i]].push({
268
+ action: fn,
269
+ context: context || this
270
+ });
271
+ }
272
+
241
273
  return this;
242
274
  },
243
275
 
244
- hasEventListeners: function (/*String*/ type) /*-> Boolean*/ {
245
- var k = '_leaflet_events';
246
- return (k in this) && (type in this[k]) && (this[k][type].length > 0);
276
+ hasEventListeners: function (type) { // (String) -> Boolean
277
+ return (key in this) && (type in this[key]) && (this[key][type].length > 0);
247
278
  },
248
279
 
249
- removeEventListener: function (/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
250
- if (!this.hasEventListeners(type)) {
280
+ removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
281
+ var events = this[key],
282
+ type, i, len, listeners, j;
283
+
284
+ if (typeof types === 'object') {
285
+ for (type in types) {
286
+ if (types.hasOwnProperty(type)) {
287
+ this.removeEventListener(type, types[type], fn);
288
+ }
289
+ }
290
+
251
291
  return this;
252
292
  }
253
293
 
254
- for (var i = 0, events = this._leaflet_events, len = events[type].length; i < len; i++) {
255
- if (
256
- (events[type][i].action === fn) &&
257
- (!context || (events[type][i].context === context))
258
- ) {
259
- events[type].splice(i, 1);
260
- return this;
294
+ types = L.Util.splitWords(types);
295
+
296
+ for (i = 0, len = types.length; i < len; i++) {
297
+
298
+ if (this.hasEventListeners(types[i])) {
299
+ listeners = events[types[i]];
300
+
301
+ for (j = listeners.length - 1; j >= 0; j--) {
302
+ if (
303
+ (!fn || listeners[j].action === fn) &&
304
+ (!context || (listeners[j].context === context))
305
+ ) {
306
+ listeners.splice(j, 1);
307
+ }
308
+ }
261
309
  }
262
310
  }
311
+
263
312
  return this;
264
313
  },
265
314
 
266
- fireEvent: function (/*String*/ type, /*(optional) Object*/ data) {
315
+ fireEvent: function (type, data) { // (String[, Object])
267
316
  if (!this.hasEventListeners(type)) {
268
317
  return this;
269
318
  }
@@ -273,7 +322,7 @@ L.Mixin.Events = {
273
322
  target: this
274
323
  }, data);
275
324
 
276
- var listeners = this._leaflet_events[type].slice();
325
+ var listeners = this[key][type].slice();
277
326
 
278
327
  for (var i = 0, len = listeners.length; i < len; i++) {
279
328
  listeners[i].action.call(listeners[i].context || this, event);
@@ -294,16 +343,19 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
294
343
  ie6 = ie && !window.XMLHttpRequest,
295
344
  webkit = ua.indexOf("webkit") !== -1,
296
345
  gecko = ua.indexOf("gecko") !== -1,
346
+ //Terrible browser detection to work around a safari / iOS / android browser bug. See TileLayer._addTile and debug/hacks/jitter.html
347
+ chrome = ua.indexOf("chrome") !== -1,
297
348
  opera = window.opera,
298
349
  android = ua.indexOf("android") !== -1,
299
- mobile = typeof orientation !== 'undefined' ? true : false,
350
+ android23 = ua.search("android [23]") !== -1,
351
+ mobile = typeof orientation !== undefined + '' ? true : false,
300
352
  doc = document.documentElement,
301
353
  ie3d = ie && ('transition' in doc.style),
302
354
  webkit3d = webkit && ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
303
355
  gecko3d = gecko && ('MozPerspective' in doc.style),
304
356
  opera3d = opera && ('OTransition' in doc.style);
305
357
 
306
- var touch = (function () {
358
+ var touch = !window.L_NO_TOUCH && (function () {
307
359
  var startName = 'ontouchstart';
308
360
 
309
361
  // WebKit, etc
@@ -330,26 +382,34 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
330
382
  return supported;
331
383
  }());
332
384
 
385
+ var retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) || ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches));
386
+
333
387
  L.Browser = {
388
+ ua: ua,
334
389
  ie: ie,
335
390
  ie6: ie6,
336
391
  webkit: webkit,
337
392
  gecko: gecko,
338
393
  opera: opera,
339
394
  android: android,
395
+ android23: android23,
396
+
397
+ chrome: chrome,
340
398
 
341
399
  ie3d: ie3d,
342
400
  webkit3d: webkit3d,
343
401
  gecko3d: gecko3d,
344
402
  opera3d: opera3d,
345
- any3d: ie3d || webkit3d || gecko3d || opera3d,
403
+ any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d),
346
404
 
347
405
  mobile: mobile,
348
406
  mobileWebkit: mobile && webkit,
349
407
  mobileWebkit3d: mobile && webkit3d,
350
408
  mobileOpera: mobile && opera,
351
409
 
352
- touch: touch
410
+ touch: touch,
411
+
412
+ retina: retina
353
413
  };
354
414
  }());
355
415
 
@@ -365,7 +425,7 @@ L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
365
425
 
366
426
  L.Point.prototype = {
367
427
  add: function (point) {
368
- return this.clone()._add(point);
428
+ return this.clone()._add(L.point(point));
369
429
  },
370
430
 
371
431
  _add: function (point) {
@@ -375,7 +435,7 @@ L.Point.prototype = {
375
435
  },
376
436
 
377
437
  subtract: function (point) {
378
- return this.clone()._subtract(point);
438
+ return this.clone()._subtract(L.point(point));
379
439
  },
380
440
 
381
441
  // destructive subtract (faster)
@@ -389,13 +449,16 @@ L.Point.prototype = {
389
449
  return new L.Point(this.x / num, this.y / num, round);
390
450
  },
391
451
 
392
- multiplyBy: function (num) {
393
- return new L.Point(this.x * num, this.y * num);
452
+ multiplyBy: function (num, round) {
453
+ return new L.Point(this.x * num, this.y * num, round);
394
454
  },
395
455
 
396
456
  distanceTo: function (point) {
457
+ point = L.point(point);
458
+
397
459
  var x = point.x - this.x,
398
460
  y = point.y - this.y;
461
+
399
462
  return Math.sqrt(x * x + y * y);
400
463
  },
401
464
 
@@ -410,6 +473,16 @@ L.Point.prototype = {
410
473
  return this;
411
474
  },
412
475
 
476
+ floor: function () {
477
+ return this.clone()._floor();
478
+ },
479
+
480
+ _floor: function () {
481
+ this.x = Math.floor(this.x);
482
+ this.y = Math.floor(this.y);
483
+ return this;
484
+ },
485
+
413
486
  clone: function () {
414
487
  return new L.Point(this.x, this.y);
415
488
  },
@@ -421,44 +494,75 @@ L.Point.prototype = {
421
494
  }
422
495
  };
423
496
 
497
+ L.point = function (x, y, round) {
498
+ if (x instanceof L.Point) {
499
+ return x;
500
+ }
501
+ if (x instanceof Array) {
502
+ return new L.Point(x[0], x[1]);
503
+ }
504
+ if (isNaN(x)) {
505
+ return x;
506
+ }
507
+ return new L.Point(x, y, round);
508
+ };
509
+
424
510
 
425
511
  /*
426
512
  * L.Bounds represents a rectangular area on the screen in pixel coordinates.
427
513
  */
428
514
 
429
515
  L.Bounds = L.Class.extend({
430
- initialize: function (min, max) { //(Point, Point) or Point[]
431
- if (!min) {
432
- return;
433
- }
434
- var points = (min instanceof Array ? min : [min, max]);
516
+
517
+ initialize: function (a, b) { //(Point, Point) or Point[]
518
+ if (!a) { return; }
519
+
520
+ var points = b ? [a, b] : a;
521
+
435
522
  for (var i = 0, len = points.length; i < len; i++) {
436
523
  this.extend(points[i]);
437
524
  }
438
525
  },
439
526
 
440
527
  // extend the bounds to contain the given point
441
- extend: function (/*Point*/ point) {
528
+ extend: function (point) { // (Point)
529
+ point = L.point(point);
530
+
442
531
  if (!this.min && !this.max) {
443
- this.min = new L.Point(point.x, point.y);
444
- this.max = new L.Point(point.x, point.y);
532
+ this.min = point.clone();
533
+ this.max = point.clone();
445
534
  } else {
446
535
  this.min.x = Math.min(point.x, this.min.x);
447
536
  this.max.x = Math.max(point.x, this.max.x);
448
537
  this.min.y = Math.min(point.y, this.min.y);
449
538
  this.max.y = Math.max(point.y, this.max.y);
450
539
  }
540
+ return this;
451
541
  },
452
542
 
453
- getCenter: function (round)/*->Point*/ {
543
+ getCenter: function (round) { // (Boolean) -> Point
454
544
  return new L.Point(
455
545
  (this.min.x + this.max.x) / 2,
456
546
  (this.min.y + this.max.y) / 2, round);
457
547
  },
458
548
 
459
- contains: function (/*Bounds or Point*/ obj)/*->Boolean*/ {
549
+ getBottomLeft: function () { // -> Point
550
+ return new L.Point(this.min.x, this.max.y);
551
+ },
552
+
553
+ getTopRight: function () { // -> Point
554
+ return new L.Point(this.max.x, this.min.y);
555
+ },
556
+
557
+ contains: function (obj) { // (Bounds) or (Point) -> Boolean
460
558
  var min, max;
461
559
 
560
+ if (typeof obj[0] === 'number' || obj instanceof L.Point) {
561
+ obj = L.point(obj);
562
+ } else {
563
+ obj = L.bounds(obj);
564
+ }
565
+
462
566
  if (obj instanceof L.Bounds) {
463
567
  min = obj.min;
464
568
  max = obj.max;
@@ -472,7 +576,9 @@ L.Bounds = L.Class.extend({
472
576
  (max.y <= this.max.y);
473
577
  },
474
578
 
475
- intersects: function (/*Bounds*/ bounds) {
579
+ intersects: function (bounds) { // (Bounds) -> Boolean
580
+ bounds = L.bounds(bounds);
581
+
476
582
  var min = this.min,
477
583
  max = this.max,
478
584
  min2 = bounds.min,
@@ -486,6 +592,13 @@ L.Bounds = L.Class.extend({
486
592
 
487
593
  });
488
594
 
595
+ L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
596
+ if (!a || a instanceof L.Bounds) {
597
+ return a;
598
+ }
599
+ return new L.Bounds(a, b);
600
+ };
601
+
489
602
 
490
603
  /*
491
604
  * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
@@ -616,19 +729,38 @@ L.DomUtil = {
616
729
  },
617
730
 
618
731
  removeClass: function (el, name) {
619
- el.className = el.className.replace(/(\S+)\s*/g, function (w, match) {
732
+ function replaceFn(w, match) {
620
733
  if (match === name) {
621
734
  return '';
622
735
  }
623
736
  return w;
624
- }).replace(/^\s+/, '');
737
+ }
738
+ el.className = el.className
739
+ .replace(/(\S+)\s*/g, replaceFn)
740
+ .replace(/(^\s+|\s+$)/, '');
625
741
  },
626
742
 
627
743
  setOpacity: function (el, value) {
628
- if (L.Browser.ie) {
629
- el.style.filter += value !== 1 ? 'alpha(opacity=' + Math.round(value * 100) + ')' : '';
630
- } else {
744
+
745
+ if ('opacity' in el.style) {
631
746
  el.style.opacity = value;
747
+
748
+ } else if (L.Browser.ie) {
749
+
750
+ var filter = false,
751
+ filterName = 'DXImageTransform.Microsoft.Alpha';
752
+
753
+ // filters collection throws an error if we try to retrieve a filter that doesn't exist
754
+ try { filter = el.filters.item(filterName); } catch (e) {}
755
+
756
+ value = Math.round(value * 100);
757
+
758
+ if (filter) {
759
+ filter.Enabled = (value === 100);
760
+ filter.Opacity = value;
761
+ } else {
762
+ el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
763
+ }
632
764
  }
633
765
  },
634
766
 
@@ -693,7 +825,7 @@ L.Util.extend(L.DomUtil, {
693
825
  CM.LatLng represents a geographical point with latitude and longtitude coordinates.
694
826
  */
695
827
 
696
- L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
828
+ L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
697
829
  var lat = parseFloat(rawLat),
698
830
  lng = parseFloat(rawLng);
699
831
 
@@ -706,7 +838,6 @@ L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
706
838
  lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
707
839
  }
708
840
 
709
- //TODO change to lat() & lng()
710
841
  this.lat = lat;
711
842
  this.lng = lng;
712
843
  };
@@ -718,23 +849,25 @@ L.Util.extend(L.LatLng, {
718
849
  });
719
850
 
720
851
  L.LatLng.prototype = {
721
- equals: function (/*LatLng*/ obj) {
722
- if (!(obj instanceof L.LatLng)) {
723
- return false;
724
- }
852
+ equals: function (obj) { // (LatLng) -> Boolean
853
+ if (!obj) { return false; }
854
+
855
+ obj = L.latLng(obj);
725
856
 
726
857
  var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
727
858
  return margin <= L.LatLng.MAX_MARGIN;
728
859
  },
729
860
 
730
- toString: function () {
861
+ toString: function () { // -> String
731
862
  return 'LatLng(' +
732
863
  L.Util.formatNum(this.lat) + ', ' +
733
864
  L.Util.formatNum(this.lng) + ')';
734
865
  },
735
866
 
736
867
  // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
737
- distanceTo: function (/*LatLng*/ other)/*->Double*/ {
868
+ distanceTo: function (other) { // (LatLng) -> Number
869
+ other = L.latLng(other);
870
+
738
871
  var R = 6378137, // earth radius in meters
739
872
  d2r = L.LatLng.DEG_TO_RAD,
740
873
  dLat = (other.lat - this.lat) * d2r,
@@ -750,6 +883,19 @@ L.LatLng.prototype = {
750
883
  }
751
884
  };
752
885
 
886
+ L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
887
+ if (a instanceof L.LatLng) {
888
+ return a;
889
+ }
890
+ if (a instanceof Array) {
891
+ return new L.LatLng(a[0], a[1]);
892
+ }
893
+ if (isNaN(a)) {
894
+ return a;
895
+ }
896
+ return new L.LatLng(a, b, c);
897
+ };
898
+
753
899
 
754
900
  /*
755
901
  * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
@@ -757,17 +903,23 @@ L.LatLng.prototype = {
757
903
 
758
904
  L.LatLngBounds = L.Class.extend({
759
905
  initialize: function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
760
- if (!southWest) {
761
- return;
762
- }
763
- var latlngs = (southWest instanceof Array ? southWest : [southWest, northEast]);
906
+ if (!southWest) { return; }
907
+
908
+ var latlngs = northEast ? [southWest, northEast] : southWest;
909
+
764
910
  for (var i = 0, len = latlngs.length; i < len; i++) {
765
911
  this.extend(latlngs[i]);
766
912
  }
767
913
  },
768
914
 
769
915
  // extend the bounds to contain the given point or bounds
770
- extend: function (/*LatLng or LatLngBounds*/ obj) {
916
+ extend: function (obj) { // (LatLng) or (LatLngBounds)
917
+ if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
918
+ obj = L.latLng(obj);
919
+ } else {
920
+ obj = L.latLngBounds(obj);
921
+ }
922
+
771
923
  if (obj instanceof L.LatLng) {
772
924
  if (!this._southWest && !this._northEast) {
773
925
  this._southWest = new L.LatLng(obj.lat, obj.lng, true);
@@ -775,6 +927,7 @@ L.LatLngBounds = L.Class.extend({
775
927
  } else {
776
928
  this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
777
929
  this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
930
+
778
931
  this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
779
932
  this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
780
933
  }
@@ -797,7 +950,7 @@ L.LatLngBounds = L.Class.extend({
797
950
  new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
798
951
  },
799
952
 
800
- getCenter: function () /*-> LatLng*/ {
953
+ getCenter: function () { // -> LatLng
801
954
  return new L.LatLng(
802
955
  (this._southWest.lat + this._northEast.lat) / 2,
803
956
  (this._southWest.lng + this._northEast.lng) / 2);
@@ -819,7 +972,13 @@ L.LatLngBounds = L.Class.extend({
819
972
  return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
820
973
  },
821
974
 
822
- contains: function (/*LatLngBounds or LatLng*/ obj) /*-> Boolean*/ {
975
+ contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
976
+ if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
977
+ obj = L.latLng(obj);
978
+ } else {
979
+ obj = L.latLngBounds(obj);
980
+ }
981
+
823
982
  var sw = this._southWest,
824
983
  ne = this._northEast,
825
984
  sw2, ne2;
@@ -835,7 +994,9 @@ L.LatLngBounds = L.Class.extend({
835
994
  (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
836
995
  },
837
996
 
838
- intersects: function (/*LatLngBounds*/ bounds) {
997
+ intersects: function (bounds) { // (LatLngBounds)
998
+ bounds = L.latLngBounds(bounds);
999
+
839
1000
  var sw = this._southWest,
840
1001
  ne = this._northEast,
841
1002
  sw2 = bounds.getSouthWest(),
@@ -853,14 +1014,25 @@ L.LatLngBounds = L.Class.extend({
853
1014
  return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
854
1015
  },
855
1016
 
856
- equals: function (/*LatLngBounds*/ bounds) {
857
- return bounds ? this._southWest.equals(bounds.getSouthWest()) &&
858
- this._northEast.equals(bounds.getNorthEast()) : false;
1017
+ equals: function (bounds) { // (LatLngBounds)
1018
+ if (!bounds) { return false; }
1019
+
1020
+ bounds = L.latLngBounds(bounds);
1021
+
1022
+ return this._southWest.equals(bounds.getSouthWest()) &&
1023
+ this._northEast.equals(bounds.getNorthEast());
859
1024
  }
860
1025
  });
861
1026
 
862
1027
  //TODO International date line?
863
1028
 
1029
+ L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
1030
+ if (!a || a instanceof L.LatLngBounds) {
1031
+ return a;
1032
+ }
1033
+ return new L.LatLngBounds(a, b);
1034
+ };
1035
+
864
1036
 
865
1037
  /*
866
1038
  * L.Projection contains various geographical projections used by CRS classes.
@@ -966,6 +1138,7 @@ L.CRS.EPSG4326 = L.Util.extend({}, L.CRS, {
966
1138
  */
967
1139
 
968
1140
  L.Map = L.Class.extend({
1141
+
969
1142
  includes: L.Mixin.Events,
970
1143
 
971
1144
  options: {
@@ -977,9 +1150,9 @@ L.Map = L.Class.extend({
977
1150
  layers: Array,
978
1151
  */
979
1152
 
980
- fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android,
1153
+ fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
981
1154
  trackResize: true,
982
- markerZoomAnimation: true
1155
+ markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
983
1156
  },
984
1157
 
985
1158
  initialize: function (id, options) { // (HTMLElement or String, Object)
@@ -994,8 +1167,8 @@ L.Map = L.Class.extend({
994
1167
  this.setMaxBounds(options.maxBounds);
995
1168
  }
996
1169
 
997
- if (options.center && typeof options.zoom !== 'undefined') {
998
- this.setView(options.center, options.zoom, true);
1170
+ if (options.center && options.zoom !== undefined) {
1171
+ this.setView(L.latLng(options.center), options.zoom, true);
999
1172
  }
1000
1173
 
1001
1174
  this._initLayers(options.layers);
@@ -1006,7 +1179,7 @@ L.Map = L.Class.extend({
1006
1179
 
1007
1180
  // replaced by animation-powered implementation in Map.PanAnimation.js
1008
1181
  setView: function (center, zoom) {
1009
- this._resetView(center, this._limitZoom(zoom));
1182
+ this._resetView(L.latLng(center), this._limitZoom(zoom));
1010
1183
  return this;
1011
1184
  },
1012
1185
 
@@ -1024,7 +1197,7 @@ L.Map = L.Class.extend({
1024
1197
 
1025
1198
  fitBounds: function (bounds) { // (LatLngBounds)
1026
1199
  var zoom = this.getBoundsZoom(bounds);
1027
- return this.setView(bounds.getCenter(), zoom);
1200
+ return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
1028
1201
  },
1029
1202
 
1030
1203
  fitWorld: function () {
@@ -1042,13 +1215,15 @@ L.Map = L.Class.extend({
1042
1215
  // replaced with animated panBy in Map.Animation.js
1043
1216
  this.fire('movestart');
1044
1217
 
1045
- this._rawPanBy(offset);
1218
+ this._rawPanBy(L.point(offset));
1046
1219
 
1047
1220
  this.fire('move');
1048
1221
  return this.fire('moveend');
1049
1222
  },
1050
1223
 
1051
1224
  setMaxBounds: function (bounds) {
1225
+ bounds = L.latLngBounds(bounds);
1226
+
1052
1227
  this.options.maxBounds = bounds;
1053
1228
 
1054
1229
  if (!bounds) {
@@ -1072,6 +1247,8 @@ L.Map = L.Class.extend({
1072
1247
  },
1073
1248
 
1074
1249
  panInsideBounds: function (bounds) {
1250
+ bounds = L.latLngBounds(bounds);
1251
+
1075
1252
  var viewBounds = this.getBounds(),
1076
1253
  viewSw = this.project(viewBounds.getSouthWest()),
1077
1254
  viewNe = this.project(viewBounds.getNorthEast()),
@@ -1096,7 +1273,7 @@ L.Map = L.Class.extend({
1096
1273
  return this.panBy(new L.Point(dx, dy, true));
1097
1274
  },
1098
1275
 
1099
- addLayer: function (layer, insertAtTheBottom) {
1276
+ addLayer: function (layer) {
1100
1277
  // TODO method is too big, refactor
1101
1278
 
1102
1279
  var id = L.Util.stamp(layer);
@@ -1121,7 +1298,7 @@ L.Map = L.Class.extend({
1121
1298
  }
1122
1299
 
1123
1300
  var onMapLoad = function () {
1124
- layer.onAdd(this, insertAtTheBottom);
1301
+ layer.onAdd(this);
1125
1302
  this.fire('layeradd', {layer: layer});
1126
1303
  };
1127
1304
 
@@ -1158,7 +1335,7 @@ L.Map = L.Class.extend({
1158
1335
  return this._layers.hasOwnProperty(id);
1159
1336
  },
1160
1337
 
1161
- invalidateSize: function () {
1338
+ invalidateSize: function (animate) {
1162
1339
  var oldSize = this.getSize();
1163
1340
 
1164
1341
  this._sizeChanged = true;
@@ -1170,13 +1347,16 @@ L.Map = L.Class.extend({
1170
1347
  if (!this._loaded) { return this; }
1171
1348
 
1172
1349
  var offset = oldSize.subtract(this.getSize()).divideBy(2, true);
1173
- this._rawPanBy(offset);
1174
-
1175
- this.fire('move');
1350
+ if (animate) {
1351
+ this.panBy(offset);
1352
+ } else {
1353
+ this._rawPanBy(offset);
1176
1354
 
1177
- clearTimeout(this._sizeTimer);
1178
- this._sizeTimer = setTimeout(L.Util.bind(this.fire, this, 'moveend'), 200);
1355
+ this.fire('move');
1179
1356
 
1357
+ clearTimeout(this._sizeTimer);
1358
+ this._sizeTimer = setTimeout(L.Util.bind(this.fire, this, 'moveend'), 200);
1359
+ }
1180
1360
  return this;
1181
1361
  },
1182
1362
 
@@ -1197,10 +1377,7 @@ L.Map = L.Class.extend({
1197
1377
  // public methods for getting map state
1198
1378
 
1199
1379
  getCenter: function () { // (Boolean) -> LatLng
1200
- var viewHalf = this.getSize().divideBy(2),
1201
- centerPoint = this._getTopLeftPoint().add(viewHalf);
1202
-
1203
- return this.unproject(centerPoint, this._zoom);
1380
+ return this.layerPointToLatLng(this._getCenterLayerPoint());
1204
1381
  },
1205
1382
 
1206
1383
  getZoom: function () {
@@ -1209,8 +1386,8 @@ L.Map = L.Class.extend({
1209
1386
 
1210
1387
  getBounds: function () {
1211
1388
  var bounds = this.getPixelBounds(),
1212
- sw = this.unproject(new L.Point(bounds.min.x, bounds.max.y), this._zoom, true),
1213
- ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y), this._zoom, true);
1389
+ sw = this.unproject(bounds.getBottomLeft()),
1390
+ ne = this.unproject(bounds.getTopRight());
1214
1391
 
1215
1392
  return new L.LatLngBounds(sw, ne);
1216
1393
  },
@@ -1224,13 +1401,15 @@ L.Map = L.Class.extend({
1224
1401
  },
1225
1402
 
1226
1403
  getMaxZoom: function () {
1227
- var z1 = typeof this.options.maxZoom === 'undefined' ? Infinity : this.options.maxZoom,
1228
- z2 = typeof this._layersMaxZoom === 'undefined' ? Infinity : this._layersMaxZoom;
1404
+ var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
1405
+ z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom;
1229
1406
 
1230
1407
  return Math.min(z1, z2);
1231
1408
  },
1232
1409
 
1233
1410
  getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
1411
+ bounds = L.latLngBounds(bounds);
1412
+
1234
1413
  var size = this.getSize(),
1235
1414
  zoom = this.options.minZoom || 0,
1236
1415
  maxZoom = this.getMaxZoom(),
@@ -1249,7 +1428,7 @@ L.Map = L.Class.extend({
1249
1428
  zoom++;
1250
1429
  nePoint = this.project(ne, zoom);
1251
1430
  swPoint = this.project(sw, zoom);
1252
- boundsSize = new L.Point(nePoint.x - swPoint.x, swPoint.y - nePoint.y);
1431
+ boundsSize = new L.Point(Math.abs(nePoint.x - swPoint.x), Math.abs(swPoint.y - nePoint.y));
1253
1432
 
1254
1433
  if (!inside) {
1255
1434
  zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
@@ -1294,56 +1473,71 @@ L.Map = L.Class.extend({
1294
1473
  },
1295
1474
 
1296
1475
 
1297
- // conversion methods
1476
+ // TODO replace with universal implementation after refactoring projections
1298
1477
 
1299
- mouseEventToContainerPoint: function (e) { // (MouseEvent)
1300
- return L.DomEvent.getMousePosition(e, this._container);
1478
+ getZoomScale: function (toZoom) {
1479
+ var crs = this.options.crs;
1480
+ return crs.scale(toZoom) / crs.scale(this._zoom);
1301
1481
  },
1302
1482
 
1303
- mouseEventToLayerPoint: function (e) { // (MouseEvent)
1304
- return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
1483
+ getScaleZoom: function (scale) {
1484
+ return this._zoom + (Math.log(scale) / Math.LN2);
1305
1485
  },
1306
1486
 
1307
- mouseEventToLatLng: function (e) { // (MouseEvent)
1308
- return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
1309
- },
1310
1487
 
1311
- containerPointToLayerPoint: function (point) { // (Point)
1312
- return point.subtract(L.DomUtil.getPosition(this._mapPane));
1488
+ // conversion methods
1489
+
1490
+ project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
1491
+ zoom = zoom === undefined ? this._zoom : zoom;
1492
+ return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
1313
1493
  },
1314
1494
 
1315
- layerPointToContainerPoint: function (point) { // (Point)
1316
- return point.add(L.DomUtil.getPosition(this._mapPane));
1495
+ unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
1496
+ zoom = zoom === undefined ? this._zoom : zoom;
1497
+ return this.options.crs.pointToLatLng(L.point(point), zoom);
1317
1498
  },
1318
1499
 
1319
1500
  layerPointToLatLng: function (point) { // (Point)
1320
- return this.unproject(point.add(this._initialTopLeftPoint));
1501
+ var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
1502
+ return this.unproject(projectedPoint);
1321
1503
  },
1322
1504
 
1323
1505
  latLngToLayerPoint: function (latlng) { // (LatLng)
1324
- return this.project(latlng)._round()._subtract(this._initialTopLeftPoint);
1506
+ var projectedPoint = this.project(L.latLng(latlng))._round();
1507
+ return projectedPoint._subtract(this._initialTopLeftPoint);
1508
+ },
1509
+
1510
+ containerPointToLayerPoint: function (point) { // (Point)
1511
+ return L.point(point).subtract(this._getMapPanePos());
1512
+ },
1513
+
1514
+ layerPointToContainerPoint: function (point) { // (Point)
1515
+ return L.point(point).add(this._getMapPanePos());
1325
1516
  },
1326
1517
 
1327
1518
  containerPointToLatLng: function (point) {
1328
- return this.layerPointToLatLng(this.containerPointToLayerPoint(point));
1519
+ var layerPoint = this.containerPointToLayerPoint(L.point(point));
1520
+ return this.layerPointToLatLng(layerPoint);
1329
1521
  },
1330
1522
 
1331
1523
  latLngToContainerPoint: function (latlng) {
1332
- return this.layerPointToContainerPoint(this.latLngToLayerPoint(latlng));
1524
+ return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
1333
1525
  },
1334
1526
 
1335
- project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
1336
- zoom = typeof zoom === 'undefined' ? this._zoom : zoom;
1337
- return this.options.crs.latLngToPoint(latlng, zoom);
1527
+ mouseEventToContainerPoint: function (e) { // (MouseEvent)
1528
+ return L.DomEvent.getMousePosition(e, this._container);
1338
1529
  },
1339
1530
 
1340
- unproject: function (point, zoom) { // (Point[, Number, Boolean]) -> LatLng
1341
- zoom = typeof zoom === 'undefined' ? this._zoom : zoom;
1342
- return this.options.crs.pointToLatLng(point, zoom);
1531
+ mouseEventToLayerPoint: function (e) { // (MouseEvent)
1532
+ return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
1343
1533
  },
1344
1534
 
1535
+ mouseEventToLatLng: function (e) { // (MouseEvent)
1536
+ return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
1537
+ },
1345
1538
 
1346
- // private methods that modify map state
1539
+
1540
+ // map initialization methods
1347
1541
 
1348
1542
  _initContainer: function (id) {
1349
1543
  var container = this._container = L.DomUtil.get(id);
@@ -1359,14 +1553,14 @@ L.Map = L.Class.extend({
1359
1553
  var container = this._container;
1360
1554
 
1361
1555
  container.innerHTML = '';
1362
- container.className += ' leaflet-container';
1556
+ L.DomUtil.addClass(container, 'leaflet-container');
1363
1557
 
1364
1558
  if (L.Browser.touch) {
1365
- container.className += ' leaflet-touch';
1559
+ L.DomUtil.addClass(container, 'leaflet-touch');
1366
1560
  }
1367
1561
 
1368
1562
  if (this.options.fadeAnimation) {
1369
- container.className += ' leaflet-fade-anim';
1563
+ L.DomUtil.addClass(container, 'leaflet-fade-anim');
1370
1564
  }
1371
1565
 
1372
1566
  var position = L.DomUtil.getStyle(container, 'position');
@@ -1395,10 +1589,12 @@ L.Map = L.Class.extend({
1395
1589
  panes.markerPane = this._createPane('leaflet-marker-pane');
1396
1590
  panes.popupPane = this._createPane('leaflet-popup-pane');
1397
1591
 
1592
+ var zoomHide = ' leaflet-zoom-hide';
1593
+
1398
1594
  if (!this.options.markerZoomAnimation) {
1399
- panes.markerPane.className += ' leaflet-zoom-hide';
1400
- panes.shadowPane.className += ' leaflet-zoom-hide';
1401
- panes.popupPane.className += ' leaflet-zoom-hide';
1595
+ L.DomUtil.addClass(panes.markerPane, zoomHide);
1596
+ L.DomUtil.addClass(panes.shadowPane, zoomHide);
1597
+ L.DomUtil.addClass(panes.popupPane, zoomHide);
1402
1598
  }
1403
1599
  },
1404
1600
 
@@ -1415,6 +1611,22 @@ L.Map = L.Class.extend({
1415
1611
  }
1416
1612
  },
1417
1613
 
1614
+ _initLayers: function (layers) {
1615
+ layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
1616
+
1617
+ this._layers = {};
1618
+ this._tileLayersNum = 0;
1619
+
1620
+ var i, len;
1621
+
1622
+ for (i = 0, len = layers.length; i < len; i++) {
1623
+ this.addLayer(layers[i]);
1624
+ }
1625
+ },
1626
+
1627
+
1628
+ // private methods that modify map state
1629
+
1418
1630
  _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
1419
1631
 
1420
1632
  var zoomChanged = (this._zoom !== zoom);
@@ -1434,7 +1646,7 @@ L.Map = L.Class.extend({
1434
1646
  if (!preserveMapOffset) {
1435
1647
  L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
1436
1648
  } else {
1437
- this._initialTopLeftPoint._add(L.DomUtil.getPosition(this._mapPane));
1649
+ this._initialTopLeftPoint._add(this._getMapPanePos());
1438
1650
  }
1439
1651
 
1440
1652
  this._tileLayersToLoad = this._tileLayersNum;
@@ -1447,7 +1659,7 @@ L.Map = L.Class.extend({
1447
1659
  this.fire('zoomend');
1448
1660
  }
1449
1661
 
1450
- this.fire('moveend');
1662
+ this.fire('moveend', {hard: !preserveMapOffset});
1451
1663
 
1452
1664
  if (!this._loaded) {
1453
1665
  this._loaded = true;
@@ -1455,22 +1667,8 @@ L.Map = L.Class.extend({
1455
1667
  }
1456
1668
  },
1457
1669
 
1458
- _initLayers: function (layers) {
1459
- layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
1460
-
1461
- this._layers = {};
1462
- this._tileLayersNum = 0;
1463
-
1464
- var i, len;
1465
-
1466
- for (i = 0, len = layers.length; i < len; i++) {
1467
- this.addLayer(layers[i]);
1468
- }
1469
- },
1470
-
1471
1670
  _rawPanBy: function (offset) {
1472
- var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset);
1473
- L.DomUtil.setPosition(this._mapPane, newPos);
1671
+ L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
1474
1672
  },
1475
1673
 
1476
1674
 
@@ -1479,29 +1677,29 @@ L.Map = L.Class.extend({
1479
1677
  _initEvents: function () {
1480
1678
  if (!L.DomEvent) { return; }
1481
1679
 
1482
- L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
1483
-
1484
- var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'];
1680
+ L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
1485
1681
 
1486
- var i, len;
1682
+ var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'],
1683
+ i, len;
1487
1684
 
1488
1685
  for (i = 0, len = events.length; i < len; i++) {
1489
- L.DomEvent.addListener(this._container, events[i], this._fireMouseEvent, this);
1686
+ L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
1490
1687
  }
1491
1688
 
1492
1689
  if (this.options.trackResize) {
1493
- L.DomEvent.addListener(window, 'resize', this._onResize, this);
1690
+ L.DomEvent.on(window, 'resize', this._onResize, this);
1494
1691
  }
1495
1692
  },
1496
1693
 
1497
1694
  _onResize: function () {
1498
- L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);
1695
+ L.Util.cancelAnimFrame(this._resizeRequest);
1696
+ this._resizeRequest = L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);
1499
1697
  },
1500
1698
 
1501
1699
  _onMouseClick: function (e) {
1502
1700
  if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
1503
1701
 
1504
- this.fire('pre' + e.type);
1702
+ this.fire('preclick');
1505
1703
  this._fireMouseEvent(e);
1506
1704
  },
1507
1705
 
@@ -1543,13 +1741,16 @@ L.Map = L.Class.extend({
1543
1741
 
1544
1742
  // private methods for getting map state
1545
1743
 
1744
+ _getMapPanePos: function () {
1745
+ return L.DomUtil.getPosition(this._mapPane);
1746
+ },
1747
+
1546
1748
  _getTopLeftPoint: function () {
1547
1749
  if (!this._loaded) {
1548
1750
  throw new Error('Set map center and zoom first.');
1549
1751
  }
1550
1752
 
1551
- var mapPanePos = L.DomUtil.getPosition(this._mapPane);
1552
- return this._initialTopLeftPoint.subtract(mapPanePos);
1753
+ return this._initialTopLeftPoint.subtract(this._getMapPanePos());
1553
1754
  },
1554
1755
 
1555
1756
  _getNewTopLeftPoint: function (center, zoom) {
@@ -1559,10 +1760,16 @@ L.Map = L.Class.extend({
1559
1760
  },
1560
1761
 
1561
1762
  _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
1562
- var mapPaneOffset = L.DomUtil.getPosition(this._mapPane),
1563
- topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(mapPaneOffset);
1763
+ var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
1764
+ return this.project(latlng, newZoom)._subtract(topLeft);
1765
+ },
1564
1766
 
1565
- return this.project(latlng, newZoom)._round()._subtract(topLeft);
1767
+ _getCenterLayerPoint: function () {
1768
+ return this.containerPointToLayerPoint(this.getSize().divideBy(2));
1769
+ },
1770
+
1771
+ _getCenterOffset: function (center) {
1772
+ return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
1566
1773
  },
1567
1774
 
1568
1775
  _limitZoom: function (zoom) {
@@ -1583,6 +1790,11 @@ L.Map.addInitHook = function (fn) {
1583
1790
  this.prototype._initializers.push(init);
1584
1791
  };
1585
1792
 
1793
+ L.map = function (id, options) {
1794
+ return new L.Map(id, options);
1795
+ };
1796
+
1797
+
1586
1798
 
1587
1799
  L.Projection.Mercator = {
1588
1800
  MAX_LATITUDE: 85.0840591556,
@@ -1666,24 +1878,26 @@ L.TileLayer = L.Class.extend({
1666
1878
  subdomains: 'abc',
1667
1879
  errorTileUrl: '',
1668
1880
  attribution: '',
1881
+ zoomOffset: 0,
1669
1882
  opacity: 1,
1670
- scheme: 'xyz',
1883
+ /* (undefined works too)
1884
+ zIndex: null,
1885
+ tms: false,
1671
1886
  continuousWorld: false,
1672
1887
  noWrap: false,
1673
- zoomOffset: 0,
1674
1888
  zoomReverse: false,
1675
1889
  detectRetina: false,
1676
-
1890
+ reuseTiles: false,
1891
+ */
1677
1892
  unloadInvisibleTiles: L.Browser.mobile,
1678
- updateWhenIdle: L.Browser.mobile,
1679
- reuseTiles: false
1893
+ updateWhenIdle: L.Browser.mobile
1680
1894
  },
1681
1895
 
1682
1896
  initialize: function (url, options) {
1683
1897
  options = L.Util.setOptions(this, options);
1684
1898
 
1685
1899
  // detecting retina displays, adjusting tileSize and zoom levels
1686
- if (options.detectRetina && window.devicePixelRatio > 1 && options.maxZoom > 0) {
1900
+ if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
1687
1901
 
1688
1902
  options.tileSize = Math.floor(options.tileSize / 2);
1689
1903
  options.zoomOffset++;
@@ -1703,9 +1917,8 @@ L.TileLayer = L.Class.extend({
1703
1917
  }
1704
1918
  },
1705
1919
 
1706
- onAdd: function (map, insertAtTheBottom) {
1920
+ onAdd: function (map) {
1707
1921
  this._map = map;
1708
- this._insertAtTheBottom = insertAtTheBottom;
1709
1922
 
1710
1923
  // create a container div for tiles
1711
1924
  this._initContainer();
@@ -1714,8 +1927,10 @@ L.TileLayer = L.Class.extend({
1714
1927
  this._createTileProto();
1715
1928
 
1716
1929
  // set up events
1717
- map.on('viewreset', this._resetCallback, this);
1718
- map.on('moveend', this._update, this);
1930
+ map.on({
1931
+ 'viewreset': this._resetCallback,
1932
+ 'moveend': this._update
1933
+ }, this);
1719
1934
 
1720
1935
  if (!this.options.updateWhenIdle) {
1721
1936
  this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
@@ -1726,11 +1941,18 @@ L.TileLayer = L.Class.extend({
1726
1941
  this._update();
1727
1942
  },
1728
1943
 
1944
+ addTo: function (map) {
1945
+ map.addLayer(this);
1946
+ return this;
1947
+ },
1948
+
1729
1949
  onRemove: function (map) {
1730
1950
  map._panes.tilePane.removeChild(this._container);
1731
1951
 
1732
- map.off('viewreset', this._resetCallback, this);
1733
- map.off('moveend', this._update, this);
1952
+ map.off({
1953
+ 'viewreset': this._resetCallback,
1954
+ 'moveend': this._update
1955
+ }, this);
1734
1956
 
1735
1957
  if (!this.options.updateWhenIdle) {
1736
1958
  map.off('move', this._limitedUpdate, this);
@@ -1741,16 +1963,25 @@ L.TileLayer = L.Class.extend({
1741
1963
  },
1742
1964
 
1743
1965
  bringToFront: function () {
1966
+ var pane = this._map._panes.tilePane;
1967
+
1744
1968
  if (this._container) {
1745
- this._map._panes.tilePane.appendChild(this._container);
1969
+ pane.appendChild(this._container);
1970
+ this._setAutoZIndex(pane, Math.max);
1746
1971
  }
1972
+
1973
+ return this;
1747
1974
  },
1748
1975
 
1749
1976
  bringToBack: function () {
1750
1977
  var pane = this._map._panes.tilePane;
1978
+
1751
1979
  if (this._container) {
1752
1980
  pane.insertBefore(this._container, pane.firstChild);
1981
+ this._setAutoZIndex(pane, Math.min);
1753
1982
  }
1983
+
1984
+ return this;
1754
1985
  },
1755
1986
 
1756
1987
  getAttribution: function () {
@@ -1763,6 +1994,60 @@ L.TileLayer = L.Class.extend({
1763
1994
  if (this._map) {
1764
1995
  this._updateOpacity();
1765
1996
  }
1997
+
1998
+ return this;
1999
+ },
2000
+
2001
+ setZIndex: function (zIndex) {
2002
+ this.options.zIndex = zIndex;
2003
+ this._updateZIndex();
2004
+
2005
+ return this;
2006
+ },
2007
+
2008
+ setUrl: function (url, noRedraw) {
2009
+ this._url = url;
2010
+
2011
+ if (!noRedraw) {
2012
+ this.redraw();
2013
+ }
2014
+
2015
+ return this;
2016
+ },
2017
+
2018
+ redraw: function () {
2019
+ if (this._map) {
2020
+ this._map._panes.tilePane.empty = false;
2021
+ this._reset(true);
2022
+ this._update();
2023
+ }
2024
+ return this;
2025
+ },
2026
+
2027
+ _updateZIndex: function () {
2028
+ if (this._container && this.options.zIndex !== undefined) {
2029
+ this._container.style.zIndex = this.options.zIndex;
2030
+ }
2031
+ },
2032
+
2033
+ _setAutoZIndex: function (pane, compare) {
2034
+
2035
+ var layers = pane.getElementsByClassName('leaflet-layer'),
2036
+ edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
2037
+ zIndex;
2038
+
2039
+ for (var i = 0, len = layers.length; i < len; i++) {
2040
+
2041
+ if (layers[i] !== this._container) {
2042
+ zIndex = parseInt(layers[i].style.zIndex, 10);
2043
+
2044
+ if (!isNaN(zIndex)) {
2045
+ edgeZIndex = compare(edgeZIndex, zIndex);
2046
+ }
2047
+ }
2048
+ }
2049
+
2050
+ this._container.style.zIndex = isFinite(edgeZIndex) ? edgeZIndex + compare(1, -1) : '';
1766
2051
  },
1767
2052
 
1768
2053
  _updateOpacity: function () {
@@ -1782,17 +2067,14 @@ L.TileLayer = L.Class.extend({
1782
2067
  },
1783
2068
 
1784
2069
  _initContainer: function () {
1785
- var tilePane = this._map._panes.tilePane,
1786
- first = tilePane.firstChild;
2070
+ var tilePane = this._map._panes.tilePane;
1787
2071
 
1788
2072
  if (!this._container || tilePane.empty) {
1789
2073
  this._container = L.DomUtil.create('div', 'leaflet-layer');
1790
2074
 
1791
- if (this._insertAtTheBottom && first) {
1792
- tilePane.insertBefore(this._container, first);
1793
- } else {
1794
- tilePane.appendChild(this._container);
1795
- }
2075
+ this._updateZIndex();
2076
+
2077
+ tilePane.appendChild(this._container);
1796
2078
 
1797
2079
  if (this.options.opacity < 1) {
1798
2080
  this._updateOpacity();
@@ -1815,6 +2097,7 @@ L.TileLayer = L.Class.extend({
1815
2097
  }
1816
2098
 
1817
2099
  this._tiles = {};
2100
+ this._tilesToLoad = 0;
1818
2101
 
1819
2102
  if (this.options.reuseTiles) {
1820
2103
  this._unusedTiles = [];
@@ -1857,16 +2140,21 @@ L.TileLayer = L.Class.extend({
1857
2140
  var queue = [],
1858
2141
  center = bounds.getCenter();
1859
2142
 
1860
- var j, i;
2143
+ var j, i, point;
2144
+
1861
2145
  for (j = bounds.min.y; j <= bounds.max.y; j++) {
1862
2146
  for (i = bounds.min.x; i <= bounds.max.x; i++) {
1863
- if (!((i + ':' + j) in this._tiles)) {
1864
- queue.push(new L.Point(i, j));
2147
+ point = new L.Point(i, j);
2148
+
2149
+ if (this._tileShouldBeLoaded(point)) {
2150
+ queue.push(point);
1865
2151
  }
1866
2152
  }
1867
2153
  }
1868
2154
 
1869
- if (queue.length === 0) { return; }
2155
+ var tilesToLoad = queue.length;
2156
+
2157
+ if (tilesToLoad === 0) { return; }
1870
2158
 
1871
2159
  // load tiles in order of their distance to center
1872
2160
  queue.sort(function (a, b) {
@@ -1875,16 +2163,37 @@ L.TileLayer = L.Class.extend({
1875
2163
 
1876
2164
  var fragment = document.createDocumentFragment();
1877
2165
 
1878
- this._tilesToLoad = queue.length;
2166
+ // if its the first batch of tiles to load
2167
+ if (!this._tilesToLoad) {
2168
+ this.fire('loading');
2169
+ }
2170
+
2171
+ this._tilesToLoad += tilesToLoad;
1879
2172
 
1880
- var k, len;
1881
- for (k = 0, len = this._tilesToLoad; k < len; k++) {
1882
- this._addTile(queue[k], fragment);
2173
+ for (i = 0; i < tilesToLoad; i++) {
2174
+ this._addTile(queue[i], fragment);
1883
2175
  }
1884
2176
 
1885
2177
  this._container.appendChild(fragment);
1886
2178
  },
1887
2179
 
2180
+ _tileShouldBeLoaded: function (tilePoint) {
2181
+ if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
2182
+ return false; // already loaded
2183
+ }
2184
+
2185
+ if (!this.options.continuousWorld) {
2186
+ var limit = this._getWrapTileNum();
2187
+
2188
+ if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
2189
+ tilePoint.y < 0 || tilePoint.y >= limit) {
2190
+ return false; // exceeds world bounds
2191
+ }
2192
+ }
2193
+
2194
+ return true;
2195
+ },
2196
+
1888
2197
  _removeOtherTiles: function (bounds) {
1889
2198
  var kArr, x, y, key;
1890
2199
 
@@ -1908,58 +2217,48 @@ L.TileLayer = L.Class.extend({
1908
2217
  this.fire("tileunload", {tile: tile, url: tile.src});
1909
2218
 
1910
2219
  if (this.options.reuseTiles) {
1911
- tile.className = tile.className.replace(' leaflet-tile-loaded', '');
2220
+ L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
1912
2221
  this._unusedTiles.push(tile);
1913
2222
  } else if (tile.parentNode === this._container) {
1914
2223
  this._container.removeChild(tile);
1915
2224
  }
1916
2225
 
1917
- tile.src = L.Util.emptyImageUrl;
2226
+ if (!L.Browser.android) { //For https://github.com/CloudMade/Leaflet/issues/137
2227
+ tile.src = L.Util.emptyImageUrl;
2228
+ }
1918
2229
 
1919
2230
  delete this._tiles[key];
1920
2231
  },
1921
2232
 
1922
2233
  _addTile: function (tilePoint, container) {
1923
- var tilePos = this._getTilePos(tilePoint),
1924
- zoom = this._map.getZoom(),
1925
- key = tilePoint.x + ':' + tilePoint.y,
1926
- limit = Math.pow(2, this._getOffsetZoom(zoom));
1927
-
1928
- // wrap tile coordinates
1929
- if (!this.options.continuousWorld) {
1930
- if (!this.options.noWrap) {
1931
- tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
1932
- } else if (tilePoint.x < 0 || tilePoint.x >= limit) {
1933
- this._tilesToLoad--;
1934
- return;
1935
- }
1936
-
1937
- if (tilePoint.y < 0 || tilePoint.y >= limit) {
1938
- this._tilesToLoad--;
1939
- return;
1940
- }
1941
- }
2234
+ var tilePos = this._getTilePos(tilePoint);
1942
2235
 
1943
2236
  // get unused tile - or create a new tile
1944
2237
  var tile = this._getTile();
1945
- L.DomUtil.setPosition(tile, tilePos, true);
1946
2238
 
1947
- this._tiles[key] = tile;
2239
+ // Chrome 20 layouts much faster with top/left (Verify with timeline, frames), Safari 5.1.7, iOS 5.1.1,
2240
+ // android browser (4.0) have display issues with top/left and requires transform instead
2241
+ // (other browsers don't currently care) - see debug/hacks/jitter.html for an example
2242
+ L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);
1948
2243
 
1949
- if (this.options.scheme === 'tms') {
1950
- tilePoint.y = limit - tilePoint.y - 1;
1951
- }
2244
+ this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
1952
2245
 
1953
- this._loadTile(tile, tilePoint, zoom);
2246
+ this._loadTile(tile, tilePoint);
1954
2247
 
1955
2248
  if (tile.parentNode !== this._container) {
1956
2249
  container.appendChild(tile);
1957
2250
  }
1958
2251
  },
1959
2252
 
1960
- _getOffsetZoom: function (zoom) {
1961
- var options = this.options;
1962
- zoom = options.zoomReverse ? options.maxZoom - zoom : zoom;
2253
+ _getZoomForUrl: function () {
2254
+
2255
+ var options = this.options,
2256
+ zoom = this._map.getZoom();
2257
+
2258
+ if (options.zoomReverse) {
2259
+ zoom = options.maxZoom - zoom;
2260
+ }
2261
+
1963
2262
  return zoom + options.zoomOffset;
1964
2263
  },
1965
2264
 
@@ -1972,19 +2271,41 @@ L.TileLayer = L.Class.extend({
1972
2271
 
1973
2272
  // image-specific code (override to implement e.g. Canvas or SVG tile layer)
1974
2273
 
1975
- getTileUrl: function (tilePoint, zoom) {
1976
- var subdomains = this.options.subdomains,
1977
- index = (tilePoint.x + tilePoint.y) % subdomains.length,
1978
- s = this.options.subdomains[index];
2274
+ getTileUrl: function (tilePoint) {
2275
+ this._adjustTilePoint(tilePoint);
1979
2276
 
1980
2277
  return L.Util.template(this._url, L.Util.extend({
1981
- s: s,
1982
- z: this._getOffsetZoom(zoom),
2278
+ s: this._getSubdomain(tilePoint),
2279
+ z: this._getZoomForUrl(),
1983
2280
  x: tilePoint.x,
1984
2281
  y: tilePoint.y
1985
2282
  }, this.options));
1986
2283
  },
1987
2284
 
2285
+ _getWrapTileNum: function () {
2286
+ // TODO refactor, limit is not valid for non-standard projections
2287
+ return Math.pow(2, this._getZoomForUrl());
2288
+ },
2289
+
2290
+ _adjustTilePoint: function (tilePoint) {
2291
+
2292
+ var limit = this._getWrapTileNum();
2293
+
2294
+ // wrap tile coordinates
2295
+ if (!this.options.continuousWorld && !this.options.noWrap) {
2296
+ tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
2297
+ }
2298
+
2299
+ if (this.options.tms) {
2300
+ tilePoint.y = limit - tilePoint.y - 1;
2301
+ }
2302
+ },
2303
+
2304
+ _getSubdomain: function (tilePoint) {
2305
+ var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
2306
+ return this.options.subdomains[index];
2307
+ },
2308
+
1988
2309
  _createTileProto: function () {
1989
2310
  var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
1990
2311
  img.galleryimg = 'no';
@@ -2013,12 +2334,12 @@ L.TileLayer = L.Class.extend({
2013
2334
  return tile;
2014
2335
  },
2015
2336
 
2016
- _loadTile: function (tile, tilePoint, zoom) {
2337
+ _loadTile: function (tile, tilePoint) {
2017
2338
  tile._layer = this;
2018
2339
  tile.onload = this._tileOnLoad;
2019
2340
  tile.onerror = this._tileOnError;
2020
2341
 
2021
- tile.src = this.getTileUrl(tilePoint, zoom);
2342
+ tile.src = this.getTileUrl(tilePoint);
2022
2343
  },
2023
2344
 
2024
2345
  _tileLoaded: function () {
@@ -2033,7 +2354,7 @@ L.TileLayer = L.Class.extend({
2033
2354
 
2034
2355
  //Only if we are loading an actual image
2035
2356
  if (this.src !== L.Util.emptyImageUrl) {
2036
- this.className += ' leaflet-tile-loaded';
2357
+ L.DomUtil.addClass(this, 'leaflet-tile-loaded');
2037
2358
 
2038
2359
  layer.fire('tileload', {
2039
2360
  tile: this,
@@ -2061,8 +2382,13 @@ L.TileLayer = L.Class.extend({
2061
2382
  }
2062
2383
  });
2063
2384
 
2385
+ L.tileLayer = function (url, options) {
2386
+ return new L.TileLayer(url, options);
2387
+ };
2388
+
2064
2389
 
2065
2390
  L.TileLayer.WMS = L.TileLayer.extend({
2391
+
2066
2392
  defaultWmsParams: {
2067
2393
  service: 'WMS',
2068
2394
  request: 'GetMap',
@@ -2074,10 +2400,16 @@ L.TileLayer.WMS = L.TileLayer.extend({
2074
2400
  },
2075
2401
 
2076
2402
  initialize: function (url, options) { // (String, Object)
2403
+
2077
2404
  this._url = url;
2078
2405
 
2079
2406
  var wmsParams = L.Util.extend({}, this.defaultWmsParams);
2080
- wmsParams.width = wmsParams.height = this.options.tileSize;
2407
+
2408
+ if (options.detectRetina && L.Browser.retina) {
2409
+ wmsParams.width = wmsParams.height = this.options.tileSize * 2;
2410
+ } else {
2411
+ wmsParams.width = wmsParams.height = this.options.tileSize;
2412
+ }
2081
2413
 
2082
2414
  for (var i in options) {
2083
2415
  // all keys that are not TileLayer options go to WMS params
@@ -2091,34 +2423,49 @@ L.TileLayer.WMS = L.TileLayer.extend({
2091
2423
  L.Util.setOptions(this, options);
2092
2424
  },
2093
2425
 
2094
- onAdd: function (map, insertAtTheBottom) {
2426
+ onAdd: function (map) {
2427
+
2095
2428
  var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
2096
2429
  this.wmsParams[projectionKey] = map.options.crs.code;
2097
2430
 
2098
- L.TileLayer.prototype.onAdd.call(this, map, insertAtTheBottom);
2431
+ L.TileLayer.prototype.onAdd.call(this, map);
2099
2432
  },
2100
2433
 
2101
2434
  getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
2435
+
2102
2436
  var map = this._map,
2103
2437
  crs = map.options.crs,
2104
-
2105
2438
  tileSize = this.options.tileSize,
2106
2439
 
2107
2440
  nwPoint = tilePoint.multiplyBy(tileSize),
2108
2441
  sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
2109
2442
 
2110
- nwMap = map.unproject(nwPoint, zoom, true),
2111
- seMap = map.unproject(sePoint, zoom, true),
2443
+ nw = crs.project(map.unproject(nwPoint, zoom)),
2444
+ se = crs.project(map.unproject(sePoint, zoom)),
2445
+
2446
+ bbox = [nw.x, se.y, se.x, nw.y].join(','),
2112
2447
 
2113
- nw = crs.project(nwMap),
2114
- se = crs.project(seMap),
2448
+ url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
2115
2449
 
2116
- bbox = [nw.x, se.y, se.x, nw.y].join(',');
2450
+ return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
2451
+ },
2452
+
2453
+ setParams: function (params, noRedraw) {
2454
+
2455
+ L.Util.extend(this.wmsParams, params);
2456
+
2457
+ if (!noRedraw) {
2458
+ this.redraw();
2459
+ }
2117
2460
 
2118
- return this._url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
2461
+ return this;
2119
2462
  }
2120
2463
  });
2121
2464
 
2465
+ L.tileLayer.wms = function (url, options) {
2466
+ return new L.TileLayer.WMS(url, options);
2467
+ };
2468
+
2122
2469
 
2123
2470
  L.TileLayer.Canvas = L.TileLayer.extend({
2124
2471
  options: {
@@ -2180,6 +2527,10 @@ L.TileLayer.Canvas = L.TileLayer.extend({
2180
2527
  });
2181
2528
 
2182
2529
 
2530
+ L.tileLayer.canvas = function (options) {
2531
+ return new L.TileLayer.Canvas(options);
2532
+ };
2533
+
2183
2534
  L.ImageOverlay = L.Class.extend({
2184
2535
  includes: L.Mixin.Events,
2185
2536
 
@@ -2187,9 +2538,9 @@ L.ImageOverlay = L.Class.extend({
2187
2538
  opacity: 1
2188
2539
  },
2189
2540
 
2190
- initialize: function (url, bounds, options) { // (String, LatLngBounds)
2541
+ initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
2191
2542
  this._url = url;
2192
- this._bounds = bounds;
2543
+ this._bounds = L.latLngBounds(bounds);
2193
2544
 
2194
2545
  L.Util.setOptions(this, options);
2195
2546
  },
@@ -2203,25 +2554,60 @@ L.ImageOverlay = L.Class.extend({
2203
2554
 
2204
2555
  map._panes.overlayPane.appendChild(this._image);
2205
2556
 
2206
- map.on('zoomanim', this._zoomAnimation, this);
2207
2557
  map.on('viewreset', this._reset, this);
2558
+
2559
+ if (map.options.zoomAnimation && L.Browser.any3d) {
2560
+ map.on('zoomanim', this._animateZoom, this);
2561
+ }
2562
+
2208
2563
  this._reset();
2209
2564
  },
2210
2565
 
2211
2566
  onRemove: function (map) {
2212
2567
  map.getPanes().overlayPane.removeChild(this._image);
2568
+
2213
2569
  map.off('viewreset', this._reset, this);
2570
+
2571
+ if (map.options.zoomAnimation) {
2572
+ map.off('zoomanim', this._animateZoom, this);
2573
+ }
2574
+ },
2575
+
2576
+ addTo: function (map) {
2577
+ map.addLayer(this);
2578
+ return this;
2579
+ },
2580
+
2581
+ setOpacity: function (opacity) {
2582
+ this.options.opacity = opacity;
2583
+ this._updateOpacity();
2584
+ return this;
2585
+ },
2586
+
2587
+ // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
2588
+ bringToFront: function () {
2589
+ if (this._image) {
2590
+ this._map._panes.overlayPane.appendChild(this._image);
2591
+ }
2592
+ return this;
2214
2593
  },
2215
2594
 
2216
- setOpacity: function (opacity) {
2217
- this.options.opacity = opacity;
2218
- this._updateOpacity();
2595
+ bringToBack: function () {
2596
+ var pane = this._map._panes.overlayPane;
2597
+ if (this._image) {
2598
+ pane.insertBefore(this._image, pane.firstChild);
2599
+ }
2600
+ return this;
2219
2601
  },
2220
2602
 
2221
2603
  _initImage: function () {
2222
- this._image = L.DomUtil.create('img', 'leaflet-image-layer leaflet-zoom-animated');
2604
+ this._image = L.DomUtil.create('img', 'leaflet-image-layer');
2223
2605
 
2224
- this._image.style.visibility = 'hidden';
2606
+ if (this._map.options.zoomAnimation && L.Browser.any3d) {
2607
+ L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
2608
+ } else {
2609
+ L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
2610
+ }
2225
2611
 
2226
2612
  this._updateOpacity();
2227
2613
 
@@ -2235,14 +2621,18 @@ L.ImageOverlay = L.Class.extend({
2235
2621
  });
2236
2622
  },
2237
2623
 
2238
- _zoomAnimation: function (opt) {
2239
- var image = this._image,
2240
- scale = Math.pow(2, opt.zoom - this._map._zoom),
2241
- topLeft = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), opt.zoom, opt.center),
2242
- size = this._map._latLngToNewLayerPoint(this._bounds.getSouthEast(), opt.zoom, opt.center).subtract(topLeft),
2243
- currentSize = this._map.latLngToLayerPoint(this._bounds.getSouthEast()).subtract(this._map.latLngToLayerPoint(this._bounds.getNorthWest()));
2624
+ _animateZoom: function (e) {
2625
+ var map = this._map,
2626
+ image = this._image,
2627
+ scale = map.getZoomScale(e.zoom),
2628
+ nw = this._bounds.getNorthWest(),
2629
+ se = this._bounds.getSouthEast(),
2630
+ topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
2631
+ size = map._latLngToNewLayerPoint(se, e.zoom, e.center).subtract(topLeft),
2632
+ currentSize = map.latLngToLayerPoint(se).subtract(map.latLngToLayerPoint(nw)),
2633
+ origin = topLeft.add(size.subtract(currentSize).divideBy(2));
2244
2634
 
2245
- image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(topLeft.add(size.subtract(currentSize).divideBy(2))) + ' scale(' + scale + ') ';
2635
+ image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
2246
2636
  },
2247
2637
 
2248
2638
  _reset: function () {
@@ -2257,7 +2647,6 @@ L.ImageOverlay = L.Class.extend({
2257
2647
  },
2258
2648
 
2259
2649
  _onImageLoad: function () {
2260
- this._image.style.visibility = '';
2261
2650
  this.fire('load');
2262
2651
  },
2263
2652
 
@@ -2266,6 +2655,10 @@ L.ImageOverlay = L.Class.extend({
2266
2655
  }
2267
2656
  });
2268
2657
 
2658
+ L.imageOverlay = function (url, bounds, options) {
2659
+ return new L.ImageOverlay(url, bounds, options);
2660
+ };
2661
+
2269
2662
 
2270
2663
  L.Icon = L.Class.extend({
2271
2664
  options: {
@@ -2276,6 +2669,7 @@ L.Icon = L.Class.extend({
2276
2669
  popupAnchor: (Point) (if not specified, popup opens in the anchor point)
2277
2670
  shadowUrl: (Point) (no shadow by default)
2278
2671
  shadowSize: (Point)
2672
+ shadowAnchor: (Point)
2279
2673
  */
2280
2674
  className: ''
2281
2675
  },
@@ -2295,7 +2689,12 @@ L.Icon = L.Class.extend({
2295
2689
  _createIcon: function (name) {
2296
2690
  var src = this._getIconUrl(name);
2297
2691
 
2298
- if (!src) { return null; }
2692
+ if (!src) {
2693
+ if (name === 'icon') {
2694
+ throw new Error("iconUrl not set in Icon options (see the docs).");
2695
+ }
2696
+ return null;
2697
+ }
2299
2698
 
2300
2699
  var img = this._createImg(src);
2301
2700
  this._setIconStyles(img, name);
@@ -2305,18 +2704,20 @@ L.Icon = L.Class.extend({
2305
2704
 
2306
2705
  _setIconStyles: function (img, name) {
2307
2706
  var options = this.options,
2308
- size = options[name + 'Size'],
2309
- anchor = options.iconAnchor;
2707
+ size = L.point(options[name + 'Size']),
2708
+ anchor;
2310
2709
 
2311
- if (!anchor && size) {
2312
- anchor = size.divideBy(2, true);
2710
+ if (name === 'shadow') {
2711
+ anchor = L.point(options.shadowAnchor || options.iconAnchor);
2712
+ } else {
2713
+ anchor = L.point(options.iconAnchor);
2313
2714
  }
2314
2715
 
2315
- if (name === 'shadow' && anchor && options.shadowOffset) {
2316
- anchor._add(options.shadowOffset);
2716
+ if (!anchor && size) {
2717
+ anchor = size.divideBy(2, true);
2317
2718
  }
2318
2719
 
2319
- img.className = 'leaflet-marker-' + name + ' ' + options.className + ' leaflet-zoom-animated';
2720
+ img.className = 'leaflet-marker-' + name + ' ' + options.className;
2320
2721
 
2321
2722
  if (anchor) {
2322
2723
  img.style.marginLeft = (-anchor.x) + 'px';
@@ -2347,25 +2748,35 @@ L.Icon = L.Class.extend({
2347
2748
  }
2348
2749
  });
2349
2750
 
2751
+ L.icon = function (options) {
2752
+ return new L.Icon(options);
2753
+ };
2754
+
2350
2755
 
2351
- // TODO move to a separate file
2352
2756
 
2353
2757
  L.Icon.Default = L.Icon.extend({
2758
+
2354
2759
  options: {
2355
2760
  iconSize: new L.Point(25, 41),
2356
2761
  iconAnchor: new L.Point(13, 41),
2357
- popupAnchor: new L.Point(0, -33),
2762
+ popupAnchor: new L.Point(1, -34),
2358
2763
 
2359
2764
  shadowSize: new L.Point(41, 41)
2360
2765
  },
2361
2766
 
2362
2767
  _getIconUrl: function (name) {
2363
- var path = L.Icon.Default.imagePath;
2364
- if (!path) {
2365
- throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
2768
+ var key = name + 'Url';
2769
+
2770
+ if (this.options[key]) {
2771
+ return this.options[key];
2366
2772
  }
2367
2773
 
2368
- return path + '/marker-' + name + '.png';
2774
+ if (name == 'shadow') {
2775
+ return "<%= asset_path('marker-shadow.png') %>";
2776
+ }
2777
+ else {
2778
+ return "<%= asset_path('marker-icon.png') %>";
2779
+ }
2369
2780
  }
2370
2781
  });
2371
2782
 
@@ -2385,6 +2796,7 @@ L.Icon.Default.imagePath = (function () {
2385
2796
  }
2386
2797
  }());
2387
2798
 
2799
+
2388
2800
  /*
2389
2801
  * L.Marker is used to display clickable/draggable icons on the map.
2390
2802
  */
@@ -2404,7 +2816,7 @@ L.Marker = L.Class.extend({
2404
2816
 
2405
2817
  initialize: function (latlng, options) {
2406
2818
  L.Util.setOptions(this, options);
2407
- this._latlng = latlng;
2819
+ this._latlng = L.latLng(latlng);
2408
2820
  },
2409
2821
 
2410
2822
  onAdd: function (map) {
@@ -2412,12 +2824,17 @@ L.Marker = L.Class.extend({
2412
2824
 
2413
2825
  map.on('viewreset', this.update, this);
2414
2826
 
2827
+ this._initIcon();
2828
+ this.update();
2829
+
2415
2830
  if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
2416
- map.on('zoomanim', this._zoomAnimation, this);
2831
+ map.on('zoomanim', this._animateZoom, this);
2417
2832
  }
2833
+ },
2418
2834
 
2419
- this._initIcon();
2420
- this.update();
2835
+ addTo: function (map) {
2836
+ map.addLayer(this);
2837
+ return this;
2421
2838
  },
2422
2839
 
2423
2840
  onRemove: function (map) {
@@ -2428,8 +2845,10 @@ L.Marker = L.Class.extend({
2428
2845
  this.closePopup();
2429
2846
  }
2430
2847
 
2431
- map.off('viewreset', this.update, this)
2432
- .off('zoomanim', this._zoomAnimation, this);
2848
+ map.off({
2849
+ 'viewreset': this.update,
2850
+ 'zoomanim': this._animateZoom
2851
+ }, this);
2433
2852
 
2434
2853
  this._map = null;
2435
2854
  },
@@ -2439,7 +2858,7 @@ L.Marker = L.Class.extend({
2439
2858
  },
2440
2859
 
2441
2860
  setLatLng: function (latlng) {
2442
- this._latlng = latlng;
2861
+ this._latlng = L.latLng(latlng);
2443
2862
 
2444
2863
  this.update();
2445
2864
 
@@ -2474,7 +2893,11 @@ L.Marker = L.Class.extend({
2474
2893
  },
2475
2894
 
2476
2895
  _initIcon: function () {
2477
- var options = this.options;
2896
+ var options = this.options,
2897
+ map = this._map,
2898
+ animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
2899
+ classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
2900
+ needOpacityUpdate = false;
2478
2901
 
2479
2902
  if (!this._icon) {
2480
2903
  this._icon = options.icon.createIcon();
@@ -2484,10 +2907,21 @@ L.Marker = L.Class.extend({
2484
2907
  }
2485
2908
 
2486
2909
  this._initInteraction();
2487
- this._updateOpacity();
2910
+ needOpacityUpdate = (this.options.opacity < 1);
2911
+
2912
+ L.DomUtil.addClass(this._icon, classToAdd);
2488
2913
  }
2489
2914
  if (!this._shadow) {
2490
2915
  this._shadow = options.icon.createShadow();
2916
+
2917
+ if (this._shadow) {
2918
+ L.DomUtil.addClass(this._shadow, classToAdd);
2919
+ needOpacityUpdate = (this.options.opacity < 1);
2920
+ }
2921
+ }
2922
+
2923
+ if (needOpacityUpdate) {
2924
+ this._updateOpacity();
2491
2925
  }
2492
2926
 
2493
2927
  var panes = this._map._panes;
@@ -2521,7 +2955,7 @@ L.Marker = L.Class.extend({
2521
2955
  this._icon.style.zIndex = pos.y + this.options.zIndexOffset;
2522
2956
  },
2523
2957
 
2524
- _zoomAnimation: function (opt) {
2958
+ _animateZoom: function (opt) {
2525
2959
  var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
2526
2960
 
2527
2961
  this._setPos(pos);
@@ -2535,11 +2969,11 @@ L.Marker = L.Class.extend({
2535
2969
  var icon = this._icon,
2536
2970
  events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
2537
2971
 
2538
- icon.className += ' leaflet-clickable';
2539
- L.DomEvent.addListener(icon, 'click', this._onMouseClick, this);
2972
+ L.DomUtil.addClass(icon, 'leaflet-clickable');
2973
+ L.DomEvent.on(icon, 'click', this._onMouseClick, this);
2540
2974
 
2541
2975
  for (var i = 0; i < events.length; i++) {
2542
- L.DomEvent.addListener(icon, events[i], this._fireMouseEvent, this);
2976
+ L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
2543
2977
  }
2544
2978
 
2545
2979
  if (L.Handler.MarkerDrag) {
@@ -2576,11 +3010,18 @@ L.Marker = L.Class.extend({
2576
3010
  }
2577
3011
  },
2578
3012
 
2579
- _updateOpacity: function (opacity) {
3013
+ _updateOpacity: function () {
2580
3014
  L.DomUtil.setOpacity(this._icon, this.options.opacity);
3015
+ if (this._shadow) {
3016
+ L.DomUtil.setOpacity(this._shadow, this.options.opacity);
3017
+ }
2581
3018
  }
2582
3019
  });
2583
3020
 
3021
+ L.marker = function (latlng, options) {
3022
+ return new L.Marker(latlng, options);
3023
+ };
3024
+
2584
3025
 
2585
3026
  L.DivIcon = L.Icon.extend({
2586
3027
  options: {
@@ -2588,12 +3029,25 @@ L.DivIcon = L.Icon.extend({
2588
3029
  /*
2589
3030
  iconAnchor: (Point)
2590
3031
  popupAnchor: (Point)
3032
+ html: (String)
3033
+ bgPos: (Point)
2591
3034
  */
2592
3035
  className: 'leaflet-div-icon'
2593
3036
  },
2594
3037
 
2595
3038
  createIcon: function () {
2596
- var div = document.createElement('div');
3039
+ var div = document.createElement('div'),
3040
+ options = this.options;
3041
+
3042
+ if (options.html) {
3043
+ div.innerHTML = options.html;
3044
+ }
3045
+
3046
+ if (options.bgPos) {
3047
+ div.style.backgroundPosition =
3048
+ (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
3049
+ }
3050
+
2597
3051
  this._setIconStyles(div, 'icon');
2598
3052
  return div;
2599
3053
  },
@@ -2603,6 +3057,10 @@ L.DivIcon = L.Icon.extend({
2603
3057
  }
2604
3058
  });
2605
3059
 
3060
+ L.divIcon = function (options) {
3061
+ return new L.DivIcon(options);
3062
+ };
3063
+
2606
3064
 
2607
3065
 
2608
3066
  L.Map.mergeOptions({
@@ -2618,7 +3076,7 @@ L.Popup = L.Class.extend({
2618
3076
  maxHeight: null,
2619
3077
  autoPan: true,
2620
3078
  closeButton: true,
2621
- offset: new L.Point(0, 2),
3079
+ offset: new L.Point(0, 6),
2622
3080
  autoPanPadding: new L.Point(5, 5),
2623
3081
  className: ''
2624
3082
  },
@@ -2637,7 +3095,11 @@ L.Popup = L.Class.extend({
2637
3095
  }
2638
3096
  this._updateContent();
2639
3097
 
2640
- this._container.style.opacity = '0';
3098
+ var animFade = map.options.fadeAnimation;
3099
+
3100
+ if (animFade) {
3101
+ L.DomUtil.setOpacity(this._container, 0);
3102
+ }
2641
3103
  map._panes.popupPane.appendChild(this._container);
2642
3104
 
2643
3105
  map.on('viewreset', this._updatePosition, this);
@@ -2652,25 +3114,41 @@ L.Popup = L.Class.extend({
2652
3114
 
2653
3115
  this._update();
2654
3116
 
2655
- this._container.style.opacity = '1'; //TODO fix ugly opacity hack
3117
+ if (animFade) {
3118
+ L.DomUtil.setOpacity(this._container, 1);
3119
+ }
3120
+ },
3121
+
3122
+ addTo: function (map) {
3123
+ map.addLayer(this);
3124
+ return this;
3125
+ },
3126
+
3127
+ openOn: function (map) {
3128
+ map.openPopup(this);
3129
+ return this;
2656
3130
  },
2657
3131
 
2658
3132
  onRemove: function (map) {
2659
3133
  map._panes.popupPane.removeChild(this._container);
2660
3134
 
2661
- L.Util.falseFn(this._container.offsetWidth);
3135
+ L.Util.falseFn(this._container.offsetWidth); // force reflow
2662
3136
 
2663
- map.off('viewreset', this._updatePosition, this)
2664
- .off('preclick', this._close, this)
2665
- .off('zoomanim', this._zoomAnimation, this);
3137
+ map.off({
3138
+ viewreset: this._updatePosition,
3139
+ preclick: this._close,
3140
+ zoomanim: this._zoomAnimation
3141
+ }, this);
2666
3142
 
2667
- this._container.style.opacity = '0';
3143
+ if (map.options.fadeAnimation) {
3144
+ L.DomUtil.setOpacity(this._container, 0);
3145
+ }
2668
3146
 
2669
3147
  this._map = null;
2670
3148
  },
2671
3149
 
2672
3150
  setLatLng: function (latlng) {
2673
- this._latlng = latlng;
3151
+ this._latlng = L.latLng(latlng);
2674
3152
  this._update();
2675
3153
  return this;
2676
3154
  },
@@ -2701,15 +3179,16 @@ L.Popup = L.Class.extend({
2701
3179
  if (this.options.closeButton) {
2702
3180
  closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
2703
3181
  closeButton.href = '#close';
3182
+ closeButton.innerHTML = '&#215;';
2704
3183
 
2705
- L.DomEvent.addListener(closeButton, 'click', this._onCloseButtonClick, this);
3184
+ L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
2706
3185
  }
2707
3186
 
2708
3187
  var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
2709
3188
  L.DomEvent.disableClickPropagation(wrapper);
2710
3189
 
2711
3190
  this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
2712
- L.DomEvent.addListener(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
3191
+ L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
2713
3192
 
2714
3193
  this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
2715
3194
  this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
@@ -2744,29 +3223,30 @@ L.Popup = L.Class.extend({
2744
3223
  },
2745
3224
 
2746
3225
  _updateLayout: function () {
2747
- var container = this._contentNode;
3226
+ var container = this._contentNode,
3227
+ style = container.style;
2748
3228
 
2749
- container.style.width = '';
2750
- container.style.whiteSpace = 'nowrap';
3229
+ style.width = '';
3230
+ style.whiteSpace = 'nowrap';
2751
3231
 
2752
3232
  var width = container.offsetWidth;
2753
3233
  width = Math.min(width, this.options.maxWidth);
2754
3234
  width = Math.max(width, this.options.minWidth);
2755
3235
 
2756
- container.style.width = (width + 1) + 'px';
2757
- container.style.whiteSpace = '';
3236
+ style.width = (width + 1) + 'px';
3237
+ style.whiteSpace = '';
2758
3238
 
2759
- container.style.height = '';
3239
+ style.height = '';
2760
3240
 
2761
3241
  var height = container.offsetHeight,
2762
3242
  maxHeight = this.options.maxHeight,
2763
- scrolledClass = ' leaflet-popup-scrolled';
3243
+ scrolledClass = 'leaflet-popup-scrolled';
2764
3244
 
2765
3245
  if (maxHeight && height > maxHeight) {
2766
- container.style.height = maxHeight + 'px';
2767
- container.className += scrolledClass;
3246
+ style.height = maxHeight + 'px';
3247
+ L.DomUtil.addClass(container, scrolledClass);
2768
3248
  } else {
2769
- container.className = container.className.replace(scrolledClass, '');
3249
+ L.DomUtil.removeClass(container, scrolledClass);
2770
3250
  }
2771
3251
 
2772
3252
  this._containerWidth = this._container.offsetWidth;
@@ -2790,7 +3270,7 @@ L.Popup = L.Class.extend({
2790
3270
  },
2791
3271
 
2792
3272
  _zoomAnimation: function (opt) {
2793
- var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center)._round();
3273
+ var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
2794
3274
 
2795
3275
  L.DomUtil.setPosition(this._container, pos);
2796
3276
  },
@@ -2838,6 +3318,10 @@ L.Popup = L.Class.extend({
2838
3318
  }
2839
3319
  });
2840
3320
 
3321
+ L.popup = function (options, source) {
3322
+ return new L.Popup(options, source);
3323
+ };
3324
+
2841
3325
 
2842
3326
  /*
2843
3327
  * Popup extension to L.Marker, adding openPopup & bindPopup methods.
@@ -2861,7 +3345,9 @@ L.Marker.include({
2861
3345
  },
2862
3346
 
2863
3347
  bindPopup: function (content, options) {
2864
- var anchor = this.options.icon.options.popupAnchor || new L.Point(0, 0);
3348
+ var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
3349
+
3350
+ anchor = anchor.add(L.Popup.prototype.options.offset);
2865
3351
 
2866
3352
  if (options && options.offset) {
2867
3353
  anchor = anchor.add(options.offset);
@@ -2951,7 +3437,7 @@ L.LayerGroup = L.Class.extend({
2951
3437
  },
2952
3438
 
2953
3439
  clearLayers: function () {
2954
- this._iterateLayers(this.removeLayer, this);
3440
+ this.eachLayer(this.removeLayer, this);
2955
3441
  return this;
2956
3442
  },
2957
3443
 
@@ -2974,15 +3460,20 @@ L.LayerGroup = L.Class.extend({
2974
3460
 
2975
3461
  onAdd: function (map) {
2976
3462
  this._map = map;
2977
- this._iterateLayers(map.addLayer, map);
3463
+ this.eachLayer(map.addLayer, map);
2978
3464
  },
2979
3465
 
2980
3466
  onRemove: function (map) {
2981
- this._iterateLayers(map.removeLayer, map);
3467
+ this.eachLayer(map.removeLayer, map);
2982
3468
  this._map = null;
2983
3469
  },
2984
3470
 
2985
- _iterateLayers: function (method, context) {
3471
+ addTo: function (map) {
3472
+ map.addLayer(this);
3473
+ return this;
3474
+ },
3475
+
3476
+ eachLayer: function (method, context) {
2986
3477
  for (var i in this._layers) {
2987
3478
  if (this._layers.hasOwnProperty(i)) {
2988
3479
  method.call(context, this._layers[i]);
@@ -2991,6 +3482,10 @@ L.LayerGroup = L.Class.extend({
2991
3482
  }
2992
3483
  });
2993
3484
 
3485
+ L.layerGroup = function (layers) {
3486
+ return new L.LayerGroup(layers);
3487
+ };
3488
+
2994
3489
 
2995
3490
  /*
2996
3491
  * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
@@ -3000,13 +3495,31 @@ L.FeatureGroup = L.LayerGroup.extend({
3000
3495
  includes: L.Mixin.Events,
3001
3496
 
3002
3497
  addLayer: function (layer) {
3003
- this._initEvents(layer);
3498
+ if (this._layers[L.Util.stamp(layer)]) {
3499
+ return this;
3500
+ }
3501
+
3502
+ layer.on('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
3004
3503
 
3005
3504
  L.LayerGroup.prototype.addLayer.call(this, layer);
3006
3505
 
3007
3506
  if (this._popupContent && layer.bindPopup) {
3008
3507
  layer.bindPopup(this._popupContent);
3009
3508
  }
3509
+
3510
+ return this;
3511
+ },
3512
+
3513
+ removeLayer: function (layer) {
3514
+ layer.off('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
3515
+
3516
+ L.LayerGroup.prototype.removeLayer.call(this, layer);
3517
+
3518
+ if (this._popupContent) {
3519
+ return this.invoke('unbindPopup');
3520
+ } else {
3521
+ return this;
3522
+ }
3010
3523
  },
3011
3524
 
3012
3525
  bindPopup: function (content) {
@@ -3020,21 +3533,12 @@ L.FeatureGroup = L.LayerGroup.extend({
3020
3533
 
3021
3534
  getBounds: function () {
3022
3535
  var bounds = new L.LatLngBounds();
3023
- this._iterateLayers(function (layer) {
3536
+ this.eachLayer(function (layer) {
3024
3537
  bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
3025
3538
  }, this);
3026
3539
  return bounds;
3027
3540
  },
3028
3541
 
3029
- _initEvents: function (layer) {
3030
- var events = ['click', 'dblclick', 'mouseover', 'mouseout'],
3031
- i, len;
3032
-
3033
- for (i = 0, len = events.length; i < len; i++) {
3034
- layer.on(events[i], this._propagateEvent, this);
3035
- }
3036
- },
3037
-
3038
3542
  _propagateEvent: function (e) {
3039
3543
  e.layer = e.target;
3040
3544
  e.target = this;
@@ -3043,6 +3547,10 @@ L.FeatureGroup = L.LayerGroup.extend({
3043
3547
  }
3044
3548
  });
3045
3549
 
3550
+ L.featureGroup = function (layers) {
3551
+ return new L.FeatureGroup(layers);
3552
+ };
3553
+
3046
3554
 
3047
3555
  /*
3048
3556
  * L.Path is a base class for rendering vector paths on a map. It's inherited by Polyline, Circle, etc.
@@ -3054,12 +3562,17 @@ L.Path = L.Class.extend({
3054
3562
  statics: {
3055
3563
  // how much to extend the clip area around the map view
3056
3564
  // (relative to its size, e.g. 0.5 is half the screen in each direction)
3057
- CLIP_PADDING: 0.5
3565
+ // set in such way that SVG element doesn't exceed 1280px (vector layers flicker on dragend if it is)
3566
+ CLIP_PADDING: L.Browser.mobile ?
3567
+ Math.max(0, Math.min(0.5,
3568
+ (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2))
3569
+ : 0.5
3058
3570
  },
3059
3571
 
3060
3572
  options: {
3061
3573
  stroke: true,
3062
3574
  color: '#0033ff',
3575
+ dashArray: null,
3063
3576
  weight: 5,
3064
3577
  opacity: 0.5,
3065
3578
 
@@ -3077,24 +3590,44 @@ L.Path = L.Class.extend({
3077
3590
  onAdd: function (map) {
3078
3591
  this._map = map;
3079
3592
 
3080
- this._initElements();
3081
- this._initEvents();
3593
+ if (!this._container) {
3594
+ this._initElements();
3595
+ this._initEvents();
3596
+ }
3597
+
3082
3598
  this.projectLatlngs();
3083
3599
  this._updatePath();
3084
3600
 
3085
- map
3086
- .on('viewreset', this.projectLatlngs, this)
3087
- .on('moveend', this._updatePath, this);
3601
+ if (this._container) {
3602
+ this._map._pathRoot.appendChild(this._container);
3603
+ }
3604
+
3605
+ map.on({
3606
+ 'viewreset': this.projectLatlngs,
3607
+ 'moveend': this._updatePath
3608
+ }, this);
3609
+ },
3610
+
3611
+ addTo: function (map) {
3612
+ map.addLayer(this);
3613
+ return this;
3088
3614
  },
3089
3615
 
3090
3616
  onRemove: function (map) {
3617
+ map._pathRoot.removeChild(this._container);
3618
+
3091
3619
  this._map = null;
3092
3620
 
3093
- map._pathRoot.removeChild(this._container);
3621
+ if (L.Browser.vml) {
3622
+ this._container = null;
3623
+ this._stroke = null;
3624
+ this._fill = null;
3625
+ }
3094
3626
 
3095
- map
3096
- .off('viewreset', this.projectLatlngs, this)
3097
- .off('moveend', this._updatePath, this);
3627
+ map.off({
3628
+ 'viewreset': this.projectLatlngs,
3629
+ 'moveend': this._updatePath
3630
+ }, this);
3098
3631
  },
3099
3632
 
3100
3633
  projectLatlngs: function () {
@@ -3174,8 +3707,6 @@ L.Path = L.Path.extend({
3174
3707
 
3175
3708
  this._path = this._createElement('path');
3176
3709
  this._container.appendChild(this._path);
3177
-
3178
- this._map._pathRoot.appendChild(this._container);
3179
3710
  },
3180
3711
 
3181
3712
  _initStyle: function () {
@@ -3194,6 +3725,11 @@ L.Path = L.Path.extend({
3194
3725
  this._path.setAttribute('stroke', this.options.color);
3195
3726
  this._path.setAttribute('stroke-opacity', this.options.opacity);
3196
3727
  this._path.setAttribute('stroke-width', this.options.weight);
3728
+ if (this.options.dashArray) {
3729
+ this._path.setAttribute('stroke-dasharray', this.options.dashArray);
3730
+ } else {
3731
+ this._path.removeAttribute('stroke-dasharray');
3732
+ }
3197
3733
  } else {
3198
3734
  this._path.setAttribute('stroke', 'none');
3199
3735
  }
@@ -3221,11 +3757,11 @@ L.Path = L.Path.extend({
3221
3757
  this._path.setAttribute('class', 'leaflet-clickable');
3222
3758
  }
3223
3759
 
3224
- L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
3760
+ L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
3225
3761
 
3226
3762
  var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'mousemove', 'contextmenu'];
3227
3763
  for (var i = 0; i < events.length; i++) {
3228
- L.DomEvent.addListener(this._container, events[i], this._fireMouseEvent, this);
3764
+ L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
3229
3765
  }
3230
3766
  }
3231
3767
  },
@@ -3235,17 +3771,20 @@ L.Path = L.Path.extend({
3235
3771
  return;
3236
3772
  }
3237
3773
 
3238
- if (e.type === 'contextmenu') {
3239
- L.DomEvent.preventDefault(e);
3240
- }
3241
-
3242
3774
  this._fireMouseEvent(e);
3775
+
3776
+ L.DomEvent.stopPropagation(e);
3243
3777
  },
3244
3778
 
3245
3779
  _fireMouseEvent: function (e) {
3246
3780
  if (!this.hasEventListeners(e.type)) {
3247
3781
  return;
3248
3782
  }
3783
+
3784
+ if (e.type === 'contextmenu') {
3785
+ L.DomEvent.preventDefault(e);
3786
+ }
3787
+
3249
3788
  var map = this._map,
3250
3789
  containerPoint = map.mouseEventToContainerPoint(e),
3251
3790
  layerPoint = map.containerPointToLayerPoint(containerPoint),
@@ -3257,8 +3796,6 @@ L.Path = L.Path.extend({
3257
3796
  containerPoint: containerPoint,
3258
3797
  originalEvent: e
3259
3798
  });
3260
-
3261
- L.DomEvent.stopPropagation(e);
3262
3799
  }
3263
3800
  });
3264
3801
 
@@ -3268,10 +3805,15 @@ L.Map.include({
3268
3805
  this._pathRoot = L.Path.prototype._createElement('svg');
3269
3806
  this._panes.overlayPane.appendChild(this._pathRoot);
3270
3807
 
3271
- if (this.options.zoomAnimation) {
3808
+ if (this.options.zoomAnimation && L.Browser.any3d) {
3272
3809
  this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
3273
- this.on('zoomanim', this._animatePathZoom);
3274
- this.on('zoomend', this._endPathZoom);
3810
+
3811
+ this.on({
3812
+ 'zoomanim': this._animatePathZoom,
3813
+ 'zoomend': this._endPathZoom
3814
+ });
3815
+ } else {
3816
+ this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
3275
3817
  }
3276
3818
 
3277
3819
  this.on('moveend', this._updateSvgViewport);
@@ -3280,15 +3822,12 @@ L.Map.include({
3280
3822
  },
3281
3823
 
3282
3824
  _animatePathZoom: function (opt) {
3283
- // TODO refactor into something more manageable
3284
- var centerOffset = this._getNewTopLeftPoint(opt.center).subtract(this._getTopLeftPoint()),
3285
- scale = Math.pow(2, opt.zoom - this._zoom),
3286
- offset = centerOffset.divideBy(1 - 1 / scale),
3287
- centerPoint = this.containerPointToLayerPoint(this.getSize().divideBy(-2)),
3288
- origin = centerPoint.add(offset).round(),
3289
- pathRootStyle = this._pathRoot.style;
3825
+ var scale = this.getZoomScale(opt.zoom),
3826
+ offset = this._getCenterOffset(opt.center).divideBy(1 - 1 / scale),
3827
+ viewportPos = this.containerPointToLayerPoint(this.getSize().multiplyBy(-L.Path.CLIP_PADDING)),
3828
+ origin = viewportPos.add(offset).round();
3290
3829
 
3291
- pathRootStyle[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString((origin.multiplyBy(-1).add(L.DomUtil.getPosition(this._pathRoot)).multiplyBy(scale).add(origin))) + ' scale(' + scale + ') ';
3830
+ this._pathRoot.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString((origin.multiplyBy(-1).add(L.DomUtil.getPosition(this._pathRoot)).multiplyBy(scale).add(origin))) + ' scale(' + scale + ') ';
3292
3831
 
3293
3832
  this._pathZooming = true;
3294
3833
  },
@@ -3299,9 +3838,9 @@ L.Map.include({
3299
3838
 
3300
3839
  _updateSvgViewport: function () {
3301
3840
  if (this._pathZooming) {
3302
- //Do not update SVGs while a zoom animation is going on otherwise the animation will break.
3303
- //When the zoom animation ends we will be updated again anyway
3304
- //This fixes the case where you do a momentum move and zoom while the move is still ongoing.
3841
+ // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
3842
+ // When the zoom animation ends we will be updated again anyway
3843
+ // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
3305
3844
  return;
3306
3845
  }
3307
3846
 
@@ -3316,7 +3855,6 @@ L.Map.include({
3316
3855
  pane = this._panes.overlayPane;
3317
3856
 
3318
3857
  // Hack to make flicker on drag end on mobile webkit less irritating
3319
- // Unfortunately I haven't found a good workaround for this yet
3320
3858
  if (L.Browser.mobileWebkit) {
3321
3859
  pane.removeChild(root);
3322
3860
  }
@@ -3338,10 +3876,13 @@ L.Map.include({
3338
3876
  */
3339
3877
 
3340
3878
  L.Path.include({
3879
+
3341
3880
  bindPopup: function (content, options) {
3881
+
3342
3882
  if (!this._popup || this._popup.options !== options) {
3343
3883
  this._popup = new L.Popup(options, this);
3344
3884
  }
3885
+
3345
3886
  this._popup.setContent(content);
3346
3887
 
3347
3888
  if (!this._openPopupAdded) {
@@ -3352,6 +3893,18 @@ L.Path.include({
3352
3893
  return this;
3353
3894
  },
3354
3895
 
3896
+ openPopup: function (latlng) {
3897
+
3898
+ if (this._popup) {
3899
+ latlng = latlng || this._latlng ||
3900
+ this._latlngs[Math.floor(this._latlngs.length / 2)];
3901
+
3902
+ this._openPopup({latlng: latlng});
3903
+ }
3904
+
3905
+ return this;
3906
+ },
3907
+
3355
3908
  _openPopup: function (e) {
3356
3909
  this._popup.setLatLng(e.latlng);
3357
3910
  this._map.openPopup(this._popup);
@@ -3365,13 +3918,17 @@ L.Path.include({
3365
3918
  */
3366
3919
 
3367
3920
  L.Browser.vml = (function () {
3368
- var div = document.createElement('div');
3369
- div.innerHTML = '<v:shape adj="1"/>';
3921
+ try {
3922
+ var div = document.createElement('div');
3923
+ div.innerHTML = '<v:shape adj="1"/>';
3370
3924
 
3371
- var shape = div.firstChild;
3372
- shape.style.behavior = 'url(#default#VML)';
3925
+ var shape = div.firstChild;
3926
+ shape.style.behavior = 'url(#default#VML)';
3373
3927
 
3374
- return shape && (typeof shape.adj === 'object');
3928
+ return shape && (typeof shape.adj === 'object');
3929
+ } catch (e) {
3930
+ return false;
3931
+ }
3375
3932
  }());
3376
3933
 
3377
3934
  L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
@@ -3395,8 +3952,10 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
3395
3952
 
3396
3953
  _initPath: function () {
3397
3954
  var container = this._container = this._createElement('shape');
3398
- container.className += ' leaflet-vml-shape' +
3399
- (this.options.clickable ? ' leaflet-clickable' : '');
3955
+ L.DomUtil.addClass(container, 'leaflet-vml-shape');
3956
+ if (this.options.clickable) {
3957
+ L.DomUtil.addClass(container, 'leaflet-clickable');
3958
+ }
3400
3959
  container.coordsize = '1 1';
3401
3960
 
3402
3961
  this._path = this._createElement('path');
@@ -3427,6 +3986,11 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
3427
3986
  stroke.weight = options.weight + 'px';
3428
3987
  stroke.color = options.color;
3429
3988
  stroke.opacity = options.opacity;
3989
+ if (options.dashArray) {
3990
+ stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
3991
+ } else {
3992
+ stroke.dashStyle = '';
3993
+ }
3430
3994
  } else if (stroke) {
3431
3995
  container.removeChild(stroke);
3432
3996
  this._stroke = null;
@@ -3483,6 +4047,45 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
3483
4047
  SVG: false
3484
4048
  },
3485
4049
 
4050
+ redraw: function () {
4051
+ if (this._map) {
4052
+ this.projectLatlngs();
4053
+ this._requestUpdate();
4054
+ }
4055
+ return this;
4056
+ },
4057
+
4058
+ setStyle: function (style) {
4059
+ L.Util.setOptions(this, style);
4060
+
4061
+ if (this._map) {
4062
+ this._updateStyle();
4063
+ this._requestUpdate();
4064
+ }
4065
+ return this;
4066
+ },
4067
+
4068
+ onRemove: function (map) {
4069
+ map
4070
+ .off('viewreset', this.projectLatlngs, this)
4071
+ .off('moveend', this._updatePath, this);
4072
+
4073
+ this._requestUpdate();
4074
+
4075
+ this._map = null;
4076
+ },
4077
+
4078
+ _requestUpdate: function () {
4079
+ if (this._map) {
4080
+ L.Util.cancelAnimFrame(this._fireMapMoveEnd);
4081
+ this._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
4082
+ }
4083
+ },
4084
+
4085
+ _fireMapMoveEnd: function () {
4086
+ this.fire('moveend');
4087
+ },
4088
+
3486
4089
  _initElements: function () {
3487
4090
  this._map._initPathRoot();
3488
4091
  this._ctx = this._map._canvasCtx;
@@ -3564,14 +4167,7 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
3564
4167
  if (this._containsPoint(e.layerPoint)) {
3565
4168
  this.fire('click', e);
3566
4169
  }
3567
- },
3568
-
3569
- onRemove: function (map) {
3570
- map
3571
- .off('viewreset', this._projectLatlngs, this)
3572
- .off('moveend', this._updatePath, this)
3573
- .fire('moveend');
3574
- }
4170
+ }
3575
4171
  });
3576
4172
 
3577
4173
  L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
@@ -3659,7 +4255,7 @@ L.LineUtil = {
3659
4255
  _simplifyDP: function (points, sqTolerance) {
3660
4256
 
3661
4257
  var len = points.length,
3662
- ArrayConstructor = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
4258
+ ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
3663
4259
  markers = new ArrayConstructor(len);
3664
4260
 
3665
4261
  markers[0] = markers[len - 1] = 1;
@@ -3830,7 +4426,8 @@ L.LineUtil = {
3830
4426
  L.Polyline = L.Path.extend({
3831
4427
  initialize: function (latlngs, options) {
3832
4428
  L.Path.prototype.initialize.call(this, options);
3833
- this._latlngs = latlngs;
4429
+
4430
+ this._latlngs = this._convertLatLngs(latlngs);
3834
4431
 
3835
4432
  // TODO refactor: move to Polyline.Edit.js
3836
4433
  if (L.Handler.PolyEdit) {
@@ -3869,17 +4466,18 @@ L.Polyline = L.Path.extend({
3869
4466
  },
3870
4467
 
3871
4468
  setLatLngs: function (latlngs) {
3872
- this._latlngs = latlngs;
4469
+ this._latlngs = this._convertLatLngs(latlngs);
3873
4470
  return this.redraw();
3874
4471
  },
3875
4472
 
3876
4473
  addLatLng: function (latlng) {
3877
- this._latlngs.push(latlng);
4474
+ this._latlngs.push(L.latLng(latlng));
3878
4475
  return this.redraw();
3879
4476
  },
3880
4477
 
3881
4478
  spliceLatLngs: function (index, howMany) {
3882
4479
  var removed = [].splice.apply(this._latlngs, arguments);
4480
+ this._convertLatLngs(this._latlngs);
3883
4481
  this.redraw();
3884
4482
  return removed;
3885
4483
  },
@@ -3931,6 +4529,17 @@ L.Polyline = L.Path.extend({
3931
4529
  L.Path.prototype.onRemove.call(this, map);
3932
4530
  },
3933
4531
 
4532
+ _convertLatLngs: function (latlngs) {
4533
+ var i, len;
4534
+ for (i = 0, len = latlngs.length; i < len; i++) {
4535
+ if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
4536
+ return;
4537
+ }
4538
+ latlngs[i] = L.latLng(latlngs[i]);
4539
+ }
4540
+ return latlngs;
4541
+ },
4542
+
3934
4543
  _initEvents: function () {
3935
4544
  L.Path.prototype._initEvents.call(this);
3936
4545
  },
@@ -3992,6 +4601,8 @@ L.Polyline = L.Path.extend({
3992
4601
  },
3993
4602
 
3994
4603
  _updatePath: function () {
4604
+ if (!this._map) { return; }
4605
+
3995
4606
  this._clipPoints();
3996
4607
  this._simplifyPoints();
3997
4608
 
@@ -3999,6 +4610,10 @@ L.Polyline = L.Path.extend({
3999
4610
  }
4000
4611
  });
4001
4612
 
4613
+ L.polyline = function (latlngs, options) {
4614
+ return new L.Polyline(latlngs, options);
4615
+ };
4616
+
4002
4617
 
4003
4618
  /*
4004
4619
  * L.PolyUtil contains utilify functions for polygons (clipping, etc.).
@@ -4073,8 +4688,8 @@ L.Polygon = L.Polyline.extend({
4073
4688
  initialize: function (latlngs, options) {
4074
4689
  L.Polyline.prototype.initialize.call(this, latlngs, options);
4075
4690
 
4076
- if (latlngs && (latlngs[0] instanceof Array)) {
4077
- this._latlngs = latlngs[0];
4691
+ if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
4692
+ this._latlngs = this._convertLatLngs(latlngs[0]);
4078
4693
  this._holes = latlngs.slice(1);
4079
4694
  }
4080
4695
  },
@@ -4126,6 +4741,10 @@ L.Polygon = L.Polyline.extend({
4126
4741
  }
4127
4742
  });
4128
4743
 
4744
+ L.polygon = function (latlngs, options) {
4745
+ return new L.Polygon(latlngs, options);
4746
+ };
4747
+
4129
4748
 
4130
4749
  /*
4131
4750
  * Contains L.MultiPolyline and L.MultiPolygon layers.
@@ -4143,7 +4762,7 @@ L.Polygon = L.Polyline.extend({
4143
4762
  setLatLngs: function (latlngs) {
4144
4763
  var i = 0, len = latlngs.length;
4145
4764
 
4146
- this._iterateLayers(function (layer) {
4765
+ this.eachLayer(function (layer) {
4147
4766
  if (i < len) {
4148
4767
  layer.setLatLngs(latlngs[i++]);
4149
4768
  } else {
@@ -4162,6 +4781,14 @@ L.Polygon = L.Polyline.extend({
4162
4781
 
4163
4782
  L.MultiPolyline = createMulti(L.Polyline);
4164
4783
  L.MultiPolygon = createMulti(L.Polygon);
4784
+
4785
+ L.multiPolyline = function (latlngs, options) {
4786
+ return new L.MultiPolyline(latlngs, options);
4787
+ };
4788
+
4789
+ L.multiPolygon = function (latlngs, options) {
4790
+ return new L.MultiPolygon(latlngs, options);
4791
+ };
4165
4792
  }());
4166
4793
 
4167
4794
 
@@ -4179,6 +4806,7 @@ L.Rectangle = L.Polygon.extend({
4179
4806
  },
4180
4807
 
4181
4808
  _boundsToLatLngs: function (latLngBounds) {
4809
+ latLngBounds = L.latLngBounds(latLngBounds);
4182
4810
  return [
4183
4811
  latLngBounds.getSouthWest(),
4184
4812
  latLngBounds.getNorthWest(),
@@ -4189,6 +4817,10 @@ L.Rectangle = L.Polygon.extend({
4189
4817
  }
4190
4818
  });
4191
4819
 
4820
+ L.rectangle = function (latLngBounds, options) {
4821
+ return new L.Rectangle(latLngBounds, options);
4822
+ };
4823
+
4192
4824
 
4193
4825
  /*
4194
4826
  * L.Circle is a circle overlay (with a certain radius in meters).
@@ -4198,7 +4830,7 @@ L.Circle = L.Path.extend({
4198
4830
  initialize: function (latlng, radius, options) {
4199
4831
  L.Path.prototype.initialize.call(this, options);
4200
4832
 
4201
- this._latlng = latlng;
4833
+ this._latlng = L.latLng(latlng);
4202
4834
  this._mRadius = radius;
4203
4835
  },
4204
4836
 
@@ -4207,7 +4839,7 @@ L.Circle = L.Path.extend({
4207
4839
  },
4208
4840
 
4209
4841
  setLatLng: function (latlng) {
4210
- this._latlng = latlng;
4842
+ this._latlng = L.latLng(latlng);
4211
4843
  return this.redraw();
4212
4844
  },
4213
4845
 
@@ -4231,9 +4863,8 @@ L.Circle = L.Path.extend({
4231
4863
  point = map.project(this._latlng),
4232
4864
  swPoint = new L.Point(point.x - delta, point.y + delta),
4233
4865
  nePoint = new L.Point(point.x + delta, point.y - delta),
4234
- zoom = map.getZoom(),
4235
- sw = map.unproject(swPoint, zoom, true),
4236
- ne = map.unproject(nePoint, zoom, true);
4866
+ sw = map.unproject(swPoint),
4867
+ ne = map.unproject(nePoint);
4237
4868
 
4238
4869
  return new L.LatLngBounds(sw, ne);
4239
4870
  },
@@ -4285,6 +4916,10 @@ L.Circle = L.Path.extend({
4285
4916
  }
4286
4917
  });
4287
4918
 
4919
+ L.circle = function (latlng, radius, options) {
4920
+ return new L.Circle(latlng, radius, options);
4921
+ };
4922
+
4288
4923
 
4289
4924
  /*
4290
4925
  * L.CircleMarker is a circle overlay with a permanent pixel radius.
@@ -4311,6 +4946,10 @@ L.CircleMarker = L.Circle.extend({
4311
4946
  }
4312
4947
  });
4313
4948
 
4949
+ L.circleMarker = function (latlng, options) {
4950
+ return new L.CircleMarker(latlng, options);
4951
+ };
4952
+
4314
4953
 
4315
4954
 
4316
4955
  L.Polyline.include(!L.Path.CANVAS ? {} : {
@@ -4401,57 +5040,64 @@ L.GeoJSON = L.FeatureGroup.extend({
4401
5040
  initialize: function (geojson, options) {
4402
5041
  L.Util.setOptions(this, options);
4403
5042
 
4404
- this._geojson = geojson;
4405
5043
  this._layers = {};
4406
5044
 
4407
5045
  if (geojson) {
4408
- this.addGeoJSON(geojson);
5046
+ this.addData(geojson);
4409
5047
  }
4410
5048
  },
4411
5049
 
4412
- addGeoJSON: function (geojson) {
4413
- var features = geojson.features,
5050
+ addData: function (geojson) {
5051
+ var features = geojson instanceof Array ? geojson : geojson.features,
4414
5052
  i, len;
4415
5053
 
4416
5054
  if (features) {
4417
5055
  for (i = 0, len = features.length; i < len; i++) {
4418
- this.addGeoJSON(features[i]);
5056
+ this.addData(features[i]);
4419
5057
  }
4420
- return;
5058
+ return this;
4421
5059
  }
4422
5060
 
4423
- var isFeature = (geojson.type === 'Feature'),
4424
- geometry = isFeature ? geojson.geometry : geojson,
4425
- layer = L.GeoJSON.geometryToLayer(geometry, this.options.pointToLayer);
5061
+ var options = this.options,
5062
+ style = options.style;
4426
5063
 
4427
- this.fire('featureparse', {
4428
- layer: layer,
4429
- properties: geojson.properties,
4430
- geometryType: geometry.type,
4431
- bbox: geojson.bbox,
4432
- id: geojson.id,
4433
- geometry: geojson.geometry
4434
- });
5064
+ if (options.filter && !options.filter(geojson)) { return; }
5065
+
5066
+ var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
5067
+
5068
+ if (style) {
5069
+ if (typeof style === 'function') {
5070
+ style = style(geojson);
5071
+ }
5072
+ if (layer.setStyle) {
5073
+ layer.setStyle(style);
5074
+ }
5075
+ }
5076
+
5077
+ if (options.onEachFeature) {
5078
+ options.onEachFeature(geojson, layer);
5079
+ }
4435
5080
 
4436
- this.addLayer(layer);
5081
+ return this.addLayer(layer);
4437
5082
  }
4438
5083
  });
4439
5084
 
4440
5085
  L.Util.extend(L.GeoJSON, {
4441
- geometryToLayer: function (geometry, pointToLayer) {
4442
- var coords = geometry.coordinates,
5086
+ geometryToLayer: function (geojson, pointToLayer) {
5087
+ var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
5088
+ coords = geometry.coordinates,
4443
5089
  layers = [],
4444
5090
  latlng, latlngs, i, len, layer;
4445
5091
 
4446
5092
  switch (geometry.type) {
4447
5093
  case 'Point':
4448
5094
  latlng = this.coordsToLatLng(coords);
4449
- return pointToLayer ? pointToLayer(latlng) : new L.Marker(latlng);
5095
+ return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
4450
5096
 
4451
5097
  case 'MultiPoint':
4452
5098
  for (i = 0, len = coords.length; i < len; i++) {
4453
5099
  latlng = this.coordsToLatLng(coords[i]);
4454
- layer = pointToLayer ? pointToLayer(latlng) : new L.Marker(latlng);
5100
+ layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
4455
5101
  layers.push(layer);
4456
5102
  }
4457
5103
  return new L.FeatureGroup(layers);
@@ -4500,6 +5146,7 @@ L.Util.extend(L.GeoJSON, {
4500
5146
  latlng = levelsDeep ?
4501
5147
  this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
4502
5148
  this.coordsToLatLng(coords[i], reverse);
5149
+
4503
5150
  latlngs.push(latlng);
4504
5151
  }
4505
5152
 
@@ -4507,6 +5154,9 @@ L.Util.extend(L.GeoJSON, {
4507
5154
  }
4508
5155
  });
4509
5156
 
5157
+ L.geoJson = function (geojson, options) {
5158
+ return new L.GeoJSON(geojson, options);
5159
+ };
4510
5160
 
4511
5161
  /*
4512
5162
  * L.DomEvent contains functions for working with DOM events.
@@ -4514,37 +5164,43 @@ L.Util.extend(L.GeoJSON, {
4514
5164
 
4515
5165
  L.DomEvent = {
4516
5166
  /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
4517
- addListener: function (/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn, /*Object*/ context) {
5167
+ addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
5168
+
4518
5169
  var id = L.Util.stamp(fn),
4519
- key = '_leaflet_' + type + id;
5170
+ key = '_leaflet_' + type + id,
5171
+ handler, originalHandler, newType;
4520
5172
 
4521
- if (obj[key]) {
4522
- return this;
4523
- }
5173
+ if (obj[key]) { return this; }
4524
5174
 
4525
- var handler = function (e) {
5175
+ handler = function (e) {
4526
5176
  return fn.call(context || obj, e || L.DomEvent._getEvent());
4527
5177
  };
4528
5178
 
4529
5179
  if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
4530
- this.addDoubleTapListener(obj, handler, id);
5180
+ return this.addDoubleTapListener(obj, handler, id);
5181
+
4531
5182
  } else if ('addEventListener' in obj) {
5183
+
4532
5184
  if (type === 'mousewheel') {
4533
5185
  obj.addEventListener('DOMMouseScroll', handler, false);
4534
5186
  obj.addEventListener(type, handler, false);
5187
+
4535
5188
  } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4536
- var originalHandler = handler,
4537
- newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
5189
+
5190
+ originalHandler = handler;
5191
+ newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
5192
+
4538
5193
  handler = function (e) {
4539
- if (!L.DomEvent._checkMouse(obj, e)) {
4540
- return;
4541
- }
5194
+ if (!L.DomEvent._checkMouse(obj, e)) { return; }
4542
5195
  return originalHandler(e);
4543
5196
  };
5197
+
4544
5198
  obj.addEventListener(newType, handler, false);
5199
+
4545
5200
  } else {
4546
5201
  obj.addEventListener(type, handler, false);
4547
5202
  }
5203
+
4548
5204
  } else if ('attachEvent' in obj) {
4549
5205
  obj.attachEvent("on" + type, handler);
4550
5206
  }
@@ -4554,21 +5210,23 @@ L.DomEvent = {
4554
5210
  return this;
4555
5211
  },
4556
5212
 
4557
- removeListener: function (/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn) {
5213
+ removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
5214
+
4558
5215
  var id = L.Util.stamp(fn),
4559
5216
  key = '_leaflet_' + type + id,
4560
5217
  handler = obj[key];
4561
5218
 
4562
- if (!handler) {
4563
- return;
4564
- }
5219
+ if (!handler) { return; }
4565
5220
 
4566
5221
  if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
4567
5222
  this.removeDoubleTapListener(obj, id);
5223
+
4568
5224
  } else if ('removeEventListener' in obj) {
5225
+
4569
5226
  if (type === 'mousewheel') {
4570
5227
  obj.removeEventListener('DOMMouseScroll', handler, false);
4571
5228
  obj.removeEventListener(type, handler, false);
5229
+
4572
5230
  } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4573
5231
  obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
4574
5232
  } else {
@@ -4577,47 +5235,14 @@ L.DomEvent = {
4577
5235
  } else if ('detachEvent' in obj) {
4578
5236
  obj.detachEvent("on" + type, handler);
4579
5237
  }
5238
+
4580
5239
  obj[key] = null;
4581
5240
 
4582
5241
  return this;
4583
5242
  },
4584
5243
 
4585
- _checkMouse: function (el, e) {
4586
- var related = e.relatedTarget;
4587
-
4588
- if (!related) {
4589
- return true;
4590
- }
4591
-
4592
- try {
4593
- while (related && (related !== el)) {
4594
- related = related.parentNode;
4595
- }
4596
- } catch (err) {
4597
- return false;
4598
- }
4599
-
4600
- return (related !== el);
4601
- },
4602
-
4603
- /*jshint noarg:false */ // evil magic for IE
4604
- _getEvent: function () {
4605
- var e = window.event;
4606
- if (!e) {
4607
- var caller = arguments.callee.caller;
4608
- while (caller) {
4609
- e = caller['arguments'][0];
4610
- if (e && window.Event === e.constructor) {
4611
- break;
4612
- }
4613
- caller = caller.caller;
4614
- }
4615
- }
4616
- return e;
4617
- },
4618
- /*jshint noarg:false */
5244
+ stopPropagation: function (e) {
4619
5245
 
4620
- stopPropagation: function (/*Event*/ e) {
4621
5246
  if (e.stopPropagation) {
4622
5247
  e.stopPropagation();
4623
5248
  } else {
@@ -4626,14 +5251,18 @@ L.DomEvent = {
4626
5251
  return this;
4627
5252
  },
4628
5253
 
4629
- disableClickPropagation: function (/*HTMLElement*/ el) {
5254
+ disableClickPropagation: function (el) {
5255
+
5256
+ var stop = L.DomEvent.stopPropagation;
5257
+
4630
5258
  return L.DomEvent
4631
- .addListener(el, L.Draggable.START, L.DomEvent.stopPropagation)
4632
- .addListener(el, 'click', L.DomEvent.stopPropagation)
4633
- .addListener(el, 'dblclick', L.DomEvent.stopPropagation);
5259
+ .addListener(el, L.Draggable.START, stop)
5260
+ .addListener(el, 'click', stop)
5261
+ .addListener(el, 'dblclick', stop);
4634
5262
  },
4635
5263
 
4636
- preventDefault: function (/*Event*/ e) {
5264
+ preventDefault: function (e) {
5265
+
4637
5266
  if (e.preventDefault) {
4638
5267
  e.preventDefault();
4639
5268
  } else {
@@ -4643,24 +5272,24 @@ L.DomEvent = {
4643
5272
  },
4644
5273
 
4645
5274
  stop: function (e) {
4646
- return L.DomEvent
4647
- .preventDefault(e)
4648
- .stopPropagation(e);
5275
+ return L.DomEvent.preventDefault(e).stopPropagation(e);
4649
5276
  },
4650
5277
 
4651
5278
  getMousePosition: function (e, container) {
4652
- var x = e.pageX ? e.pageX : e.clientX +
4653
- document.body.scrollLeft + document.documentElement.scrollLeft,
4654
- y = e.pageY ? e.pageY : e.clientY +
4655
- document.body.scrollTop + document.documentElement.scrollTop,
5279
+
5280
+ var body = document.body,
5281
+ docEl = document.documentElement,
5282
+ x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
5283
+ y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
4656
5284
  pos = new L.Point(x, y);
4657
5285
 
4658
- return (container ?
4659
- pos.subtract(L.DomUtil.getViewportOffset(container)) : pos);
5286
+ return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
4660
5287
  },
4661
5288
 
4662
5289
  getWheelDelta: function (e) {
5290
+
4663
5291
  var delta = 0;
5292
+
4664
5293
  if (e.wheelDelta) {
4665
5294
  delta = e.wheelDelta / 120;
4666
5295
  }
@@ -4668,10 +5297,46 @@ L.DomEvent = {
4668
5297
  delta = -e.detail / 3;
4669
5298
  }
4670
5299
  return delta;
5300
+ },
5301
+
5302
+ // check if element really left/entered the event target (for mouseenter/mouseleave)
5303
+ _checkMouse: function (el, e) {
5304
+
5305
+ var related = e.relatedTarget;
5306
+
5307
+ if (!related) { return true; }
5308
+
5309
+ try {
5310
+ while (related && (related !== el)) {
5311
+ related = related.parentNode;
5312
+ }
5313
+ } catch (err) {
5314
+ return false;
5315
+ }
5316
+ return (related !== el);
5317
+ },
5318
+
5319
+ /*jshint noarg:false */
5320
+ _getEvent: function () { // evil magic for IE
5321
+
5322
+ var e = window.event;
5323
+ if (!e) {
5324
+ var caller = arguments.callee.caller;
5325
+ while (caller) {
5326
+ e = caller['arguments'][0];
5327
+ if (e && window.Event === e.constructor) {
5328
+ break;
5329
+ }
5330
+ caller = caller.caller;
5331
+ }
5332
+ }
5333
+ return e;
4671
5334
  }
5335
+ /*jshint noarg:false */
4672
5336
  };
4673
5337
 
4674
-
5338
+ L.DomEvent.on = L.DomEvent.addListener;
5339
+ L.DomEvent.off = L.DomEvent.removeListener;
4675
5340
 
4676
5341
  /*
4677
5342
  * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
@@ -4696,7 +5361,7 @@ L.Draggable = L.Class.extend({
4696
5361
  if (this._enabled) {
4697
5362
  return;
4698
5363
  }
4699
- L.DomEvent.addListener(this._dragStartTarget, L.Draggable.START, this._onDown, this);
5364
+ L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
4700
5365
  this._enabled = true;
4701
5366
  },
4702
5367
 
@@ -4704,7 +5369,7 @@ L.Draggable = L.Class.extend({
4704
5369
  if (!this._enabled) {
4705
5370
  return;
4706
5371
  }
4707
- L.DomEvent.removeListener(this._dragStartTarget, L.Draggable.START, this._onDown);
5372
+ L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
4708
5373
  this._enabled = false;
4709
5374
  this._moved = false;
4710
5375
  },
@@ -4727,7 +5392,7 @@ L.Draggable = L.Class.extend({
4727
5392
  L.DomEvent.preventDefault(e);
4728
5393
 
4729
5394
  if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
4730
- el.className += ' leaflet-active';
5395
+ L.DomUtil.addClass(el, 'leaflet-active');
4731
5396
  }
4732
5397
 
4733
5398
  this._moved = false;
@@ -4743,8 +5408,8 @@ L.Draggable = L.Class.extend({
4743
5408
  this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
4744
5409
  this._startPoint = new L.Point(first.clientX, first.clientY);
4745
5410
 
4746
- L.DomEvent.addListener(document, L.Draggable.MOVE, this._onMove, this);
4747
- L.DomEvent.addListener(document, L.Draggable.END, this._onUp, this);
5411
+ L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
5412
+ L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
4748
5413
  },
4749
5414
 
4750
5415
  _onMove: function (e) {
@@ -4783,7 +5448,7 @@ L.Draggable = L.Class.extend({
4783
5448
  dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
4784
5449
 
4785
5450
  if (el.tagName.toLowerCase() === 'a') {
4786
- el.className = el.className.replace(' leaflet-active', '');
5451
+ L.DomUtil.removeClass(el, 'leaflet-active');
4787
5452
  }
4788
5453
 
4789
5454
  if (dist < L.Draggable.TAP_TOLERANCE) {
@@ -4796,8 +5461,8 @@ L.Draggable = L.Class.extend({
4796
5461
  this._restoreCursor();
4797
5462
  }
4798
5463
 
4799
- L.DomEvent.removeListener(document, L.Draggable.MOVE, this._onMove);
4800
- L.DomEvent.removeListener(document, L.Draggable.END, this._onUp);
5464
+ L.DomEvent.off(document, L.Draggable.MOVE, this._onMove);
5465
+ L.DomEvent.off(document, L.Draggable.END, this._onUp);
4801
5466
 
4802
5467
  if (this._moved) {
4803
5468
  // ensure drag is not fired after dragend
@@ -4809,11 +5474,11 @@ L.Draggable = L.Class.extend({
4809
5474
  },
4810
5475
 
4811
5476
  _setMovingCursor: function () {
4812
- document.body.className += ' leaflet-dragging';
5477
+ L.DomUtil.addClass(document.body, 'leaflet-dragging');
4813
5478
  },
4814
5479
 
4815
5480
  _restoreCursor: function () {
4816
- document.body.className = document.body.className.replace(/ leaflet-dragging/g, '');
5481
+ L.DomUtil.removeClass(document.body, 'leaflet-dragging');
4817
5482
  },
4818
5483
 
4819
5484
  _simulateEvent: function (type, e) {
@@ -4840,17 +5505,15 @@ L.Handler = L.Class.extend({
4840
5505
  },
4841
5506
 
4842
5507
  enable: function () {
4843
- if (this._enabled) {
4844
- return;
4845
- }
5508
+ if (this._enabled) { return; }
5509
+
4846
5510
  this._enabled = true;
4847
5511
  this.addHooks();
4848
5512
  },
4849
5513
 
4850
5514
  disable: function () {
4851
- if (!this._enabled) {
4852
- return;
4853
- }
5515
+ if (!this._enabled) { return; }
5516
+
4854
5517
  this._enabled = false;
4855
5518
  this.removeHooks();
4856
5519
  },
@@ -4868,14 +5531,13 @@ L.Handler = L.Class.extend({
4868
5531
  L.Map.mergeOptions({
4869
5532
  dragging: true,
4870
5533
 
4871
- inertia: !L.Browser.android,
4872
- inertiaDeceleration: L.Browser.touch ? 3000 : 2000, // px/s^2
4873
- inertiaMaxSpeed: L.Browser.touch ? 1500 : 1000, // px/s
4874
- inertiaThreshold: L.Browser.touch ? 32 : 16, // ms
5534
+ inertia: !L.Browser.android23,
5535
+ inertiaDeceleration: 3000, // px/s^2
5536
+ inertiaMaxSpeed: 1500, // px/s
5537
+ inertiaThreshold: L.Browser.touch ? 32 : 14, // ms
4875
5538
 
4876
5539
  // TODO refactor, move to CRS
4877
- worldCopyJump: true,
4878
- continuousWorld: false
5540
+ worldCopyJump: true
4879
5541
  });
4880
5542
 
4881
5543
  L.Map.Drag = L.Handler.extend({
@@ -4883,14 +5545,15 @@ L.Map.Drag = L.Handler.extend({
4883
5545
  if (!this._draggable) {
4884
5546
  this._draggable = new L.Draggable(this._map._mapPane, this._map._container);
4885
5547
 
4886
- this._draggable
4887
- .on('dragstart', this._onDragStart, this)
4888
- .on('drag', this._onDrag, this)
4889
- .on('dragend', this._onDragEnd, this);
5548
+ this._draggable.on({
5549
+ 'dragstart': this._onDragStart,
5550
+ 'drag': this._onDrag,
5551
+ 'dragend': this._onDragEnd
5552
+ }, this);
4890
5553
 
4891
5554
  var options = this._map.options;
4892
5555
 
4893
- if (options.worldCopyJump && !options.continuousWorld) {
5556
+ if (options.worldCopyJump) {
4894
5557
  this._draggable.on('predrag', this._onPreDrag, this);
4895
5558
  this._map.on('viewreset', this._onViewReset, this);
4896
5559
  }
@@ -4946,14 +5609,16 @@ L.Map.Drag = L.Handler.extend({
4946
5609
  var pxCenter = this._map.getSize().divideBy(2),
4947
5610
  pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
4948
5611
 
4949
- this._initialWorldOffset = pxWorldCenter.subtract(pxCenter);
5612
+ this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
5613
+ this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
4950
5614
  },
4951
5615
 
4952
5616
  _onPreDrag: function () {
5617
+ // TODO refactor to be able to adjust map pane position after zoom
4953
5618
  var map = this._map,
4954
- worldWidth = map.options.crs.scale(map.getZoom()),
5619
+ worldWidth = this._worldWidth,
4955
5620
  halfWidth = Math.round(worldWidth / 2),
4956
- dx = this._initialWorldOffset.x,
5621
+ dx = this._initialWorldOffset,
4957
5622
  x = this._draggable._newPos.x,
4958
5623
  newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
4959
5624
  newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
@@ -4969,7 +5634,7 @@ L.Map.Drag = L.Handler.extend({
4969
5634
 
4970
5635
  noInertia = !options.inertia ||
4971
5636
  delay > options.inertiaThreshold ||
4972
- typeof this._positions[0] === 'undefined';
5637
+ this._positions[0] === undefined;
4973
5638
 
4974
5639
  if (noInertia) {
4975
5640
  map.fire('moveend');
@@ -5013,6 +5678,7 @@ L.Map.Drag = L.Handler.extend({
5013
5678
 
5014
5679
  L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
5015
5680
 
5681
+
5016
5682
  /*
5017
5683
  * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
5018
5684
  */
@@ -5047,12 +5713,12 @@ L.Map.mergeOptions({
5047
5713
 
5048
5714
  L.Map.ScrollWheelZoom = L.Handler.extend({
5049
5715
  addHooks: function () {
5050
- L.DomEvent.addListener(this._map._container, 'mousewheel', this._onWheelScroll, this);
5716
+ L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
5051
5717
  this._delta = 0;
5052
5718
  },
5053
5719
 
5054
5720
  removeHooks: function () {
5055
- L.DomEvent.removeListener(this._map._container, 'mousewheel', this._onWheelScroll);
5721
+ L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
5056
5722
  },
5057
5723
 
5058
5724
  _onWheelScroll: function (e) {
@@ -5062,7 +5728,7 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
5062
5728
  this._lastMousePos = this._map.mouseEventToContainerPoint(e);
5063
5729
 
5064
5730
  clearTimeout(this._timer);
5065
- this._timer = setTimeout(L.Util.bind(this._performZoom, this), 50);
5731
+ this._timer = setTimeout(L.Util.bind(this._performZoom, this), 40);
5066
5732
 
5067
5733
  L.DomEvent.preventDefault(e);
5068
5734
  },
@@ -5079,25 +5745,26 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
5079
5745
 
5080
5746
  if (!delta) { return; }
5081
5747
 
5082
- var newCenter = this._getCenterForScrollWheelZoom(this._lastMousePos, delta),
5083
- newZoom = zoom + delta;
5748
+ var newZoom = zoom + delta,
5749
+ newCenter = this._getCenterForScrollWheelZoom(this._lastMousePos, newZoom);
5084
5750
 
5085
5751
  map.setView(newCenter, newZoom);
5086
5752
  },
5087
5753
 
5088
- _getCenterForScrollWheelZoom: function (mousePos, delta) {
5754
+ _getCenterForScrollWheelZoom: function (mousePos, newZoom) {
5089
5755
  var map = this._map,
5090
- centerPoint = map.getPixelBounds().getCenter(),
5756
+ scale = map.getZoomScale(newZoom),
5091
5757
  viewHalf = map.getSize().divideBy(2),
5092
- centerOffset = mousePos.subtract(viewHalf).multiplyBy(1 - Math.pow(2, -delta)),
5093
- newCenterPoint = centerPoint.add(centerOffset);
5758
+ centerOffset = mousePos.subtract(viewHalf).multiplyBy(1 - 1 / scale),
5759
+ newCenterPoint = map._getTopLeftPoint().add(viewHalf).add(centerOffset);
5094
5760
 
5095
- return map.unproject(newCenterPoint, map._zoom, true);
5761
+ return map.unproject(newCenterPoint);
5096
5762
  }
5097
5763
  });
5098
5764
 
5099
5765
  L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
5100
5766
 
5767
+
5101
5768
  L.Util.extend(L.DomEvent, {
5102
5769
  // inspired by Zepto touch code by Thomas Fuchs
5103
5770
  addDoubleTapListener: function (obj, handler, id) {
@@ -5133,12 +5800,14 @@ L.Util.extend(L.DomEvent, {
5133
5800
 
5134
5801
  obj.addEventListener(touchstart, onTouchStart, false);
5135
5802
  obj.addEventListener(touchend, onTouchEnd, false);
5803
+ return this;
5136
5804
  },
5137
5805
 
5138
5806
  removeDoubleTapListener: function (obj, id) {
5139
5807
  var pre = '_leaflet_';
5140
5808
  obj.removeEventListener(obj, obj[pre + 'touchstart' + id], false);
5141
5809
  obj.removeEventListener(obj, obj[pre + 'touchend' + id], false);
5810
+ return this;
5142
5811
  }
5143
5812
  });
5144
5813
 
@@ -5148,16 +5817,16 @@ L.Util.extend(L.DomEvent, {
5148
5817
  */
5149
5818
 
5150
5819
  L.Map.mergeOptions({
5151
- touchZoom: L.Browser.touch && !L.Browser.android
5820
+ touchZoom: L.Browser.touch && !L.Browser.android23
5152
5821
  });
5153
5822
 
5154
5823
  L.Map.TouchZoom = L.Handler.extend({
5155
5824
  addHooks: function () {
5156
- L.DomEvent.addListener(this._map._container, 'touchstart', this._onTouchStart, this);
5825
+ L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
5157
5826
  },
5158
5827
 
5159
5828
  removeHooks: function () {
5160
- L.DomEvent.removeListener(this._map._container, 'touchstart', this._onTouchStart, this);
5829
+ L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
5161
5830
  },
5162
5831
 
5163
5832
  _onTouchStart: function (e) {
@@ -5167,7 +5836,7 @@ L.Map.TouchZoom = L.Handler.extend({
5167
5836
 
5168
5837
  var p1 = map.mouseEventToLayerPoint(e.touches[0]),
5169
5838
  p2 = map.mouseEventToLayerPoint(e.touches[1]),
5170
- viewCenter = map.containerPointToLayerPoint(map.getSize().divideBy(2));
5839
+ viewCenter = map._getCenterLayerPoint();
5171
5840
 
5172
5841
  this._startCenter = p1.add(p2).divideBy(2, true);
5173
5842
  this._startDist = p1.distanceTo(p2);
@@ -5178,8 +5847,8 @@ L.Map.TouchZoom = L.Handler.extend({
5178
5847
  this._centerOffset = viewCenter.subtract(this._startCenter);
5179
5848
 
5180
5849
  L.DomEvent
5181
- .addListener(document, 'touchmove', this._onTouchMove, this)
5182
- .addListener(document, 'touchend', this._onTouchEnd, this);
5850
+ .on(document, 'touchmove', this._onTouchMove, this)
5851
+ .on(document, 'touchend', this._onTouchEnd, this);
5183
5852
 
5184
5853
  L.DomEvent.preventDefault(e);
5185
5854
  },
@@ -5197,14 +5866,8 @@ L.Map.TouchZoom = L.Handler.extend({
5197
5866
 
5198
5867
  if (this._scale === 1) { return; }
5199
5868
 
5200
- var zoom = this._map._zoom + Math.log(this._scale) / Math.LN2;
5201
-
5202
- var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale),
5203
- centerPoint = this._map.getPixelOrigin().add(this._startCenter).add(centerOffset),
5204
- center = this._map.unproject(centerPoint);
5205
-
5206
5869
  if (!this._moved) {
5207
- map._mapPane.className += ' leaflet-zoom-anim leaflet-touching';
5870
+ L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
5208
5871
 
5209
5872
  map
5210
5873
  .fire('movestart')
@@ -5214,9 +5877,20 @@ L.Map.TouchZoom = L.Handler.extend({
5214
5877
  this._moved = true;
5215
5878
  }
5216
5879
 
5880
+ L.Util.cancelAnimFrame(this._animRequest);
5881
+ this._animRequest = L.Util.requestAnimFrame(this._updateOnMove, this, true, this._map._container);
5882
+
5883
+ L.DomEvent.preventDefault(e);
5884
+ },
5885
+
5886
+ _updateOnMove: function () {
5887
+ var map = this._map,
5888
+ origin = this._getScaleOrigin(),
5889
+ center = map.layerPointToLatLng(origin);
5890
+
5217
5891
  map.fire('zoomanim', {
5218
5892
  center: center,
5219
- zoom: zoom
5893
+ zoom: map.getScaleZoom(this._scale)
5220
5894
  });
5221
5895
 
5222
5896
  // Used 2 translates instead of transform-origin because of a very strange bug -
@@ -5224,42 +5898,46 @@ L.Map.TouchZoom = L.Handler.extend({
5224
5898
 
5225
5899
  map._tileBg.style[L.DomUtil.TRANSFORM] =
5226
5900
  L.DomUtil.getTranslateString(this._delta) + ' ' +
5227
- L.DomUtil.getScaleString(this._scale, this._startCenter);
5228
-
5229
- L.DomEvent.preventDefault(e);
5901
+ L.DomUtil.getScaleString(this._scale, this._startCenter);
5230
5902
  },
5231
5903
 
5232
5904
  _onTouchEnd: function (e) {
5233
5905
  if (!this._moved || !this._zooming) { return; }
5234
5906
 
5907
+ var map = this._map;
5908
+
5235
5909
  this._zooming = false;
5236
- this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-touching', ''); //TODO toggleClass util
5910
+ L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
5237
5911
 
5238
5912
  L.DomEvent
5239
- .removeListener(document, 'touchmove', this._onTouchMove)
5240
- .removeListener(document, 'touchend', this._onTouchEnd);
5913
+ .off(document, 'touchmove', this._onTouchMove)
5914
+ .off(document, 'touchend', this._onTouchEnd);
5241
5915
 
5242
- var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale),
5243
- centerPoint = this._map.getPixelOrigin().add(this._startCenter).add(centerOffset),
5244
- center = this._map.unproject(centerPoint),
5916
+ var origin = this._getScaleOrigin(),
5917
+ center = map.layerPointToLatLng(origin),
5245
5918
 
5246
- oldZoom = this._map.getZoom(),
5247
- floatZoomDelta = Math.log(this._scale) / Math.LN2,
5919
+ oldZoom = map.getZoom(),
5920
+ floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
5248
5921
  roundZoomDelta = (floatZoomDelta > 0 ? Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
5249
- zoom = this._map._limitZoom(oldZoom + roundZoomDelta),
5250
- finalScale = Math.pow(2, zoom - oldZoom);
5922
+ zoom = map._limitZoom(oldZoom + roundZoomDelta);
5251
5923
 
5252
- this._map.fire('zoomanim', {
5924
+ map.fire('zoomanim', {
5253
5925
  center: center,
5254
5926
  zoom: zoom
5255
5927
  });
5256
5928
 
5257
- this._map._runAnimation(center, zoom, finalScale / this._scale, this._startCenter.add(centerOffset), true);
5929
+ map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
5930
+ },
5931
+
5932
+ _getScaleOrigin: function () {
5933
+ var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
5934
+ return this._startCenter.add(centerOffset);
5258
5935
  }
5259
5936
  });
5260
5937
 
5261
5938
  L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
5262
5939
 
5940
+
5263
5941
  /*
5264
5942
  * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
5265
5943
  */
@@ -5276,11 +5954,11 @@ L.Map.BoxZoom = L.Handler.extend({
5276
5954
  },
5277
5955
 
5278
5956
  addHooks: function () {
5279
- L.DomEvent.addListener(this._container, 'mousedown', this._onMouseDown, this);
5957
+ L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
5280
5958
  },
5281
5959
 
5282
5960
  removeHooks: function () {
5283
- L.DomEvent.removeListener(this._container, 'mousedown', this._onMouseDown);
5961
+ L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
5284
5962
  },
5285
5963
 
5286
5964
  _onMouseDown: function (e) {
@@ -5297,8 +5975,8 @@ L.Map.BoxZoom = L.Handler.extend({
5297
5975
  this._container.style.cursor = 'crosshair';
5298
5976
 
5299
5977
  L.DomEvent
5300
- .addListener(document, 'mousemove', this._onMouseMove, this)
5301
- .addListener(document, 'mouseup', this._onMouseUp, this)
5978
+ .on(document, 'mousemove', this._onMouseMove, this)
5979
+ .on(document, 'mouseup', this._onMouseUp, this)
5302
5980
  .preventDefault(e);
5303
5981
 
5304
5982
  this._map.fire("boxzoomstart");
@@ -5329,8 +6007,8 @@ L.Map.BoxZoom = L.Handler.extend({
5329
6007
  L.DomUtil.enableTextSelection();
5330
6008
 
5331
6009
  L.DomEvent
5332
- .removeListener(document, 'mousemove', this._onMouseMove)
5333
- .removeListener(document, 'mouseup', this._onMouseUp);
6010
+ .off(document, 'mousemove', this._onMouseMove)
6011
+ .off(document, 'mouseup', this._onMouseUp);
5334
6012
 
5335
6013
  var map = this._map,
5336
6014
  layerPoint = map.mouseEventToLayerPoint(e);
@@ -5350,6 +6028,139 @@ L.Map.BoxZoom = L.Handler.extend({
5350
6028
  L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
5351
6029
 
5352
6030
 
6031
+ L.Map.mergeOptions({
6032
+ keyboard: true,
6033
+ keyboardPanOffset: 80,
6034
+ keyboardZoomOffset: 1
6035
+ });
6036
+
6037
+ L.Map.Keyboard = L.Handler.extend({
6038
+
6039
+ // list of e.keyCode values for particular actions
6040
+ keyCodes: {
6041
+ left: [37],
6042
+ right: [39],
6043
+ down: [40],
6044
+ up: [38],
6045
+ zoomIn: [187, 61, 107],
6046
+ zoomOut: [189, 109, 0]
6047
+ },
6048
+
6049
+ initialize: function (map) {
6050
+ this._map = map;
6051
+
6052
+ this._setPanOffset(map.options.keyboardPanOffset);
6053
+ this._setZoomOffset(map.options.keyboardZoomOffset);
6054
+ },
6055
+
6056
+ addHooks: function () {
6057
+ var container = this._map._container;
6058
+
6059
+ // make the container focusable by tabbing
6060
+ if (container.tabIndex === -1) {
6061
+ container.tabIndex = "0";
6062
+ }
6063
+
6064
+ L.DomEvent
6065
+ .addListener(container, 'focus', this._onFocus, this)
6066
+ .addListener(container, 'blur', this._onBlur, this)
6067
+ .addListener(container, 'mousedown', this._onMouseDown, this);
6068
+
6069
+ this._map
6070
+ .on('focus', this._addHooks, this)
6071
+ .on('blur', this._removeHooks, this);
6072
+ },
6073
+
6074
+ removeHooks: function () {
6075
+ this._removeHooks();
6076
+
6077
+ var container = this._map._container;
6078
+ L.DomEvent
6079
+ .removeListener(container, 'focus', this._onFocus, this)
6080
+ .removeListener(container, 'blur', this._onBlur, this)
6081
+ .removeListener(container, 'mousedown', this._onMouseDown, this);
6082
+
6083
+ this._map
6084
+ .off('focus', this._addHooks, this)
6085
+ .off('blur', this._removeHooks, this);
6086
+ },
6087
+
6088
+ _onMouseDown: function () {
6089
+ if (!this._focused) {
6090
+ this._map._container.focus();
6091
+ }
6092
+ },
6093
+
6094
+ _onFocus: function () {
6095
+ this._focused = true;
6096
+ this._map.fire('focus');
6097
+ },
6098
+
6099
+ _onBlur: function () {
6100
+ this._focused = false;
6101
+ this._map.fire('blur');
6102
+ },
6103
+
6104
+ _setPanOffset: function (pan) {
6105
+ var keys = this._panKeys = {},
6106
+ codes = this.keyCodes,
6107
+ i, len;
6108
+
6109
+ for (i = 0, len = codes.left.length; i < len; i++) {
6110
+ keys[codes.left[i]] = [-1 * pan, 0];
6111
+ }
6112
+ for (i = 0, len = codes.right.length; i < len; i++) {
6113
+ keys[codes.right[i]] = [pan, 0];
6114
+ }
6115
+ for (i = 0, len = codes.down.length; i < len; i++) {
6116
+ keys[codes.down[i]] = [0, pan];
6117
+ }
6118
+ for (i = 0, len = codes.up.length; i < len; i++) {
6119
+ keys[codes.up[i]] = [0, -1 * pan];
6120
+ }
6121
+ },
6122
+
6123
+ _setZoomOffset: function (zoom) {
6124
+ var keys = this._zoomKeys = {},
6125
+ codes = this.keyCodes,
6126
+ i, len;
6127
+
6128
+ for (i = 0, len = codes.zoomIn.length; i < len; i++) {
6129
+ keys[codes.zoomIn[i]] = zoom;
6130
+ }
6131
+ for (i = 0, len = codes.zoomOut.length; i < len; i++) {
6132
+ keys[codes.zoomOut[i]] = -zoom;
6133
+ }
6134
+ },
6135
+
6136
+ _addHooks: function () {
6137
+ L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this);
6138
+ },
6139
+
6140
+ _removeHooks: function () {
6141
+ L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this);
6142
+ },
6143
+
6144
+ _onKeyDown: function (e) {
6145
+ var key = e.keyCode;
6146
+
6147
+ if (this._panKeys.hasOwnProperty(key)) {
6148
+ this._map.panBy(this._panKeys[key]);
6149
+
6150
+ } else if (this._zoomKeys.hasOwnProperty(key)) {
6151
+ this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]);
6152
+
6153
+ } else {
6154
+ return;
6155
+ }
6156
+
6157
+ L.DomEvent.stop(e);
6158
+ }
6159
+ });
6160
+
6161
+ L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
6162
+
6163
+
5353
6164
  /*
5354
6165
  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
5355
6166
  */
@@ -5542,7 +6353,7 @@ L.Handler.PolyEdit = L.Handler.extend({
5542
6353
  },
5543
6354
 
5544
6355
  _updateIndexes: function (index, delta) {
5545
- this._markerGroup._iterateLayers(function (marker) {
6356
+ this._markerGroup.eachLayer(function (marker) {
5546
6357
  if (marker._index > index) {
5547
6358
  marker._index += delta;
5548
6359
  }
@@ -5645,6 +6456,8 @@ L.Control = L.Class.extend({
5645
6456
  if (map) {
5646
6457
  map.addControl(this);
5647
6458
  }
6459
+
6460
+ return this;
5648
6461
  },
5649
6462
 
5650
6463
  addTo: function (map) {
@@ -5680,6 +6493,9 @@ L.Control = L.Class.extend({
5680
6493
  }
5681
6494
  });
5682
6495
 
6496
+ L.control = function (options) {
6497
+ return new L.Control(options);
6498
+ };
5683
6499
 
5684
6500
 
5685
6501
  L.Map.include({
@@ -5735,9 +6551,10 @@ L.Control.Zoom = L.Control.extend({
5735
6551
  link.title = title;
5736
6552
 
5737
6553
  L.DomEvent
5738
- .addListener(link, 'click', L.DomEvent.stopPropagation)
5739
- .addListener(link, 'click', L.DomEvent.preventDefault)
5740
- .addListener(link, 'click', fn, context);
6554
+ .on(link, 'click', L.DomEvent.stopPropagation)
6555
+ .on(link, 'click', L.DomEvent.preventDefault)
6556
+ .on(link, 'click', fn, context)
6557
+ .on(link, 'dblclick', L.DomEvent.stopPropagation);
5741
6558
 
5742
6559
  return link;
5743
6560
  }
@@ -5754,6 +6571,10 @@ L.Map.addInitHook(function () {
5754
6571
  }
5755
6572
  });
5756
6573
 
6574
+ L.control.zoom = function (options) {
6575
+ return new L.Control.Zoom(options);
6576
+ };
6577
+
5757
6578
  L.Control.Attribution = L.Control.extend({
5758
6579
  options: {
5759
6580
  position: 'bottomright',
@@ -5789,6 +6610,7 @@ L.Control.Attribution = L.Control.extend({
5789
6610
  setPrefix: function (prefix) {
5790
6611
  this.options.prefix = prefix;
5791
6612
  this._update();
6613
+ return this;
5792
6614
  },
5793
6615
 
5794
6616
  addAttribution: function (text) {
@@ -5800,6 +6622,8 @@ L.Control.Attribution = L.Control.extend({
5800
6622
  this._attributions[text]++;
5801
6623
 
5802
6624
  this._update();
6625
+
6626
+ return this;
5803
6627
  },
5804
6628
 
5805
6629
  removeAttribution: function (text) {
@@ -5807,6 +6631,8 @@ L.Control.Attribution = L.Control.extend({
5807
6631
 
5808
6632
  this._attributions[text]--;
5809
6633
  this._update();
6634
+
6635
+ return this;
5810
6636
  },
5811
6637
 
5812
6638
  _update: function () {
@@ -5829,7 +6655,7 @@ L.Control.Attribution = L.Control.extend({
5829
6655
  prefixAndAttribs.push(attribs.join(', '));
5830
6656
  }
5831
6657
 
5832
- this._container.innerHTML = prefixAndAttribs.join(' &mdash; ');
6658
+ this._container.innerHTML = prefixAndAttribs.join(' &#8212; ');
5833
6659
  },
5834
6660
 
5835
6661
  _onLayerAdd: function (e) {
@@ -5855,6 +6681,11 @@ L.Map.addInitHook(function () {
5855
6681
  }
5856
6682
  });
5857
6683
 
6684
+ L.control.attribution = function (options) {
6685
+ return new L.Control.Attribution(options);
6686
+ };
6687
+
6688
+
5858
6689
  L.Control.Scale = L.Control.extend({
5859
6690
  options: {
5860
6691
  position: 'bottomleft',
@@ -5871,12 +6702,7 @@ L.Control.Scale = L.Control.extend({
5871
6702
  container = L.DomUtil.create('div', className),
5872
6703
  options = this.options;
5873
6704
 
5874
- if (options.metric) {
5875
- this._mScale = L.DomUtil.create('div', className + '-line', container);
5876
- }
5877
- if (options.imperial) {
5878
- this._iScale = L.DomUtil.create('div', className + '-line', container);
5879
- }
6705
+ this._addScales(options, className, container);
5880
6706
 
5881
6707
  map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5882
6708
  this._update();
@@ -5888,21 +6714,33 @@ L.Control.Scale = L.Control.extend({
5888
6714
  map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5889
6715
  },
5890
6716
 
6717
+ _addScales: function (options, className, container) {
6718
+ if (options.metric) {
6719
+ this._mScale = L.DomUtil.create('div', className + '-line', container);
6720
+ }
6721
+ if (options.imperial) {
6722
+ this._iScale = L.DomUtil.create('div', className + '-line', container);
6723
+ }
6724
+ },
6725
+
5891
6726
  _update: function () {
5892
6727
  var bounds = this._map.getBounds(),
5893
6728
  centerLat = bounds.getCenter().lat,
5894
-
5895
- left = new L.LatLng(centerLat, bounds.getSouthWest().lng),
5896
- right = new L.LatLng(centerLat, bounds.getNorthEast().lng),
6729
+ halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
6730
+ dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
5897
6731
 
5898
6732
  size = this._map.getSize(),
5899
6733
  options = this.options,
5900
- maxMeters = 0;
6734
+ maxMeters = 0;
5901
6735
 
5902
6736
  if (size.x > 0) {
5903
- maxMeters = left.distanceTo(right) * (options.maxWidth / size.x);
6737
+ maxMeters = dist * (options.maxWidth / size.x);
5904
6738
  }
5905
6739
 
6740
+ this._updateScales(options, maxMeters);
6741
+ },
6742
+
6743
+ _updateScales: function (options, maxMeters) {
5906
6744
  if (options.metric && maxMeters) {
5907
6745
  this._updateMetric(maxMeters);
5908
6746
  }
@@ -5947,24 +6785,30 @@ L.Control.Scale = L.Control.extend({
5947
6785
  var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5948
6786
  d = num / pow10;
5949
6787
 
5950
- d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 2 ? 2 : 1;
6788
+ d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
5951
6789
 
5952
6790
  return pow10 * d;
5953
6791
  }
5954
6792
  });
5955
6793
 
6794
+ L.control.scale = function (options) {
6795
+ return new L.Control.Scale(options);
6796
+ };
6797
+
5956
6798
 
5957
6799
 
5958
6800
  L.Control.Layers = L.Control.extend({
5959
6801
  options: {
5960
6802
  collapsed: true,
5961
- position: 'topright'
6803
+ position: 'topright',
6804
+ autoZIndex: true
5962
6805
  },
5963
6806
 
5964
6807
  initialize: function (baseLayers, overlays, options) {
5965
6808
  L.Util.setOptions(this, options);
5966
6809
 
5967
6810
  this._layers = {};
6811
+ this._lastZIndex = 0;
5968
6812
 
5969
6813
  for (var i in baseLayers) {
5970
6814
  if (baseLayers.hasOwnProperty(i)) {
@@ -6012,15 +6856,15 @@ L.Control.Layers = L.Control.extend({
6012
6856
  if (!L.Browser.touch) {
6013
6857
  L.DomEvent.disableClickPropagation(container);
6014
6858
  } else {
6015
- L.DomEvent.addListener(container, 'click', L.DomEvent.stopPropagation);
6859
+ L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
6016
6860
  }
6017
6861
 
6018
6862
  var form = this._form = L.DomUtil.create('form', className + '-list');
6019
6863
 
6020
6864
  if (this.options.collapsed) {
6021
6865
  L.DomEvent
6022
- .addListener(container, 'mouseover', this._expand, this)
6023
- .addListener(container, 'mouseout', this._collapse, this);
6866
+ .on(container, 'mouseover', this._expand, this)
6867
+ .on(container, 'mouseout', this._collapse, this);
6024
6868
 
6025
6869
  var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
6026
6870
  link.href = '#';
@@ -6028,12 +6872,12 @@ L.Control.Layers = L.Control.extend({
6028
6872
 
6029
6873
  if (L.Browser.touch) {
6030
6874
  L.DomEvent
6031
- .addListener(link, 'click', L.DomEvent.stopPropagation)
6032
- .addListener(link, 'click', L.DomEvent.preventDefault)
6033
- .addListener(link, 'click', this._expand, this);
6875
+ .on(link, 'click', L.DomEvent.stopPropagation)
6876
+ .on(link, 'click', L.DomEvent.preventDefault)
6877
+ .on(link, 'click', this._expand, this);
6034
6878
  }
6035
6879
  else {
6036
- L.DomEvent.addListener(link, 'focus', this._expand, this);
6880
+ L.DomEvent.on(link, 'focus', this._expand, this);
6037
6881
  }
6038
6882
 
6039
6883
  this._map.on('movestart', this._collapse, this);
@@ -6051,11 +6895,17 @@ L.Control.Layers = L.Control.extend({
6051
6895
 
6052
6896
  _addLayer: function (layer, name, overlay) {
6053
6897
  var id = L.Util.stamp(layer);
6898
+
6054
6899
  this._layers[id] = {
6055
6900
  layer: layer,
6056
6901
  name: name,
6057
6902
  overlay: overlay
6058
6903
  };
6904
+
6905
+ if (this.options.autoZIndex && layer.setZIndex) {
6906
+ this._lastZIndex++;
6907
+ layer.setZIndex(this._lastZIndex);
6908
+ }
6059
6909
  },
6060
6910
 
6061
6911
  _update: function () {
@@ -6081,18 +6931,37 @@ L.Control.Layers = L.Control.extend({
6081
6931
  this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
6082
6932
  },
6083
6933
 
6084
- _addItem: function (obj, onclick) {
6085
- var label = document.createElement('label');
6934
+ // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
6935
+ _createRadioElement: function (name, checked) {
6936
+
6937
+ var radioHtml = '<input type="radio" name="' + name + '"';
6938
+ if (checked) {
6939
+ radioHtml += ' checked="checked"';
6940
+ }
6941
+ radioHtml += '/>';
6942
+
6943
+ var radioFragment = document.createElement('div');
6944
+ radioFragment.innerHTML = radioHtml;
6945
+
6946
+ return radioFragment.firstChild;
6947
+ },
6948
+
6949
+ _addItem: function (obj) {
6950
+ var label = document.createElement('label'),
6951
+ input,
6952
+ checked = this._map.hasLayer(obj.layer);
6086
6953
 
6087
- var input = document.createElement('input');
6088
- if (!obj.overlay) {
6089
- input.name = 'leaflet-base-layers';
6954
+ if (obj.overlay) {
6955
+ input = document.createElement('input');
6956
+ input.type = 'checkbox';
6957
+ input.defaultChecked = checked;
6958
+ } else {
6959
+ input = this._createRadioElement('leaflet-base-layers', checked);
6090
6960
  }
6091
- input.type = obj.overlay ? 'checkbox' : 'radio';
6961
+
6092
6962
  input.layerId = L.Util.stamp(obj.layer);
6093
- input.defaultChecked = this._map.hasLayer(obj.layer);
6094
6963
 
6095
- L.DomEvent.addListener(input, 'click', this._onInputClick, this);
6964
+ L.DomEvent.on(input, 'click', this._onInputClick, this);
6096
6965
 
6097
6966
  var name = document.createTextNode(' ' + obj.name);
6098
6967
 
@@ -6129,6 +6998,10 @@ L.Control.Layers = L.Control.extend({
6129
6998
  }
6130
6999
  });
6131
7000
 
7001
+ L.control.layers = function (baseLayers, overlays, options) {
7002
+ return new L.Control.Layers(baseLayers, overlays, options);
7003
+ };
7004
+
6132
7005
 
6133
7006
  L.Transition = L.Class.extend({
6134
7007
  includes: L.Mixin.Events,
@@ -6195,7 +7068,7 @@ L.Transition = L.Transition.extend({
6195
7068
  this._el = el;
6196
7069
  L.Util.setOptions(this, options);
6197
7070
 
6198
- L.DomEvent.addListener(el, L.Transition.END, this._onTransitionEnd, this);
7071
+ L.DomEvent.on(el, L.Transition.END, this._onTransitionEnd, this);
6199
7072
  this._onFakeStep = L.Util.bind(this._onFakeStep, this);
6200
7073
  },
6201
7074
 
@@ -6226,6 +7099,11 @@ L.Transition = L.Transition.extend({
6226
7099
 
6227
7100
  this.fire('start');
6228
7101
 
7102
+ if (L.Browser.mobileWebkit) {
7103
+ // Set up a slightly delayed call to a backup event if webkitTransitionEnd doesn't fire properly
7104
+ this.backupEventFire = setTimeout(L.Util.bind(this._onBackupFireEnd, this), this.options.duration * 1.2 * 1000);
7105
+ }
7106
+
6229
7107
  if (L.Transition.NATIVE) {
6230
7108
  clearInterval(this._timer);
6231
7109
  this._timer = setInterval(this._onFakeStep, this.options.fakeStepInterval);
@@ -6257,12 +7135,24 @@ L.Transition = L.Transition.extend({
6257
7135
 
6258
7136
  this._el.style[L.Transition.TRANSITION] = '';
6259
7137
 
7138
+ // Clear the delayed call to the backup event, we have recieved some form of webkitTransitionEnd
7139
+ clearTimeout(this.backupEventFire);
7140
+ delete this.backupEventFire;
7141
+
6260
7142
  this.fire('step');
6261
7143
 
6262
7144
  if (e && e.type) {
6263
7145
  this.fire('end');
6264
7146
  }
6265
7147
  }
7148
+ },
7149
+
7150
+ _onBackupFireEnd: function () {
7151
+ // Create and fire a transitionEnd event on the element.
7152
+
7153
+ var event = document.createEvent("Event");
7154
+ event.initEvent(L.Transition.END, true, false);
7155
+ this._el.dispatchEvent(event);
6266
7156
  }
6267
7157
  });
6268
7158
 
@@ -6281,11 +7171,8 @@ L.Transition = L.Transition.NATIVE ? L.Transition : L.Transition.extend({
6281
7171
  TIMER: true,
6282
7172
 
6283
7173
  EASINGS: {
6284
- 'ease': [0.25, 0.1, 0.25, 1.0],
6285
- 'linear': [0.0, 0.0, 1.0, 1.0],
6286
- 'ease-in': [0.42, 0, 1.0, 1.0],
6287
- 'ease-out': [0, 0, 0.58, 1.0],
6288
- 'ease-in-out': [0.42, 0, 0.58, 1.0]
7174
+ 'linear': function (t) { return t; },
7175
+ 'ease-out': function (t) { return t * (2 - t); }
6289
7176
  },
6290
7177
 
6291
7178
  CUSTOM_PROPS_GETTERS: {
@@ -6304,12 +7191,7 @@ L.Transition = L.Transition.NATIVE ? L.Transition : L.Transition.extend({
6304
7191
  this._el = el;
6305
7192
  L.Util.extend(this.options, options);
6306
7193
 
6307
- var easings = L.Transition.EASINGS[this.options.easing] || L.Transition.EASINGS.ease;
6308
-
6309
- this._p1 = new L.Point(0, 0);
6310
- this._p2 = new L.Point(easings[0], easings[1]);
6311
- this._p3 = new L.Point(easings[2], easings[3]);
6312
- this._p4 = new L.Point(1, 1);
7194
+ this._easing = L.Transition.EASINGS[this.options.easing] || L.Transition.EASINGS['ease-out'];
6313
7195
 
6314
7196
  this._step = L.Util.bind(this._step, this);
6315
7197
  this._interval = Math.round(1000 / this.options.fps);
@@ -6349,7 +7231,7 @@ L.Transition = L.Transition.NATIVE ? L.Transition : L.Transition.extend({
6349
7231
  duration = this.options.duration * 1000;
6350
7232
 
6351
7233
  if (elapsed < duration) {
6352
- this._runFrame(this._cubicBezier(elapsed / duration));
7234
+ this._runFrame(this._easing(elapsed / duration));
6353
7235
  } else {
6354
7236
  this._runFrame(1);
6355
7237
  this._complete();
@@ -6378,42 +7260,26 @@ L.Transition = L.Transition.NATIVE ? L.Transition : L.Transition.extend({
6378
7260
  _complete: function () {
6379
7261
  clearInterval(this._timer);
6380
7262
  this.fire('end');
6381
- },
6382
-
6383
- _cubicBezier: function (t) {
6384
- var a = Math.pow(1 - t, 3),
6385
- b = 3 * Math.pow(1 - t, 2) * t,
6386
- c = 3 * (1 - t) * Math.pow(t, 2),
6387
- d = Math.pow(t, 3),
6388
- p1 = this._p1.multiplyBy(a),
6389
- p2 = this._p2.multiplyBy(b),
6390
- p3 = this._p3.multiplyBy(c),
6391
- p4 = this._p4.multiplyBy(d);
6392
-
6393
- return p1.add(p2).add(p3).add(p4).y;
6394
7263
  }
6395
7264
  });
6396
7265
 
6397
7266
 
6398
7267
 
6399
7268
  L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
7269
+
6400
7270
  setView: function (center, zoom, forceReset) {
6401
7271
  zoom = this._limitZoom(zoom);
6402
7272
 
6403
7273
  var zoomChanged = (this._zoom !== zoom);
6404
7274
 
6405
7275
  if (this._loaded && !forceReset && this._layers) {
6406
- // difference between the new and current centers in pixels
6407
- var offset = this._getNewTopLeftPoint(center).subtract(this._getTopLeftPoint());
6408
-
6409
- center = new L.LatLng(center.lat, center.lng);
6410
-
6411
7276
  var done = (zoomChanged ?
6412
- this._zoomToIfCenterInView && this._zoomToIfCenterInView(center, zoom, offset) :
6413
- this._panByIfClose(offset));
7277
+ this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
7278
+ this._panByIfClose(center));
6414
7279
 
6415
7280
  // exit if animated pan or zoom started
6416
7281
  if (done) {
7282
+ clearTimeout(this._sizeTimer);
6417
7283
  return this;
6418
7284
  }
6419
7285
  }
@@ -6425,6 +7291,8 @@ L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
6425
7291
  },
6426
7292
 
6427
7293
  panBy: function (offset, options) {
7294
+ offset = L.point(offset);
7295
+
6428
7296
  if (!(offset.x || offset.y)) {
6429
7297
  return this;
6430
7298
  }
@@ -6432,15 +7300,17 @@ L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
6432
7300
  if (!this._panTransition) {
6433
7301
  this._panTransition = new L.Transition(this._mapPane);
6434
7302
 
6435
- this._panTransition.on('step', this._onPanTransitionStep, this);
6436
- this._panTransition.on('end', this._onPanTransitionEnd, this);
7303
+ this._panTransition.on({
7304
+ 'step': this._onPanTransitionStep,
7305
+ 'end': this._onPanTransitionEnd
7306
+ }, this);
6437
7307
  }
6438
7308
 
6439
7309
  L.Util.setOptions(this._panTransition, L.Util.extend({duration: 0.25}, options));
6440
7310
 
6441
7311
  this.fire('movestart');
6442
7312
 
6443
- this._mapPane.className += ' leaflet-pan-anim';
7313
+ L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
6444
7314
 
6445
7315
  this._panTransition.run({
6446
7316
  position: L.DomUtil.getPosition(this._mapPane).subtract(offset)
@@ -6454,11 +7324,14 @@ L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
6454
7324
  },
6455
7325
 
6456
7326
  _onPanTransitionEnd: function () {
6457
- this._mapPane.className = this._mapPane.className.replace(/ leaflet-pan-anim/g, '');
7327
+ L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
6458
7328
  this.fire('moveend');
6459
7329
  },
6460
7330
 
6461
- _panByIfClose: function (offset) {
7331
+ _panByIfClose: function (center) {
7332
+ // difference between the new and current centers in pixels
7333
+ var offset = this._getCenterOffset(center)._floor();
7334
+
6462
7335
  if (this._offsetIsWithinView(offset)) {
6463
7336
  this.panBy(offset);
6464
7337
  return true;
@@ -6477,53 +7350,37 @@ L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
6477
7350
 
6478
7351
 
6479
7352
  L.Map.mergeOptions({
6480
- zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android && !L.Browser.mobileOpera
7353
+ zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
6481
7354
  });
6482
7355
 
6483
7356
  L.Map.include(!L.DomUtil.TRANSITION ? {} : {
6484
- _zoomToIfCenterInView: function (center, zoom, centerOffset) {
6485
7357
 
6486
- if (this._animatingZoom) {
6487
- return true;
6488
- }
6489
- if (!this.options.zoomAnimation) {
6490
- return false;
6491
- }
7358
+ _zoomToIfClose: function (center, zoom) {
7359
+
7360
+ if (this._animatingZoom) { return true; }
7361
+ if (!this.options.zoomAnimation) { return false; }
6492
7362
 
6493
- var scale = Math.pow(2, zoom - this._zoom),
6494
- offset = centerOffset.divideBy(1 - 1 / scale);
7363
+ var scale = this.getZoomScale(zoom),
7364
+ offset = this._getCenterOffset(center).divideBy(1 - 1 / scale);
6495
7365
 
6496
7366
  // if offset does not exceed half of the view
6497
- if (!this._offsetIsWithinView(offset, 1)) {
6498
- return false;
6499
- }
7367
+ if (!this._offsetIsWithinView(offset, 1)) { return false; }
6500
7368
 
6501
- this._mapPane.className += ' leaflet-zoom-anim';
7369
+ L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
6502
7370
 
6503
7371
  this
6504
7372
  .fire('movestart')
6505
7373
  .fire('zoomstart');
6506
7374
 
6507
- //Hack: Disable this for android due to it not supporting double translate (mentioned in _runAnimation below)
6508
- //if Foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer
6509
- if (!L.Browser.android && this._tileBg && this._getLoadedTilesPercentage(this._tileBg) > 0.5 && this._getLoadedTilesPercentage(this._tilePane) < 0.5) {
6510
- //Leave current bg and just zoom it some more
6511
-
6512
- this._tilePane.style.visibility = 'hidden';
6513
- this._tilePane.empty = true;
6514
- this._stopLoadingImages(this._tilePane);
6515
- } else {
6516
- this._prepareTileBg();
6517
- }
6518
-
6519
- var centerPoint = this.containerPointToLayerPoint(this.getSize().divideBy(2)),
6520
- origin = centerPoint.add(offset);
7375
+ this._prepareTileBg();
6521
7376
 
6522
7377
  this.fire('zoomanim', {
6523
7378
  center: center,
6524
7379
  zoom: zoom
6525
7380
  });
6526
7381
 
7382
+ var origin = this._getCenterLayerPoint().add(offset);
7383
+
6527
7384
  this._runAnimation(center, zoom, scale, origin);
6528
7385
 
6529
7386
  return true;
@@ -6552,7 +7409,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
6552
7409
  // it breaks touch zoom which Anroid doesn't support anyway, so that's a really ugly hack
6553
7410
 
6554
7411
  // TODO work around this prettier
6555
- if (L.Browser.android) {
7412
+ if (L.Browser.android23) {
6556
7413
  tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
6557
7414
  scaleStr = 'scale(' + scale + ')';
6558
7415
  } else {
@@ -6575,6 +7432,18 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
6575
7432
  var tilePane = this._tilePane,
6576
7433
  tileBg = this._tileBg;
6577
7434
 
7435
+ // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
7436
+ // (disable this for Android due to it not supporting double translate)
7437
+ if (!L.Browser.android23 && tileBg &&
7438
+ this._getLoadedTilesPercentage(tileBg) > 0.5 &&
7439
+ this._getLoadedTilesPercentage(tilePane) < 0.5) {
7440
+
7441
+ tilePane.style.visibility = 'hidden';
7442
+ tilePane.empty = true;
7443
+ this._stopLoadingImages(tilePane);
7444
+ return;
7445
+ }
7446
+
6578
7447
  if (!tileBg) {
6579
7448
  tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
6580
7449
  tileBg.style.zIndex = 1;
@@ -6605,7 +7474,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
6605
7474
  },
6606
7475
 
6607
7476
  _getLoadedTilesPercentage: function (container) {
6608
- var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
7477
+ var tiles = container.getElementsByTagName('img'),
6609
7478
  i, len, count = 0;
6610
7479
 
6611
7480
  for (i = 0, len = tiles.length; i < len; i++) {
@@ -6637,10 +7506,10 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
6637
7506
  _onZoomTransitionEnd: function () {
6638
7507
  this._restoreTileFront();
6639
7508
 
6640
- L.Util.falseFn(this._tileBg.offsetWidth);
7509
+ L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
6641
7510
  this._resetView(this._animateToCenter, this._animateToZoom, true, true);
6642
7511
 
6643
- this._mapPane.className = this._mapPane.className.replace(' leaflet-zoom-anim', ''); //TODO toggleClass util
7512
+ L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
6644
7513
  this._animatingZoom = false;
6645
7514
  },
6646
7515
 
@@ -6678,10 +7547,11 @@ L.Map.include({
6678
7547
  options = this._locationOptions = L.Util.extend(this._defaultLocateOptions, options);
6679
7548
 
6680
7549
  if (!navigator.geolocation) {
6681
- return this.fire('locationerror', {
7550
+ this._handleGeolocationError({
6682
7551
  code: 0,
6683
7552
  message: "Geolocation not supported."
6684
7553
  });
7554
+ return this;
6685
7555
  }
6686
7556
 
6687
7557
  var onResponse = L.Util.bind(this._handleGeolocationResponse, this),
@@ -6704,7 +7574,7 @@ L.Map.include({
6704
7574
 
6705
7575
  _handleGeolocationError: function (error) {
6706
7576
  var c = error.code,
6707
- message =
7577
+ message = error.message ||
6708
7578
  (c === 1 ? "permission denied" :
6709
7579
  (c === 2 ? "position unavailable" : "timeout"));
6710
7580
 
@@ -6748,4 +7618,4 @@ L.Map.include({
6748
7618
 
6749
7619
 
6750
7620
 
6751
- }());
7621
+ }(this));