leaflet-rails 0.4.0.alpha6 → 0.4.2.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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));