leaflet-rails 0.6.4 → 0.7.0

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/.travis.yml CHANGED
@@ -5,10 +5,9 @@ rvm:
5
5
  - rbx-19mode
6
6
  - ruby-head
7
7
  - jruby-head
8
+ - 2.0.0
8
9
  matrix:
9
10
  allow_failures:
10
- - rvm: jruby-19mode
11
- - rvm: rbx-19mode
12
11
  - rvm: ruby-head
13
12
  - rvm: jruby-head
14
13
  notifications:
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![Build Status](https://travis-ci.org/axyjo/leaflet-rails.png?branch=master)](https://travis-ci.org/axyjo/leaflet-rails)
2
+ [![Gem Version](https://badge.fury.io/rb/leaflet-rails.png)](http://badge.fury.io/rb/leaflet-rails)
2
3
 
3
4
  Quickstart Guide
4
5
  ================
@@ -17,12 +18,6 @@ Then, run `bundle install` from within your project to download the necessary fi
17
18
  = require leaflet
18
19
  ```
19
20
 
20
- If you require support for Internet Explorer, add the following line as well:
21
-
22
- ```
23
- = require leaflet.ie
24
- ```
25
-
26
21
  After that, open your application-wide Javascript file (typically `app/assets/javascripts/application.js`) and add the following line before requiring files which depend on Leaflet:
27
22
 
28
23
  ```
data/lib/leaflet-rails.rb CHANGED
@@ -5,13 +5,13 @@ module Leaflet
5
5
  mattr_accessor :tile_layer
6
6
  mattr_accessor :attribution
7
7
  mattr_accessor :max_zoom
8
-
8
+
9
9
  module Rails
10
10
  class Engine < ::Rails::Engine
11
11
  initializer 'leaflet-rails.precompile' do |app|
12
12
  app.config.assets.precompile += %w(layers-2x.png layers.png marker-icon-2x.png marker-icon.png marker-shadow.png)
13
13
  end
14
-
14
+
15
15
  initializer 'leaflet-rails.helpers' do
16
16
  ActionView::Base.send :include, Leaflet::ViewHelpers
17
17
  end
@@ -1,5 +1,5 @@
1
1
  module Leaflet
2
2
  module Rails
3
- VERSION = "0.6.4"
3
+ VERSION = "0.7.0"
4
4
  end
5
5
  end
@@ -1,12 +1,12 @@
1
1
  module Leaflet
2
2
  module ViewHelpers
3
-
3
+
4
4
  def map(options)
5
5
  options[:tile_layer] ||= Leaflet.tile_layer
6
6
  options[:attribution] ||= Leaflet.attribution
7
7
  options[:max_zoom] ||= Leaflet.max_zoom
8
8
  options[:container_id] ||= 'map'
9
-
9
+
10
10
  output = []
11
11
  output << "<div id='#{options[:container_id]}'></div>"
12
12
  output << "<script>"
@@ -27,7 +27,7 @@ module Leaflet
27
27
  output << "</script>"
28
28
  output.join("\n").html_safe
29
29
  end
30
-
30
+
31
31
  end
32
-
32
+
33
33
  end
@@ -7,7 +7,7 @@
7
7
  var oldL = window.L,
8
8
  L = {};
9
9
 
10
- L.version = '0.6.4';
10
+ L.version = '0.7';
11
11
 
12
12
  // define Leaflet for Node module pattern loaders, including Browserify
13
13
  if (typeof module === 'object' && typeof module.exports === 'object') {
@@ -135,19 +135,23 @@ L.Util = {
135
135
  return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
136
136
  },
137
137
 
138
- template: function (str, data) {
139
- return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
140
- var value = data[key];
141
- if (value === undefined) {
142
- throw new Error('No value provided for variable ' + str);
143
- } else if (typeof value === 'function') {
144
- value = value(data);
145
- }
146
- return value;
138
+ compileTemplate: function (str, data) {
139
+ // based on https://gist.github.com/padolsey/6008842
140
+ str = str.replace(/"/g, '\\\"');
141
+ str = str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
142
+ return '" + o["' + key + '"]' + (typeof data[key] === 'function' ? '(o)' : '') + ' + "';
147
143
  });
144
+ // jshint evil: true
145
+ return new Function('o', 'return "' + str + '";');
146
+ },
147
+
148
+ template: function (str, data) {
149
+ var cache = L.Util._templateCache = L.Util._templateCache || {};
150
+ cache[str] = cache[str] || L.Util.compileTemplate(str, data);
151
+ return cache[str](data);
148
152
  },
149
153
 
150
- isArray: function (obj) {
154
+ isArray: Array.isArray || function (obj) {
151
155
  return (Object.prototype.toString.call(obj) === '[object Array]');
152
156
  },
153
157
 
@@ -337,7 +341,7 @@ L.Mixin.Events = {
337
341
  if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
338
342
 
339
343
  var events = this[eventsKey] = this[eventsKey] || {},
340
- contextId = context && L.stamp(context),
344
+ contextId = context && context !== this && L.stamp(context),
341
345
  i, len, event, type, indexKey, indexLenKey, typeIndex;
342
346
 
343
347
  // types can be a string of space-separated words
@@ -350,7 +354,7 @@ L.Mixin.Events = {
350
354
  };
351
355
  type = types[i];
352
356
 
353
- if (context) {
357
+ if (contextId) {
354
358
  // store listeners of a particular context in a separate hash (if it has an id)
355
359
  // gives a major performance boost when removing thousands of map layers
356
360
 
@@ -397,7 +401,7 @@ L.Mixin.Events = {
397
401
  if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
398
402
 
399
403
  var events = this[eventsKey],
400
- contextId = context && L.stamp(context),
404
+ contextId = context && context !== this && L.stamp(context),
401
405
  i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
402
406
 
403
407
  types = L.Util.splitWords(types);
@@ -413,9 +417,10 @@ L.Mixin.Events = {
413
417
  // clear all listeners for a type if function isn't specified
414
418
  delete events[type];
415
419
  delete events[indexKey];
420
+ delete events[indexLenKey];
416
421
 
417
422
  } else {
418
- listeners = context && typeIndex ? typeIndex[contextId] : events[type];
423
+ listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];
419
424
 
420
425
  if (listeners) {
421
426
  for (j = listeners.length - 1; j >= 0; j--) {
@@ -458,7 +463,7 @@ L.Mixin.Events = {
458
463
  listeners = events[type].slice();
459
464
 
460
465
  for (i = 0, len = listeners.length; i < len; i++) {
461
- listeners[i].action.call(listeners[i].context || this, event);
466
+ listeners[i].action.call(listeners[i].context, event);
462
467
  }
463
468
  }
464
469
 
@@ -470,7 +475,7 @@ L.Mixin.Events = {
470
475
 
471
476
  if (listeners) {
472
477
  for (i = 0, len = listeners.length; i < len; i++) {
473
- listeners[i].action.call(listeners[i].context || this, event);
478
+ listeners[i].action.call(listeners[i].context, event);
474
479
  }
475
480
  }
476
481
  }
@@ -506,9 +511,7 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
506
511
 
507
512
  (function () {
508
513
 
509
- var ie = !!window.ActiveXObject,
510
- ie6 = ie && !window.XMLHttpRequest,
511
- ie7 = ie && !document.querySelector,
514
+ var ie = 'ActiveXObject' in window,
512
515
  ielt9 = ie && !document.addEventListener,
513
516
 
514
517
  // terrible browser detection to work around Safari / iOS / Android browser bugs
@@ -518,10 +521,13 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
518
521
  phantomjs = ua.indexOf('phantom') !== -1,
519
522
  android = ua.indexOf('android') !== -1,
520
523
  android23 = ua.search('android [23]') !== -1,
524
+ gecko = ua.indexOf('gecko') !== -1,
521
525
 
522
526
  mobile = typeof orientation !== undefined + '',
523
- msTouch = window.navigator && window.navigator.msPointerEnabled &&
524
- window.navigator.msMaxTouchPoints,
527
+ msPointer = window.navigator && window.navigator.msPointerEnabled &&
528
+ window.navigator.msMaxTouchPoints && !window.PointerEvent,
529
+ pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) ||
530
+ msPointer,
525
531
  retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
526
532
  ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
527
533
  window.matchMedia('(min-resolution:144dpi)').matches),
@@ -541,8 +547,8 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
541
547
 
542
548
  var startName = 'ontouchstart';
543
549
 
544
- // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
545
- if (msTouch || (startName in doc)) {
550
+ // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc.
551
+ if (pointer || (startName in doc)) {
546
552
  return true;
547
553
  }
548
554
 
@@ -568,10 +574,9 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
568
574
 
569
575
  L.Browser = {
570
576
  ie: ie,
571
- ie6: ie6,
572
- ie7: ie7,
573
577
  ielt9: ielt9,
574
578
  webkit: webkit,
579
+ gecko: gecko && !webkit && !window.opera && !ie,
575
580
 
576
581
  android: android,
577
582
  android23: android23,
@@ -590,7 +595,8 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
590
595
  mobileOpera: mobile && window.opera,
591
596
 
592
597
  touch: touch,
593
- msTouch: msTouch,
598
+ msPointer: msPointer,
599
+ pointer: pointer,
594
600
 
595
601
  retina: retina
596
602
  };
@@ -881,8 +887,7 @@ L.DomUtil = {
881
887
  el = element,
882
888
  docBody = document.body,
883
889
  docEl = document.documentElement,
884
- pos,
885
- ie7 = L.Browser.ie7;
890
+ pos;
886
891
 
887
892
  do {
888
893
  top += el.offsetTop || 0;
@@ -929,19 +934,6 @@ L.DomUtil = {
929
934
  top -= el.scrollTop || 0;
930
935
  left -= el.scrollLeft || 0;
931
936
 
932
- // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
933
- // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
934
- if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
935
- left += el.scrollWidth - el.clientWidth;
936
-
937
- // ie7 shows the scrollbar by default and provides clientWidth counting it, so we
938
- // need to add it back in if it is visible; scrollbar is on the left as we are RTL
939
- if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
940
- L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
941
- left += 17;
942
- }
943
- }
944
-
945
937
  el = el.parentNode;
946
938
  } while (el);
947
939
 
@@ -969,18 +961,44 @@ L.DomUtil = {
969
961
  },
970
962
 
971
963
  hasClass: function (el, name) {
972
- return (el.className.length > 0) &&
973
- new RegExp('(^|\\s)' + name + '(\\s|$)').test(el.className);
964
+ if (el.classList !== undefined) {
965
+ return el.classList.contains(name);
966
+ }
967
+ var className = L.DomUtil._getClass(el);
968
+ return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
974
969
  },
975
970
 
976
971
  addClass: function (el, name) {
977
- if (!L.DomUtil.hasClass(el, name)) {
978
- el.className += (el.className ? ' ' : '') + name;
972
+ if (el.classList !== undefined) {
973
+ var classes = L.Util.splitWords(name);
974
+ for (var i = 0, len = classes.length; i < len; i++) {
975
+ el.classList.add(classes[i]);
976
+ }
977
+ } else if (!L.DomUtil.hasClass(el, name)) {
978
+ var className = L.DomUtil._getClass(el);
979
+ L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);
979
980
  }
980
981
  },
981
982
 
982
983
  removeClass: function (el, name) {
983
- el.className = L.Util.trim((' ' + el.className + ' ').replace(' ' + name + ' ', ' '));
984
+ if (el.classList !== undefined) {
985
+ el.classList.remove(name);
986
+ } else {
987
+ L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
988
+ }
989
+ },
990
+
991
+ _setClass: function (el, name) {
992
+ if (el.className.baseVal === undefined) {
993
+ el.className = name;
994
+ } else {
995
+ // in case of SVG element
996
+ el.className.baseVal = name;
997
+ }
998
+ },
999
+
1000
+ _getClass: function (el) {
1001
+ return el.className.baseVal === undefined ? el.className : el.className.baseVal;
984
1002
  },
985
1003
 
986
1004
  setOpacity: function (el, value) {
@@ -1089,27 +1107,39 @@ L.DomUtil.TRANSITION_END =
1089
1107
  L.DomUtil.TRANSITION + 'End' : 'transitionend';
1090
1108
 
1091
1109
  (function () {
1092
- var userSelectProperty = L.DomUtil.testProp(
1093
- ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1110
+ if ('onselectstart' in document) {
1111
+ L.extend(L.DomUtil, {
1112
+ disableTextSelection: function () {
1113
+ L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1114
+ },
1115
+
1116
+ enableTextSelection: function () {
1117
+ L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1118
+ }
1119
+ });
1120
+ } else {
1121
+ var userSelectProperty = L.DomUtil.testProp(
1122
+ ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1123
+
1124
+ L.extend(L.DomUtil, {
1125
+ disableTextSelection: function () {
1126
+ if (userSelectProperty) {
1127
+ var style = document.documentElement.style;
1128
+ this._userSelect = style[userSelectProperty];
1129
+ style[userSelectProperty] = 'none';
1130
+ }
1131
+ },
1132
+
1133
+ enableTextSelection: function () {
1134
+ if (userSelectProperty) {
1135
+ document.documentElement.style[userSelectProperty] = this._userSelect;
1136
+ delete this._userSelect;
1137
+ }
1138
+ }
1139
+ });
1140
+ }
1094
1141
 
1095
1142
  L.extend(L.DomUtil, {
1096
- disableTextSelection: function () {
1097
- L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1098
- if (userSelectProperty) {
1099
- var style = document.documentElement.style;
1100
- this._userSelect = style[userSelectProperty];
1101
- style[userSelectProperty] = 'none';
1102
- }
1103
- },
1104
-
1105
- enableTextSelection: function () {
1106
- L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1107
- if (userSelectProperty) {
1108
- document.documentElement.style[userSelectProperty] = this._userSelect;
1109
- delete this._userSelect;
1110
- }
1111
- },
1112
-
1113
1143
  disableImageDrag: function () {
1114
1144
  L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1115
1145
  },
@@ -1125,16 +1155,20 @@ L.DomUtil.TRANSITION_END =
1125
1155
  * L.LatLng represents a geographical point with latitude and longitude coordinates.
1126
1156
  */
1127
1157
 
1128
- L.LatLng = function (rawLat, rawLng) { // (Number, Number)
1129
- var lat = parseFloat(rawLat),
1130
- lng = parseFloat(rawLng);
1158
+ L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)
1159
+ lat = parseFloat(lat);
1160
+ lng = parseFloat(lng);
1131
1161
 
1132
1162
  if (isNaN(lat) || isNaN(lng)) {
1133
- throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
1163
+ throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1134
1164
  }
1135
1165
 
1136
1166
  this.lat = lat;
1137
1167
  this.lng = lng;
1168
+
1169
+ if (alt !== undefined) {
1170
+ this.alt = parseFloat(alt);
1171
+ }
1138
1172
  };
1139
1173
 
1140
1174
  L.extend(L.LatLng, {
@@ -1198,7 +1232,11 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe
1198
1232
  return a;
1199
1233
  }
1200
1234
  if (L.Util.isArray(a)) {
1201
- return new L.LatLng(a[0], a[1]);
1235
+ if (typeof a[0] === 'number' || typeof a[0] === 'string') {
1236
+ return new L.LatLng(a[0], a[1], a[2]);
1237
+ } else {
1238
+ return null;
1239
+ }
1202
1240
  }
1203
1241
  if (a === undefined || a === null) {
1204
1242
  return a;
@@ -1206,6 +1244,9 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe
1206
1244
  if (typeof a === 'object' && 'lat' in a) {
1207
1245
  return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
1208
1246
  }
1247
+ if (b === undefined) {
1248
+ return null;
1249
+ }
1209
1250
  return new L.LatLng(a, b);
1210
1251
  };
1211
1252
 
@@ -1230,8 +1271,9 @@ L.LatLngBounds.prototype = {
1230
1271
  extend: function (obj) { // (LatLng) or (LatLngBounds)
1231
1272
  if (!obj) { return this; }
1232
1273
 
1233
- if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
1234
- obj = L.latLng(obj);
1274
+ var latLng = L.latLng(obj);
1275
+ if (latLng !== null) {
1276
+ obj = latLng;
1235
1277
  } else {
1236
1278
  obj = L.latLngBounds(obj);
1237
1279
  }
@@ -1444,6 +1486,11 @@ L.CRS = {
1444
1486
 
1445
1487
  scale: function (zoom) {
1446
1488
  return 256 * Math.pow(2, zoom);
1489
+ },
1490
+
1491
+ getSize: function (zoom) {
1492
+ var s = this.scale(zoom);
1493
+ return L.point(s, s);
1447
1494
  }
1448
1495
  };
1449
1496
 
@@ -1522,8 +1569,13 @@ L.Map = L.Class.extend({
1522
1569
  initialize: function (id, options) { // (HTMLElement or String, Object)
1523
1570
  options = L.setOptions(this, options);
1524
1571
 
1572
+
1525
1573
  this._initContainer(id);
1526
1574
  this._initLayout();
1575
+
1576
+ // hack for https://github.com/Leaflet/Leaflet/issues/1980
1577
+ this._onResize = L.bind(this._onResize, this);
1578
+
1527
1579
  this._initEvents();
1528
1580
 
1529
1581
  if (options.maxBounds) {
@@ -1550,11 +1602,16 @@ L.Map = L.Class.extend({
1550
1602
 
1551
1603
  // replaced by animation-powered implementation in Map.PanAnimation.js
1552
1604
  setView: function (center, zoom) {
1605
+ zoom = zoom === undefined ? this.getZoom() : zoom;
1553
1606
  this._resetView(L.latLng(center), this._limitZoom(zoom));
1554
1607
  return this;
1555
1608
  },
1556
1609
 
1557
1610
  setZoom: function (zoom, options) {
1611
+ if (!this._loaded) {
1612
+ this._zoom = this._limitZoom(zoom);
1613
+ return this;
1614
+ }
1558
1615
  return this.setView(this.getCenter(), zoom, {zoom: options});
1559
1616
  },
1560
1617
 
@@ -1592,6 +1649,8 @@ L.Map = L.Class.extend({
1592
1649
  nePoint = this.project(bounds.getNorthEast(), zoom),
1593
1650
  center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
1594
1651
 
1652
+ zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom;
1653
+
1595
1654
  return this.setView(center, zoom, options);
1596
1655
  },
1597
1656
 
@@ -1604,7 +1663,7 @@ L.Map = L.Class.extend({
1604
1663
  },
1605
1664
 
1606
1665
  panBy: function (offset) { // (Point)
1607
- // replaced with animated panBy in Map.Animation.js
1666
+ // replaced with animated panBy in Map.PanAnimation.js
1608
1667
  this.fire('movestart');
1609
1668
 
1610
1669
  this._rawPanBy(L.point(offset));
@@ -1613,63 +1672,29 @@ L.Map = L.Class.extend({
1613
1672
  return this.fire('moveend');
1614
1673
  },
1615
1674
 
1616
- setMaxBounds: function (bounds, options) {
1675
+ setMaxBounds: function (bounds) {
1617
1676
  bounds = L.latLngBounds(bounds);
1618
1677
 
1619
1678
  this.options.maxBounds = bounds;
1620
1679
 
1621
1680
  if (!bounds) {
1622
- this._boundsMinZoom = null;
1623
- this.off('moveend', this._panInsideMaxBounds, this);
1624
- return this;
1681
+ return this.off('moveend', this._panInsideMaxBounds, this);
1625
1682
  }
1626
1683
 
1627
- var minZoom = this.getBoundsZoom(bounds, true);
1628
-
1629
- this._boundsMinZoom = minZoom;
1630
-
1631
1684
  if (this._loaded) {
1632
- if (this._zoom < minZoom) {
1633
- this.setView(bounds.getCenter(), minZoom, options);
1634
- } else {
1635
- this.panInsideBounds(bounds);
1636
- }
1685
+ this._panInsideMaxBounds();
1637
1686
  }
1638
1687
 
1639
- this.on('moveend', this._panInsideMaxBounds, this);
1640
-
1641
- return this;
1688
+ return this.on('moveend', this._panInsideMaxBounds, this);
1642
1689
  },
1643
1690
 
1644
- panInsideBounds: function (bounds) {
1645
- bounds = L.latLngBounds(bounds);
1691
+ panInsideBounds: function (bounds, options) {
1692
+ var center = this.getCenter(),
1693
+ newCenter = this._limitCenter(center, this._zoom, bounds);
1646
1694
 
1647
- var viewBounds = this.getPixelBounds(),
1648
- viewSw = viewBounds.getBottomLeft(),
1649
- viewNe = viewBounds.getTopRight(),
1650
- sw = this.project(bounds.getSouthWest()),
1651
- ne = this.project(bounds.getNorthEast()),
1652
- dx = 0,
1653
- dy = 0;
1695
+ if (center.equals(newCenter)) { return this; }
1654
1696
 
1655
- if (viewNe.y < ne.y) { // north
1656
- dy = Math.ceil(ne.y - viewNe.y);
1657
- }
1658
- if (viewNe.x > ne.x) { // east
1659
- dx = Math.floor(ne.x - viewNe.x);
1660
- }
1661
- if (viewSw.y > sw.y) { // south
1662
- dy = Math.floor(sw.y - viewSw.y);
1663
- }
1664
- if (viewSw.x < sw.x) { // west
1665
- dx = Math.ceil(sw.x - viewSw.x);
1666
- }
1667
-
1668
- if (dx || dy) {
1669
- return this.panBy([dx, dy]);
1670
- }
1671
-
1672
- return this;
1697
+ return this.panTo(newCenter, options);
1673
1698
  },
1674
1699
 
1675
1700
  addLayer: function (layer) {
@@ -1704,7 +1729,7 @@ L.Map = L.Class.extend({
1704
1729
  removeLayer: function (layer) {
1705
1730
  var id = L.stamp(layer);
1706
1731
 
1707
- if (!this._layers[id]) { return; }
1732
+ if (!this._layers[id]) { return this; }
1708
1733
 
1709
1734
  if (this._loaded) {
1710
1735
  layer.onRemove(this);
@@ -1752,15 +1777,14 @@ L.Map = L.Class.extend({
1752
1777
 
1753
1778
  var oldSize = this.getSize();
1754
1779
  this._sizeChanged = true;
1755
-
1756
- if (this.options.maxBounds) {
1757
- this.setMaxBounds(this.options.maxBounds);
1758
- }
1780
+ this._initialCenter = null;
1759
1781
 
1760
1782
  if (!this._loaded) { return this; }
1761
1783
 
1762
1784
  var newSize = this.getSize(),
1763
- offset = oldSize.subtract(newSize).divideBy(2).round();
1785
+ oldCenter = oldSize.divideBy(2).round(),
1786
+ newCenter = newSize.divideBy(2).round(),
1787
+ offset = oldCenter.subtract(newCenter);
1764
1788
 
1765
1789
  if (!offset.x && !offset.y) { return this; }
1766
1790
 
@@ -1774,9 +1798,12 @@ L.Map = L.Class.extend({
1774
1798
 
1775
1799
  this.fire('move');
1776
1800
 
1777
- // make sure moveend is not fired too often on resize
1778
- clearTimeout(this._sizeTimer);
1779
- this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
1801
+ if (options.debounceMoveend) {
1802
+ clearTimeout(this._sizeTimer);
1803
+ this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
1804
+ } else {
1805
+ this.fire('moveend');
1806
+ }
1780
1807
  }
1781
1808
 
1782
1809
  return this.fire('resize', {
@@ -1787,7 +1814,7 @@ L.Map = L.Class.extend({
1787
1814
 
1788
1815
  // TODO handler.addTo
1789
1816
  addHandler: function (name, HandlerClass) {
1790
- if (!HandlerClass) { return; }
1817
+ if (!HandlerClass) { return this; }
1791
1818
 
1792
1819
  var handler = this[name] = new HandlerClass(this);
1793
1820
 
@@ -1807,7 +1834,12 @@ L.Map = L.Class.extend({
1807
1834
 
1808
1835
  this._initEvents('off');
1809
1836
 
1810
- delete this._container._leaflet;
1837
+ try {
1838
+ // throws error in IE6-8
1839
+ delete this._container._leaflet;
1840
+ } catch (e) {
1841
+ this._container._leaflet = undefined;
1842
+ }
1811
1843
 
1812
1844
  this._clearPanes();
1813
1845
  if (this._clearControlPos) {
@@ -1825,7 +1857,7 @@ L.Map = L.Class.extend({
1825
1857
  getCenter: function () { // (Boolean) -> LatLng
1826
1858
  this._checkIfLoaded();
1827
1859
 
1828
- if (!this._moved()) {
1860
+ if (this._initialCenter && !this._moved()) {
1829
1861
  return this._initialCenter;
1830
1862
  }
1831
1863
  return this.layerPointToLatLng(this._getCenterLayerPoint());
@@ -1844,9 +1876,9 @@ L.Map = L.Class.extend({
1844
1876
  },
1845
1877
 
1846
1878
  getMinZoom: function () {
1847
- var z1 = this._layersMinZoom === undefined ? 0 : this._layersMinZoom,
1848
- z2 = this._boundsMinZoom === undefined ? 0 : this._boundsMinZoom;
1849
- return this.options.minZoom === undefined ? Math.max(z1, z2) : this.options.minZoom;
1879
+ return this.options.minZoom === undefined ?
1880
+ (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
1881
+ this.options.minZoom;
1850
1882
  },
1851
1883
 
1852
1884
  getMaxZoom: function () {
@@ -1998,6 +2030,7 @@ L.Map = L.Class.extend({
1998
2030
  L.DomUtil.addClass(container, 'leaflet-container' +
1999
2031
  (L.Browser.touch ? ' leaflet-touch' : '') +
2000
2032
  (L.Browser.retina ? ' leaflet-retina' : '') +
2033
+ (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
2001
2034
  (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
2002
2035
 
2003
2036
  var position = L.DomUtil.getStyle(container, 'position');
@@ -2168,12 +2201,14 @@ L.Map = L.Class.extend({
2168
2201
  _onResize: function () {
2169
2202
  L.Util.cancelAnimFrame(this._resizeRequest);
2170
2203
  this._resizeRequest = L.Util.requestAnimFrame(
2171
- this.invalidateSize, this, false, this._container);
2204
+ function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);
2172
2205
  },
2173
2206
 
2174
2207
  _onMouseClick: function (e) {
2175
- if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) ||
2176
- L.DomEvent._skipped(e)) { return; }
2208
+ if (!this._loaded || (!e._simulated &&
2209
+ ((this.dragging && this.dragging.moved()) ||
2210
+ (this.boxZoom && this.boxZoom.moved()))) ||
2211
+ L.DomEvent._skipped(e)) { return; }
2177
2212
 
2178
2213
  this.fire('preclick');
2179
2214
  this._fireMouseEvent(e);
@@ -2268,6 +2303,46 @@ L.Map = L.Class.extend({
2268
2303
  return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
2269
2304
  },
2270
2305
 
2306
+ // adjust center for view to get inside bounds
2307
+ _limitCenter: function (center, zoom, bounds) {
2308
+
2309
+ if (!bounds) { return center; }
2310
+
2311
+ var centerPoint = this.project(center, zoom),
2312
+ viewHalf = this.getSize().divideBy(2),
2313
+ viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
2314
+ offset = this._getBoundsOffset(viewBounds, bounds, zoom);
2315
+
2316
+ return this.unproject(centerPoint.add(offset), zoom);
2317
+ },
2318
+
2319
+ // adjust offset for view to get inside bounds
2320
+ _limitOffset: function (offset, bounds) {
2321
+ if (!bounds) { return offset; }
2322
+
2323
+ var viewBounds = this.getPixelBounds(),
2324
+ newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
2325
+
2326
+ return offset.add(this._getBoundsOffset(newBounds, bounds));
2327
+ },
2328
+
2329
+ // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
2330
+ _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
2331
+ var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),
2332
+ seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),
2333
+
2334
+ dx = this._rebound(nwOffset.x, -seOffset.x),
2335
+ dy = this._rebound(nwOffset.y, -seOffset.y);
2336
+
2337
+ return new L.Point(dx, dy);
2338
+ },
2339
+
2340
+ _rebound: function (left, right) {
2341
+ return left + right > 0 ?
2342
+ Math.round(left - right) / 2 :
2343
+ Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
2344
+ },
2345
+
2271
2346
  _limitZoom: function (zoom) {
2272
2347
  var min = this.getMinZoom(),
2273
2348
  max = this.getMaxZoom();
@@ -2348,9 +2423,9 @@ L.CRS.EPSG3395 = L.extend({}, L.CRS, {
2348
2423
  transformation: (function () {
2349
2424
  var m = L.Projection.Mercator,
2350
2425
  r = m.R_MAJOR,
2351
- r2 = m.R_MINOR;
2426
+ scale = 0.5 / (Math.PI * r);
2352
2427
 
2353
- return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
2428
+ return new L.Transformation(scale, 0.5, -scale, 0.5);
2354
2429
  }())
2355
2430
  });
2356
2431
 
@@ -2371,7 +2446,8 @@ L.TileLayer = L.Class.extend({
2371
2446
  attribution: '',
2372
2447
  zoomOffset: 0,
2373
2448
  opacity: 1,
2374
- /* (undefined works too)
2449
+ /*
2450
+ maxNativeZoom: null,
2375
2451
  zIndex: null,
2376
2452
  tms: false,
2377
2453
  continuousWorld: false,
@@ -2420,9 +2496,6 @@ L.TileLayer = L.Class.extend({
2420
2496
  // create a container div for tiles
2421
2497
  this._initContainer();
2422
2498
 
2423
- // create an image to clone for tiles
2424
- this._createTileProto();
2425
-
2426
2499
  // set up events
2427
2500
  map.on({
2428
2501
  'viewreset': this._reset,
@@ -2587,7 +2660,7 @@ L.TileLayer = L.Class.extend({
2587
2660
  this._updateZIndex();
2588
2661
 
2589
2662
  if (this._animated) {
2590
- var className = 'leaflet-tile-container leaflet-zoom-animated';
2663
+ var className = 'leaflet-tile-container';
2591
2664
 
2592
2665
  this._bgBuffer = L.DomUtil.create('div', className, this._container);
2593
2666
  this._tileContainer = L.DomUtil.create('div', className, this._container);
@@ -2625,13 +2698,27 @@ L.TileLayer = L.Class.extend({
2625
2698
  this._initContainer();
2626
2699
  },
2627
2700
 
2701
+ _getTileSize: function () {
2702
+ var map = this._map,
2703
+ zoom = map.getZoom() + this.options.zoomOffset,
2704
+ zoomN = this.options.maxNativeZoom,
2705
+ tileSize = this.options.tileSize;
2706
+
2707
+ if (zoomN && zoom > zoomN) {
2708
+ tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);
2709
+ }
2710
+
2711
+ return tileSize;
2712
+ },
2713
+
2628
2714
  _update: function () {
2629
2715
 
2630
2716
  if (!this._map) { return; }
2631
2717
 
2632
- var bounds = this._map.getPixelBounds(),
2633
- zoom = this._map.getZoom(),
2634
- tileSize = this.options.tileSize;
2718
+ var map = this._map,
2719
+ bounds = map.getPixelBounds(),
2720
+ zoom = map.getZoom(),
2721
+ tileSize = this._getTileSize();
2635
2722
 
2636
2723
  if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
2637
2724
  return;
@@ -2700,8 +2787,8 @@ L.TileLayer = L.Class.extend({
2700
2787
  var limit = this._getWrapTileNum();
2701
2788
 
2702
2789
  // don't load if exceeds world bounds
2703
- if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit)) ||
2704
- tilePoint.y < 0 || tilePoint.y >= limit) { return false; }
2790
+ if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||
2791
+ tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }
2705
2792
  }
2706
2793
 
2707
2794
  if (options.bounds) {
@@ -2794,12 +2881,14 @@ L.TileLayer = L.Class.extend({
2794
2881
  zoom = options.maxZoom - zoom;
2795
2882
  }
2796
2883
 
2797
- return zoom + options.zoomOffset;
2884
+ zoom += options.zoomOffset;
2885
+
2886
+ return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;
2798
2887
  },
2799
2888
 
2800
2889
  _getTilePos: function (tilePoint) {
2801
2890
  var origin = this._map.getPixelOrigin(),
2802
- tileSize = this.options.tileSize;
2891
+ tileSize = this._getTileSize();
2803
2892
 
2804
2893
  return tilePoint.multiplyBy(tileSize).subtract(origin);
2805
2894
  },
@@ -2816,8 +2905,9 @@ L.TileLayer = L.Class.extend({
2816
2905
  },
2817
2906
 
2818
2907
  _getWrapTileNum: function () {
2819
- // TODO refactor, limit is not valid for non-standard projections
2820
- return Math.pow(2, this._getZoomForUrl());
2908
+ var crs = this._map.options.crs,
2909
+ size = crs.getSize(this._map.getZoom());
2910
+ return size.divideBy(this.options.tileSize);
2821
2911
  },
2822
2912
 
2823
2913
  _adjustTilePoint: function (tilePoint) {
@@ -2826,11 +2916,11 @@ L.TileLayer = L.Class.extend({
2826
2916
 
2827
2917
  // wrap tile coordinates
2828
2918
  if (!this.options.continuousWorld && !this.options.noWrap) {
2829
- tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
2919
+ tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;
2830
2920
  }
2831
2921
 
2832
2922
  if (this.options.tms) {
2833
- tilePoint.y = limit - tilePoint.y - 1;
2923
+ tilePoint.y = limit.y - tilePoint.y - 1;
2834
2924
  }
2835
2925
 
2836
2926
  tilePoint.z = this._getZoomForUrl();
@@ -2841,12 +2931,6 @@ L.TileLayer = L.Class.extend({
2841
2931
  return this.options.subdomains[index];
2842
2932
  },
2843
2933
 
2844
- _createTileProto: function () {
2845
- var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
2846
- img.style.width = img.style.height = this.options.tileSize + 'px';
2847
- img.galleryimg = 'no';
2848
- },
2849
-
2850
2934
  _getTile: function () {
2851
2935
  if (this.options.reuseTiles && this._unusedTiles.length > 0) {
2852
2936
  var tile = this._unusedTiles.pop();
@@ -2860,12 +2944,20 @@ L.TileLayer = L.Class.extend({
2860
2944
  _resetTile: function (/*tile*/) {},
2861
2945
 
2862
2946
  _createTile: function () {
2863
- var tile = this._tileImg.cloneNode(false);
2947
+ var tile = L.DomUtil.create('img', 'leaflet-tile');
2948
+ tile.style.width = tile.style.height = this._getTileSize() + 'px';
2949
+ tile.galleryimg = 'no';
2950
+
2864
2951
  tile.onselectstart = tile.onmousemove = L.Util.falseFn;
2865
2952
 
2866
2953
  if (L.Browser.ielt9 && this.options.opacity !== undefined) {
2867
2954
  L.DomUtil.setOpacity(tile, this.options.opacity);
2868
2955
  }
2956
+ // without this hack, tiles disappear after zoom on Chrome for Android
2957
+ // https://github.com/Leaflet/Leaflet/issues/2078
2958
+ if (L.Browser.mobileWebkit3d) {
2959
+ tile.style.WebkitBackfaceVisibility = 'hidden';
2960
+ }
2869
2961
  return tile;
2870
2962
  },
2871
2963
 
@@ -2876,10 +2968,20 @@ L.TileLayer = L.Class.extend({
2876
2968
 
2877
2969
  this._adjustTilePoint(tilePoint);
2878
2970
  tile.src = this.getTileUrl(tilePoint);
2971
+
2972
+ this.fire('tileloadstart', {
2973
+ tile: tile,
2974
+ url: tile.src
2975
+ });
2879
2976
  },
2880
2977
 
2881
2978
  _tileLoaded: function () {
2882
2979
  this._tilesToLoad--;
2980
+
2981
+ if (this._animated) {
2982
+ L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');
2983
+ }
2984
+
2883
2985
  if (!this._tilesToLoad) {
2884
2986
  this.fire('load');
2885
2987
 
@@ -2974,13 +3076,15 @@ L.TileLayer.WMS = L.TileLayer.extend({
2974
3076
 
2975
3077
  this._crs = this.options.crs || map.options.crs;
2976
3078
 
2977
- var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
3079
+ this._wmsVersion = parseFloat(this.wmsParams.version);
3080
+
3081
+ var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
2978
3082
  this.wmsParams[projectionKey] = this._crs.code;
2979
3083
 
2980
3084
  L.TileLayer.prototype.onAdd.call(this, map);
2981
3085
  },
2982
3086
 
2983
- getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
3087
+ getTileUrl: function (tilePoint) { // (Point, Number) -> String
2984
3088
 
2985
3089
  var map = this._map,
2986
3090
  tileSize = this.options.tileSize,
@@ -2988,10 +3092,11 @@ L.TileLayer.WMS = L.TileLayer.extend({
2988
3092
  nwPoint = tilePoint.multiplyBy(tileSize),
2989
3093
  sePoint = nwPoint.add([tileSize, tileSize]),
2990
3094
 
2991
- nw = this._crs.project(map.unproject(nwPoint, zoom)),
2992
- se = this._crs.project(map.unproject(sePoint, zoom)),
2993
-
2994
- bbox = [nw.x, se.y, se.x, nw.y].join(','),
3095
+ nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)),
3096
+ se = this._crs.project(map.unproject(sePoint, tilePoint.z)),
3097
+ bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
3098
+ [se.y, nw.x, nw.y, se.x].join(',') :
3099
+ [nw.x, se.y, se.x, nw.y].join(','),
2995
3100
 
2996
3101
  url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
2997
3102
 
@@ -3045,13 +3150,9 @@ L.TileLayer.Canvas = L.TileLayer.extend({
3045
3150
  this.drawTile(tile, tile._tilePoint, this._map._zoom);
3046
3151
  },
3047
3152
 
3048
- _createTileProto: function () {
3049
- var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
3050
- proto.width = proto.height = this.options.tileSize;
3051
- },
3052
-
3053
3153
  _createTile: function () {
3054
- var tile = this._canvasProto.cloneNode(false);
3154
+ var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3155
+ tile.width = tile.height = this.options.tileSize;
3055
3156
  tile.onselectstart = tile.onmousemove = L.Util.falseFn;
3056
3157
  return tile;
3057
3158
  },
@@ -3155,6 +3256,15 @@ L.ImageOverlay = L.Class.extend({
3155
3256
  return this;
3156
3257
  },
3157
3258
 
3259
+ setUrl: function (url) {
3260
+ this._url = url;
3261
+ this._image.src = this._url;
3262
+ },
3263
+
3264
+ getAttribution: function () {
3265
+ return this.options.attribution;
3266
+ },
3267
+
3158
3268
  _initImage: function () {
3159
3269
  this._image = L.DomUtil.create('img', 'leaflet-image-layer');
3160
3270
 
@@ -3298,19 +3408,8 @@ L.Icon = L.Class.extend({
3298
3408
  },
3299
3409
 
3300
3410
  _createImg: function (src, el) {
3301
-
3302
- if (!L.Browser.ie6) {
3303
- if (!el) {
3304
- el = document.createElement('img');
3305
- }
3306
- el.src = src;
3307
- } else {
3308
- if (!el) {
3309
- el = document.createElement('div');
3310
- }
3311
- el.style.filter =
3312
- 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
3313
- }
3411
+ el = el || document.createElement('img');
3412
+ el.src = src;
3314
3413
  return el;
3315
3414
  },
3316
3415
 
@@ -3389,6 +3488,7 @@ L.Marker = L.Class.extend({
3389
3488
  options: {
3390
3489
  icon: new L.Icon.Default(),
3391
3490
  title: '',
3491
+ alt: '',
3392
3492
  clickable: true,
3393
3493
  draggable: false,
3394
3494
  keyboard: true,
@@ -3410,6 +3510,7 @@ L.Marker = L.Class.extend({
3410
3510
 
3411
3511
  this._initIcon();
3412
3512
  this.update();
3513
+ this.fire('add');
3413
3514
 
3414
3515
  if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
3415
3516
  map.on('zoomanim', this._animateZoom, this);
@@ -3467,6 +3568,10 @@ L.Marker = L.Class.extend({
3467
3568
  this.update();
3468
3569
  }
3469
3570
 
3571
+ if (this._popup) {
3572
+ this.bindPopup(this._popup);
3573
+ }
3574
+
3470
3575
  return this;
3471
3576
  },
3472
3577
 
@@ -3498,6 +3603,10 @@ L.Marker = L.Class.extend({
3498
3603
  if (options.title) {
3499
3604
  icon.title = options.title;
3500
3605
  }
3606
+
3607
+ if (options.alt) {
3608
+ icon.alt = options.alt;
3609
+ }
3501
3610
  }
3502
3611
 
3503
3612
  L.DomUtil.addClass(icon, classToAdd);
@@ -3582,7 +3691,7 @@ L.Marker = L.Class.extend({
3582
3691
  },
3583
3692
 
3584
3693
  _animateZoom: function (opt) {
3585
- var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3694
+ var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
3586
3695
 
3587
3696
  this._setPos(pos);
3588
3697
  },
@@ -3749,11 +3858,13 @@ L.Popup = L.Class.extend({
3749
3858
  options: {
3750
3859
  minWidth: 50,
3751
3860
  maxWidth: 300,
3752
- maxHeight: null,
3861
+ // maxHeight: null,
3753
3862
  autoPan: true,
3754
3863
  closeButton: true,
3755
3864
  offset: [0, 7],
3756
3865
  autoPanPadding: [5, 5],
3866
+ // autoPanPaddingTopLeft: null,
3867
+ // autoPanPaddingBottomRight: null,
3757
3868
  keepInView: false,
3758
3869
  className: '',
3759
3870
  zoomAnimation: true
@@ -3773,7 +3884,6 @@ L.Popup = L.Class.extend({
3773
3884
  if (!this._container) {
3774
3885
  this._initLayout();
3775
3886
  }
3776
- this._updateContent();
3777
3887
 
3778
3888
  var animFade = map.options.fadeAnimation;
3779
3889
 
@@ -3784,7 +3894,7 @@ L.Popup = L.Class.extend({
3784
3894
 
3785
3895
  map.on(this._getEvents(), this);
3786
3896
 
3787
- this._update();
3897
+ this.update();
3788
3898
 
3789
3899
  if (animFade) {
3790
3900
  L.DomUtil.setOpacity(this._container, 1);
@@ -3831,18 +3941,43 @@ L.Popup = L.Class.extend({
3831
3941
  }
3832
3942
  },
3833
3943
 
3944
+ getLatLng: function () {
3945
+ return this._latlng;
3946
+ },
3947
+
3834
3948
  setLatLng: function (latlng) {
3835
3949
  this._latlng = L.latLng(latlng);
3836
- this._update();
3950
+ if (this._map) {
3951
+ this._updatePosition();
3952
+ this._adjustPan();
3953
+ }
3837
3954
  return this;
3838
3955
  },
3839
3956
 
3957
+ getContent: function () {
3958
+ return this._content;
3959
+ },
3960
+
3840
3961
  setContent: function (content) {
3841
3962
  this._content = content;
3842
- this._update();
3963
+ this.update();
3843
3964
  return this;
3844
3965
  },
3845
3966
 
3967
+ update: function () {
3968
+ if (!this._map) { return; }
3969
+
3970
+ this._container.style.visibility = 'hidden';
3971
+
3972
+ this._updateContent();
3973
+ this._updateLayout();
3974
+ this._updatePosition();
3975
+
3976
+ this._container.style.visibility = '';
3977
+
3978
+ this._adjustPan();
3979
+ },
3980
+
3846
3981
  _getEvents: function () {
3847
3982
  var events = {
3848
3983
  viewreset: this._updatePosition
@@ -3889,27 +4024,14 @@ L.Popup = L.Class.extend({
3889
4024
  L.DomEvent.disableClickPropagation(wrapper);
3890
4025
 
3891
4026
  this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
3892
- L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
3893
- L.DomEvent.on(this._contentNode, 'MozMousePixelScroll', L.DomEvent.stopPropagation);
4027
+
4028
+ L.DomEvent.disableScrollPropagation(this._contentNode);
3894
4029
  L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
4030
+
3895
4031
  this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
3896
4032
  this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
3897
4033
  },
3898
4034
 
3899
- _update: function () {
3900
- if (!this._map) { return; }
3901
-
3902
- this._container.style.visibility = 'hidden';
3903
-
3904
- this._updateContent();
3905
- this._updateLayout();
3906
- this._updatePosition();
3907
-
3908
- this._container.style.visibility = '';
3909
-
3910
- this._adjustPan();
3911
- },
3912
-
3913
4035
  _updateContent: function () {
3914
4036
  if (!this._content) { return; }
3915
4037
 
@@ -3994,21 +4116,23 @@ L.Popup = L.Class.extend({
3994
4116
 
3995
4117
  var containerPos = map.layerPointToContainerPoint(layerPos),
3996
4118
  padding = L.point(this.options.autoPanPadding),
4119
+ paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
4120
+ paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
3997
4121
  size = map.getSize(),
3998
4122
  dx = 0,
3999
4123
  dy = 0;
4000
4124
 
4001
- if (containerPos.x + containerWidth > size.x) { // right
4002
- dx = containerPos.x + containerWidth - size.x + padding.x;
4125
+ if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
4126
+ dx = containerPos.x + containerWidth - size.x + paddingBR.x;
4003
4127
  }
4004
- if (containerPos.x - dx < 0) { // left
4005
- dx = containerPos.x - padding.x;
4128
+ if (containerPos.x - dx - paddingTL.x < 0) { // left
4129
+ dx = containerPos.x - paddingTL.x;
4006
4130
  }
4007
- if (containerPos.y + containerHeight > size.y) { // bottom
4008
- dy = containerPos.y + containerHeight - size.y + padding.y;
4131
+ if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
4132
+ dy = containerPos.y + containerHeight - size.y + paddingBR.y;
4009
4133
  }
4010
- if (containerPos.y - dy < 0) { // top
4011
- dy = containerPos.y - padding.y;
4134
+ if (containerPos.y - dy - paddingTL.y < 0) { // top
4135
+ dy = containerPos.y - paddingTL.y;
4012
4136
  }
4013
4137
 
4014
4138
  if (dx || dy) {
@@ -4103,11 +4227,12 @@ L.Marker.include({
4103
4227
 
4104
4228
  options = L.extend({offset: anchor}, options);
4105
4229
 
4106
- if (!this._popup) {
4230
+ if (!this._popupHandlersAdded) {
4107
4231
  this
4108
4232
  .on('click', this.togglePopup, this)
4109
4233
  .on('remove', this.closePopup, this)
4110
4234
  .on('move', this._movePopup, this);
4235
+ this._popupHandlersAdded = true;
4111
4236
  }
4112
4237
 
4113
4238
  if (content instanceof L.Popup) {
@@ -4132,13 +4257,18 @@ L.Marker.include({
4132
4257
  if (this._popup) {
4133
4258
  this._popup = null;
4134
4259
  this
4135
- .off('click', this.togglePopup)
4136
- .off('remove', this.closePopup)
4137
- .off('move', this._movePopup);
4260
+ .off('click', this.togglePopup, this)
4261
+ .off('remove', this.closePopup, this)
4262
+ .off('move', this._movePopup, this);
4263
+ this._popupHandlersAdded = false;
4138
4264
  }
4139
4265
  return this;
4140
4266
  },
4141
4267
 
4268
+ getPopup: function () {
4269
+ return this._popup;
4270
+ },
4271
+
4142
4272
  _movePopup: function (e) {
4143
4273
  this._popup.setLatLng(e.latlng);
4144
4274
  }
@@ -4279,7 +4409,9 @@ L.FeatureGroup = L.LayerGroup.extend({
4279
4409
  return this;
4280
4410
  }
4281
4411
 
4282
- layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
4412
+ if ('on' in layer) {
4413
+ layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
4414
+ }
4283
4415
 
4284
4416
  L.LayerGroup.prototype.addLayer.call(this, layer);
4285
4417
 
@@ -4315,6 +4447,15 @@ L.FeatureGroup = L.LayerGroup.extend({
4315
4447
  return this.invoke('bindPopup', content, options);
4316
4448
  },
4317
4449
 
4450
+ openPopup: function (latlng) {
4451
+ // open popup on the first layer
4452
+ for (var id in this._layers) {
4453
+ this._layers[id].openPopup(latlng);
4454
+ break;
4455
+ }
4456
+ return this;
4457
+ },
4458
+
4318
4459
  setStyle: function (style) {
4319
4460
  return this.invoke('setStyle', style);
4320
4461
  },
@@ -4338,11 +4479,10 @@ L.FeatureGroup = L.LayerGroup.extend({
4338
4479
  },
4339
4480
 
4340
4481
  _propagateEvent: function (e) {
4341
- if (!e.layer) {
4342
- e.layer = e.target;
4343
- }
4344
- e.target = this;
4345
-
4482
+ e = L.extend({}, e, {
4483
+ layer: e.target,
4484
+ target: this
4485
+ });
4346
4486
  this.fire(e.type, e);
4347
4487
  }
4348
4488
  });
@@ -4374,6 +4514,8 @@ L.Path = L.Class.extend({
4374
4514
  stroke: true,
4375
4515
  color: '#0033ff',
4376
4516
  dashArray: null,
4517
+ lineCap: null,
4518
+ lineJoin: null,
4377
4519
  weight: 5,
4378
4520
  opacity: 0.5,
4379
4521
 
@@ -4523,6 +4665,11 @@ L.Path = L.Path.extend({
4523
4665
  this._container = this._createElement('g');
4524
4666
 
4525
4667
  this._path = this._createElement('path');
4668
+
4669
+ if (this.options.className) {
4670
+ L.DomUtil.addClass(this._path, this.options.className);
4671
+ }
4672
+
4526
4673
  this._container.appendChild(this._path);
4527
4674
  },
4528
4675
 
@@ -4553,6 +4700,12 @@ L.Path = L.Path.extend({
4553
4700
  } else {
4554
4701
  this._path.removeAttribute('stroke-dasharray');
4555
4702
  }
4703
+ if (this.options.lineCap) {
4704
+ this._path.setAttribute('stroke-linecap', this.options.lineCap);
4705
+ }
4706
+ if (this.options.lineJoin) {
4707
+ this._path.setAttribute('stroke-linejoin', this.options.lineJoin);
4708
+ }
4556
4709
  } else {
4557
4710
  this._path.setAttribute('stroke', 'none');
4558
4711
  }
@@ -4577,7 +4730,7 @@ L.Path = L.Path.extend({
4577
4730
  _initEvents: function () {
4578
4731
  if (this.options.clickable) {
4579
4732
  if (L.Browser.svg || !L.Browser.vml) {
4580
- this._path.setAttribute('class', 'leaflet-clickable');
4733
+ L.DomUtil.addClass(this._path, 'leaflet-clickable');
4581
4734
  }
4582
4735
 
4583
4736
  L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
@@ -4627,14 +4780,14 @@ L.Map.include({
4627
4780
  this._panes.overlayPane.appendChild(this._pathRoot);
4628
4781
 
4629
4782
  if (this.options.zoomAnimation && L.Browser.any3d) {
4630
- this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
4783
+ L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated');
4631
4784
 
4632
4785
  this.on({
4633
4786
  'zoomanim': this._animatePathZoom,
4634
4787
  'zoomend': this._endPathZoom
4635
4788
  });
4636
4789
  } else {
4637
- this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
4790
+ L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide');
4638
4791
  }
4639
4792
 
4640
4793
  this.on('moveend', this._updateSvgViewport);
@@ -4801,10 +4954,14 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
4801
4954
 
4802
4955
  _initPath: function () {
4803
4956
  var container = this._container = this._createElement('shape');
4804
- L.DomUtil.addClass(container, 'leaflet-vml-shape');
4957
+
4958
+ L.DomUtil.addClass(container, 'leaflet-vml-shape' +
4959
+ (this.options.className ? ' ' + this.options.className : ''));
4960
+
4805
4961
  if (this.options.clickable) {
4806
4962
  L.DomUtil.addClass(container, 'leaflet-clickable');
4807
4963
  }
4964
+
4808
4965
  container.coordsize = '1 1';
4809
4966
 
4810
4967
  this._path = this._createElement('path');
@@ -4837,12 +4994,18 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
4837
4994
  stroke.opacity = options.opacity;
4838
4995
 
4839
4996
  if (options.dashArray) {
4840
- stroke.dashStyle = options.dashArray instanceof Array ?
4997
+ stroke.dashStyle = L.Util.isArray(options.dashArray) ?
4841
4998
  options.dashArray.join(' ') :
4842
4999
  options.dashArray.replace(/( *, *)/g, ' ');
4843
5000
  } else {
4844
5001
  stroke.dashStyle = '';
4845
5002
  }
5003
+ if (options.lineCap) {
5004
+ stroke.endcap = options.lineCap.replace('butt', 'flat');
5005
+ }
5006
+ if (options.lineJoin) {
5007
+ stroke.joinstyle = options.lineJoin;
5008
+ }
4846
5009
 
4847
5010
  } else if (stroke) {
4848
5011
  container.removeChild(stroke);
@@ -5522,10 +5685,12 @@ L.Polygon = L.Polyline.extend({
5522
5685
  },
5523
5686
 
5524
5687
  initialize: function (latlngs, options) {
5525
- var i, len, hole;
5526
-
5527
5688
  L.Polyline.prototype.initialize.call(this, latlngs, options);
5689
+ this._initWithHoles(latlngs);
5690
+ },
5528
5691
 
5692
+ _initWithHoles: function (latlngs) {
5693
+ var i, len, hole;
5529
5694
  if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
5530
5695
  this._latlngs = this._convertLatLngs(latlngs[0]);
5531
5696
  this._holes = latlngs.slice(1);
@@ -5566,6 +5731,15 @@ L.Polygon = L.Polyline.extend({
5566
5731
  }
5567
5732
  },
5568
5733
 
5734
+ setLatLngs: function (latlngs) {
5735
+ if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
5736
+ this._initWithHoles(latlngs);
5737
+ return this.redraw();
5738
+ } else {
5739
+ return L.Polyline.prototype.setLatLngs.call(this, latlngs);
5740
+ }
5741
+ },
5742
+
5569
5743
  _clipPoints: function () {
5570
5744
  var points = this._originalPoints,
5571
5745
  newParts = [];
@@ -5807,9 +5981,20 @@ L.CircleMarker = L.Circle.extend({
5807
5981
  this.setRadius(this.options.radius);
5808
5982
  },
5809
5983
 
5984
+ setLatLng: function (latlng) {
5985
+ L.Circle.prototype.setLatLng.call(this, latlng);
5986
+ if (this._popup && this._popup._isOpen) {
5987
+ this._popup.setLatLng(latlng);
5988
+ }
5989
+ },
5990
+
5810
5991
  setRadius: function (radius) {
5811
5992
  this.options.radius = this._radius = radius;
5812
5993
  return this.redraw();
5994
+ },
5995
+
5996
+ getRadius: function () {
5997
+ return this._radius;
5813
5998
  }
5814
5999
  });
5815
6000
 
@@ -5955,7 +6140,7 @@ L.GeoJSON = L.FeatureGroup.extend({
5955
6140
 
5956
6141
  if (options.filter && !options.filter(geojson)) { return; }
5957
6142
 
5958
- var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng);
6143
+ var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options);
5959
6144
  layer.feature = L.GeoJSON.asFeature(geojson);
5960
6145
 
5961
6146
  layer.defaultOptions = layer.options;
@@ -5995,11 +6180,11 @@ L.GeoJSON = L.FeatureGroup.extend({
5995
6180
  });
5996
6181
 
5997
6182
  L.extend(L.GeoJSON, {
5998
- geometryToLayer: function (geojson, pointToLayer, coordsToLatLng) {
6183
+ geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) {
5999
6184
  var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
6000
6185
  coords = geometry.coordinates,
6001
6186
  layers = [],
6002
- latlng, latlngs, i, len, layer;
6187
+ latlng, latlngs, i, len;
6003
6188
 
6004
6189
  coordsToLatLng = coordsToLatLng || this.coordsToLatLng;
6005
6190
 
@@ -6011,37 +6196,37 @@ L.extend(L.GeoJSON, {
6011
6196
  case 'MultiPoint':
6012
6197
  for (i = 0, len = coords.length; i < len; i++) {
6013
6198
  latlng = coordsToLatLng(coords[i]);
6014
- layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
6015
- layers.push(layer);
6199
+ layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
6016
6200
  }
6017
6201
  return new L.FeatureGroup(layers);
6018
6202
 
6019
6203
  case 'LineString':
6020
6204
  latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);
6021
- return new L.Polyline(latlngs);
6205
+ return new L.Polyline(latlngs, vectorOptions);
6022
6206
 
6023
6207
  case 'Polygon':
6208
+ if (coords.length === 2 && !coords[1].length) {
6209
+ throw new Error('Invalid GeoJSON object.');
6210
+ }
6024
6211
  latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
6025
- return new L.Polygon(latlngs);
6212
+ return new L.Polygon(latlngs, vectorOptions);
6026
6213
 
6027
6214
  case 'MultiLineString':
6028
6215
  latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
6029
- return new L.MultiPolyline(latlngs);
6216
+ return new L.MultiPolyline(latlngs, vectorOptions);
6030
6217
 
6031
6218
  case 'MultiPolygon':
6032
6219
  latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);
6033
- return new L.MultiPolygon(latlngs);
6220
+ return new L.MultiPolygon(latlngs, vectorOptions);
6034
6221
 
6035
6222
  case 'GeometryCollection':
6036
6223
  for (i = 0, len = geometry.geometries.length; i < len; i++) {
6037
6224
 
6038
- layer = this.geometryToLayer({
6225
+ layers.push(this.geometryToLayer({
6039
6226
  geometry: geometry.geometries[i],
6040
6227
  type: 'Feature',
6041
6228
  properties: geojson.properties
6042
- }, pointToLayer, coordsToLatLng);
6043
-
6044
- layers.push(layer);
6229
+ }, pointToLayer, coordsToLatLng, vectorOptions));
6045
6230
  }
6046
6231
  return new L.FeatureGroup(layers);
6047
6232
 
@@ -6051,7 +6236,7 @@ L.extend(L.GeoJSON, {
6051
6236
  },
6052
6237
 
6053
6238
  coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng
6054
- return new L.LatLng(coords[1], coords[0]);
6239
+ return new L.LatLng(coords[1], coords[0], coords[2]);
6055
6240
  },
6056
6241
 
6057
6242
  coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
@@ -6069,8 +6254,13 @@ L.extend(L.GeoJSON, {
6069
6254
  return latlngs;
6070
6255
  },
6071
6256
 
6072
- latLngToCoords: function (latLng) {
6073
- return [latLng.lng, latLng.lat];
6257
+ latLngToCoords: function (latlng) {
6258
+ var coords = [latlng.lng, latlng.lat];
6259
+
6260
+ if (latlng.alt !== undefined) {
6261
+ coords.push(latlng.alt);
6262
+ }
6263
+ return coords;
6074
6264
  },
6075
6265
 
6076
6266
  latLngsToCoords: function (latLngs) {
@@ -6145,43 +6335,58 @@ L.Polygon.include({
6145
6335
  });
6146
6336
 
6147
6337
  (function () {
6148
- function includeMulti(Klass, type) {
6149
- Klass.include({
6150
- toGeoJSON: function () {
6151
- var coords = [];
6338
+ function multiToGeoJSON(type) {
6339
+ return function () {
6340
+ var coords = [];
6152
6341
 
6153
- this.eachLayer(function (layer) {
6154
- coords.push(layer.toGeoJSON().geometry.coordinates);
6155
- });
6342
+ this.eachLayer(function (layer) {
6343
+ coords.push(layer.toGeoJSON().geometry.coordinates);
6344
+ });
6156
6345
 
6157
- return L.GeoJSON.getFeature(this, {
6158
- type: type,
6159
- coordinates: coords
6160
- });
6161
- }
6162
- });
6346
+ return L.GeoJSON.getFeature(this, {
6347
+ type: type,
6348
+ coordinates: coords
6349
+ });
6350
+ };
6163
6351
  }
6164
6352
 
6165
- includeMulti(L.MultiPolyline, 'MultiLineString');
6166
- includeMulti(L.MultiPolygon, 'MultiPolygon');
6167
- }());
6353
+ L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')});
6354
+ L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')});
6168
6355
 
6169
- L.LayerGroup.include({
6170
- toGeoJSON: function () {
6171
- var features = [];
6356
+ L.LayerGroup.include({
6357
+ toGeoJSON: function () {
6172
6358
 
6173
- this.eachLayer(function (layer) {
6174
- if (layer.toGeoJSON) {
6175
- features.push(L.GeoJSON.asFeature(layer.toGeoJSON()));
6359
+ var geometry = this.feature && this.feature.geometry,
6360
+ jsons = [],
6361
+ json;
6362
+
6363
+ if (geometry && geometry.type === 'MultiPoint') {
6364
+ return multiToGeoJSON('MultiPoint').call(this);
6176
6365
  }
6177
- });
6178
6366
 
6179
- return {
6180
- type: 'FeatureCollection',
6181
- features: features
6182
- };
6183
- }
6184
- });
6367
+ var isGeometryCollection = geometry && geometry.type === 'GeometryCollection';
6368
+
6369
+ this.eachLayer(function (layer) {
6370
+ if (layer.toGeoJSON) {
6371
+ json = layer.toGeoJSON();
6372
+ jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
6373
+ }
6374
+ });
6375
+
6376
+ if (isGeometryCollection) {
6377
+ return L.GeoJSON.getFeature(this, {
6378
+ geometries: jsons,
6379
+ type: 'GeometryCollection'
6380
+ });
6381
+ }
6382
+
6383
+ return {
6384
+ type: 'FeatureCollection',
6385
+ features: jsons
6386
+ };
6387
+ }
6388
+ });
6389
+ }());
6185
6390
 
6186
6391
  L.geoJson = function (geojson, options) {
6187
6392
  return new L.GeoJSON(geojson, options);
@@ -6206,8 +6411,8 @@ L.DomEvent = {
6206
6411
  return fn.call(context || obj, e || L.DomEvent._getEvent());
6207
6412
  };
6208
6413
 
6209
- if (L.Browser.msTouch && type.indexOf('touch') === 0) {
6210
- return this.addMsTouchListener(obj, type, handler, id);
6414
+ if (L.Browser.pointer && type.indexOf('touch') === 0) {
6415
+ return this.addPointerListener(obj, type, handler, id);
6211
6416
  }
6212
6417
  if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
6213
6418
  this.addDoubleTapListener(obj, handler, id);
@@ -6259,8 +6464,8 @@ L.DomEvent = {
6259
6464
 
6260
6465
  if (!handler) { return this; }
6261
6466
 
6262
- if (L.Browser.msTouch && type.indexOf('touch') === 0) {
6263
- this.removeMsTouchListener(obj, type, id);
6467
+ if (L.Browser.pointer && type.indexOf('touch') === 0) {
6468
+ this.removePointerListener(obj, type, id);
6264
6469
  } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
6265
6470
  this.removeDoubleTapListener(obj, id);
6266
6471
 
@@ -6291,19 +6496,29 @@ L.DomEvent = {
6291
6496
  } else {
6292
6497
  e.cancelBubble = true;
6293
6498
  }
6499
+ L.DomEvent._skipped(e);
6500
+
6294
6501
  return this;
6295
6502
  },
6296
6503
 
6504
+ disableScrollPropagation: function (el) {
6505
+ var stop = L.DomEvent.stopPropagation;
6506
+
6507
+ return L.DomEvent
6508
+ .on(el, 'mousewheel', stop)
6509
+ .on(el, 'MozMousePixelScroll', stop);
6510
+ },
6511
+
6297
6512
  disableClickPropagation: function (el) {
6298
6513
  var stop = L.DomEvent.stopPropagation;
6299
6514
 
6300
6515
  for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
6301
- L.DomEvent.addListener(el, L.Draggable.START[i], stop);
6516
+ L.DomEvent.on(el, L.Draggable.START[i], stop);
6302
6517
  }
6303
6518
 
6304
6519
  return L.DomEvent
6305
- .addListener(el, 'click', L.DomEvent._fakeStop)
6306
- .addListener(el, 'dblclick', stop);
6520
+ .on(el, 'click', L.DomEvent._fakeStop)
6521
+ .on(el, 'dblclick', stop);
6307
6522
  },
6308
6523
 
6309
6524
  preventDefault: function (e) {
@@ -6317,34 +6532,31 @@ L.DomEvent = {
6317
6532
  },
6318
6533
 
6319
6534
  stop: function (e) {
6320
- return L.DomEvent.preventDefault(e).stopPropagation(e);
6535
+ return L.DomEvent
6536
+ .preventDefault(e)
6537
+ .stopPropagation(e);
6321
6538
  },
6322
6539
 
6323
6540
  getMousePosition: function (e, container) {
6324
-
6325
- var ie7 = L.Browser.ie7,
6326
- body = document.body,
6541
+ var body = document.body,
6327
6542
  docEl = document.documentElement,
6328
- x = e.pageX ? e.pageX - body.scrollLeft - docEl.scrollLeft: e.clientX,
6543
+ //gecko makes scrollLeft more negative as you scroll in rtl, other browsers don't
6544
+ //ref: https://code.google.com/p/closure-library/source/browse/closure/goog/style/bidi.js
6545
+ x = L.DomUtil.documentIsLtr() ?
6546
+ (e.pageX ? e.pageX - body.scrollLeft - docEl.scrollLeft : e.clientX) :
6547
+ (L.Browser.gecko ? e.pageX - body.scrollLeft - docEl.scrollLeft :
6548
+ e.pageX ? e.pageX - body.scrollLeft + docEl.scrollLeft : e.clientX),
6329
6549
  y = e.pageY ? e.pageY - body.scrollTop - docEl.scrollTop: e.clientY,
6330
- pos = new L.Point(x, y),
6331
- rect = container.getBoundingClientRect(),
6332
- left = rect.left - container.clientLeft,
6333
- top = rect.top - container.clientTop;
6550
+ pos = new L.Point(x, y);
6334
6551
 
6335
- // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
6336
- // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
6337
- if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
6338
- left += container.scrollWidth - container.clientWidth;
6339
-
6340
- // ie7 shows the scrollbar by default and provides clientWidth counting it, so we
6341
- // need to add it back in if it is visible; scrollbar is on the left as we are RTL
6342
- if (ie7 && L.DomUtil.getStyle(container, 'overflow-y') !== 'hidden' &&
6343
- L.DomUtil.getStyle(container, 'overflow') !== 'hidden') {
6344
- left += 17;
6345
- }
6552
+ if (!container) {
6553
+ return pos;
6346
6554
  }
6347
6555
 
6556
+ var rect = container.getBoundingClientRect(),
6557
+ left = rect.left - container.clientLeft,
6558
+ top = rect.top - container.clientTop;
6559
+
6348
6560
  return pos._subtract(new L.Point(left, top));
6349
6561
  },
6350
6562
 
@@ -6444,11 +6656,13 @@ L.Draggable = L.Class.extend({
6444
6656
  END: {
6445
6657
  mousedown: 'mouseup',
6446
6658
  touchstart: 'touchend',
6659
+ pointerdown: 'touchend',
6447
6660
  MSPointerDown: 'touchend'
6448
6661
  },
6449
6662
  MOVE: {
6450
6663
  mousedown: 'mousemove',
6451
6664
  touchstart: 'touchmove',
6665
+ pointerdown: 'touchmove',
6452
6666
  MSPointerDown: 'touchmove'
6453
6667
  }
6454
6668
  },
@@ -6480,28 +6694,21 @@ L.Draggable = L.Class.extend({
6480
6694
  },
6481
6695
 
6482
6696
  _onDown: function (e) {
6697
+ this._moved = false;
6698
+
6483
6699
  if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
6484
6700
 
6485
- L.DomEvent
6486
- .stopPropagation(e);
6701
+ L.DomEvent.stopPropagation(e);
6487
6702
 
6488
6703
  if (L.Draggable._disabled) { return; }
6489
6704
 
6490
6705
  L.DomUtil.disableImageDrag();
6491
6706
  L.DomUtil.disableTextSelection();
6492
6707
 
6493
- var first = e.touches ? e.touches[0] : e,
6494
- el = first.target;
6495
-
6496
- // if touching a link, highlight it
6497
- if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
6498
- L.DomUtil.addClass(el, 'leaflet-active');
6499
- }
6500
-
6501
- this._moved = false;
6502
-
6503
6708
  if (this._moving) { return; }
6504
6709
 
6710
+ var first = e.touches ? e.touches[0] : e;
6711
+
6505
6712
  this._startPoint = new L.Point(first.clientX, first.clientY);
6506
6713
  this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
6507
6714
 
@@ -6511,7 +6718,10 @@ L.Draggable = L.Class.extend({
6511
6718
  },
6512
6719
 
6513
6720
  _onMove: function (e) {
6514
- if (e.touches && e.touches.length > 1) { return; }
6721
+ if (e.touches && e.touches.length > 1) {
6722
+ this._moved = true;
6723
+ return;
6724
+ }
6515
6725
 
6516
6726
  var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
6517
6727
  newPoint = new L.Point(first.clientX, first.clientY),
@@ -6527,9 +6737,8 @@ L.Draggable = L.Class.extend({
6527
6737
  this._moved = true;
6528
6738
  this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
6529
6739
 
6530
- if (!L.Browser.touch) {
6531
- L.DomUtil.addClass(document.body, 'leaflet-dragging');
6532
- }
6740
+ L.DomUtil.addClass(document.body, 'leaflet-dragging');
6741
+ L.DomUtil.addClass((e.target || e.srcElement), 'leaflet-drag-target');
6533
6742
  }
6534
6743
 
6535
6744
  this._newPos = this._startPos.add(offset);
@@ -6545,10 +6754,9 @@ L.Draggable = L.Class.extend({
6545
6754
  this.fire('drag');
6546
6755
  },
6547
6756
 
6548
- _onUp: function () {
6549
- if (!L.Browser.touch) {
6550
- L.DomUtil.removeClass(document.body, 'leaflet-dragging');
6551
- }
6757
+ _onUp: function (e) {
6758
+ L.DomUtil.removeClass(document.body, 'leaflet-dragging');
6759
+ L.DomUtil.removeClass((e.target || e.srcElement), 'leaflet-drag-target');
6552
6760
 
6553
6761
  for (var i in L.Draggable.MOVE) {
6554
6762
  L.DomEvent
@@ -6563,7 +6771,9 @@ L.Draggable = L.Class.extend({
6563
6771
  // ensure drag is not fired after dragend
6564
6772
  L.Util.cancelAnimFrame(this._animRequest);
6565
6773
 
6566
- this.fire('dragend');
6774
+ this.fire('dragend', {
6775
+ distance: this._newPos.distanceTo(this._startPos)
6776
+ });
6567
6777
  }
6568
6778
 
6569
6779
  this._moving = false;
@@ -6635,7 +6845,7 @@ L.Map.Drag = L.Handler.extend({
6635
6845
  this._draggable.on('predrag', this._onPreDrag, this);
6636
6846
  map.on('viewreset', this._onViewReset, this);
6637
6847
 
6638
- this._onViewReset();
6848
+ map.whenReady(this._onViewReset, this);
6639
6849
  }
6640
6850
  }
6641
6851
  this._draggable.enable();
@@ -6707,14 +6917,14 @@ L.Map.Drag = L.Handler.extend({
6707
6917
  this._draggable._newPos.x = newX;
6708
6918
  },
6709
6919
 
6710
- _onDragEnd: function () {
6920
+ _onDragEnd: function (e) {
6711
6921
  var map = this._map,
6712
6922
  options = map.options,
6713
6923
  delay = +new Date() - this._lastTime,
6714
6924
 
6715
6925
  noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
6716
6926
 
6717
- map.fire('dragend');
6927
+ map.fire('dragend', e);
6718
6928
 
6719
6929
  if (noInertia) {
6720
6930
  map.fire('moveend');
@@ -6738,6 +6948,8 @@ L.Map.Drag = L.Handler.extend({
6738
6948
  map.fire('moveend');
6739
6949
 
6740
6950
  } else {
6951
+ offset = map._limitOffset(offset, map.options.maxBounds);
6952
+
6741
6953
  L.Util.requestAnimFrame(function () {
6742
6954
  map.panBy(offset, {
6743
6955
  duration: decelerationDuration,
@@ -6763,15 +6975,22 @@ L.Map.mergeOptions({
6763
6975
 
6764
6976
  L.Map.DoubleClickZoom = L.Handler.extend({
6765
6977
  addHooks: function () {
6766
- this._map.on('dblclick', this._onDoubleClick);
6978
+ this._map.on('dblclick', this._onDoubleClick, this);
6767
6979
  },
6768
6980
 
6769
6981
  removeHooks: function () {
6770
- this._map.off('dblclick', this._onDoubleClick);
6982
+ this._map.off('dblclick', this._onDoubleClick, this);
6771
6983
  },
6772
6984
 
6773
6985
  _onDoubleClick: function (e) {
6774
- this.setZoomAround(e.containerPoint, this._zoom + 1);
6986
+ var map = this._map,
6987
+ zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1);
6988
+
6989
+ if (map.options.doubleClickZoom === 'center') {
6990
+ map.setZoom(zoom);
6991
+ } else {
6992
+ map.setZoomAround(e.containerPoint, zoom);
6993
+ }
6775
6994
  }
6776
6995
  });
6777
6996
 
@@ -6831,7 +7050,11 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
6831
7050
 
6832
7051
  if (!delta) { return; }
6833
7052
 
6834
- map.setZoomAround(this._lastMousePos, zoom + delta);
7053
+ if (map.options.scrollWheelZoom === 'center') {
7054
+ map.setZoom(zoom + delta);
7055
+ } else {
7056
+ map.setZoomAround(this._lastMousePos, zoom + delta);
7057
+ }
6835
7058
  }
6836
7059
  });
6837
7060
 
@@ -6844,8 +7067,8 @@ L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
6844
7067
 
6845
7068
  L.extend(L.DomEvent, {
6846
7069
 
6847
- _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
6848
- _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
7070
+ _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
7071
+ _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
6849
7072
 
6850
7073
  // inspired by Zepto touch code by Thomas Fuchs
6851
7074
  addDoubleTapListener: function (obj, handler, id) {
@@ -6861,7 +7084,7 @@ L.extend(L.DomEvent, {
6861
7084
  function onTouchStart(e) {
6862
7085
  var count;
6863
7086
 
6864
- if (L.Browser.msTouch) {
7087
+ if (L.Browser.pointer) {
6865
7088
  trackedTouches.push(e.pointerId);
6866
7089
  count = trackedTouches.length;
6867
7090
  } else {
@@ -6880,7 +7103,7 @@ L.extend(L.DomEvent, {
6880
7103
  }
6881
7104
 
6882
7105
  function onTouchEnd(e) {
6883
- if (L.Browser.msTouch) {
7106
+ if (L.Browser.pointer) {
6884
7107
  var idx = trackedTouches.indexOf(e.pointerId);
6885
7108
  if (idx === -1) {
6886
7109
  return;
@@ -6889,7 +7112,7 @@ L.extend(L.DomEvent, {
6889
7112
  }
6890
7113
 
6891
7114
  if (doubleTap) {
6892
- if (L.Browser.msTouch) {
7115
+ if (L.Browser.pointer) {
6893
7116
  // work around .type being readonly with MSPointer* events
6894
7117
  var newTouch = { },
6895
7118
  prop;
@@ -6913,15 +7136,15 @@ L.extend(L.DomEvent, {
6913
7136
  obj[pre + touchstart + id] = onTouchStart;
6914
7137
  obj[pre + touchend + id] = onTouchEnd;
6915
7138
 
6916
- // on msTouch we need to listen on the document, otherwise a drag starting on the map and moving off screen
7139
+ // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen
6917
7140
  // will not come through to us, so we will lose track of how many touches are ongoing
6918
- var endElement = L.Browser.msTouch ? document.documentElement : obj;
7141
+ var endElement = L.Browser.pointer ? document.documentElement : obj;
6919
7142
 
6920
7143
  obj.addEventListener(touchstart, onTouchStart, false);
6921
7144
  endElement.addEventListener(touchend, onTouchEnd, false);
6922
7145
 
6923
- if (L.Browser.msTouch) {
6924
- endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
7146
+ if (L.Browser.pointer) {
7147
+ endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false);
6925
7148
  }
6926
7149
 
6927
7150
  return this;
@@ -6931,11 +7154,12 @@ L.extend(L.DomEvent, {
6931
7154
  var pre = '_leaflet_';
6932
7155
 
6933
7156
  obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
6934
- (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(
7157
+ (L.Browser.pointer ? document.documentElement : obj).removeEventListener(
6935
7158
  this._touchend, obj[pre + this._touchend + id], false);
6936
7159
 
6937
- if (L.Browser.msTouch) {
6938
- document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
7160
+ if (L.Browser.pointer) {
7161
+ document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id],
7162
+ false);
6939
7163
  }
6940
7164
 
6941
7165
  return this;
@@ -6949,81 +7173,90 @@ L.extend(L.DomEvent, {
6949
7173
 
6950
7174
  L.extend(L.DomEvent, {
6951
7175
 
6952
- _msTouches: [],
6953
- _msDocumentListener: false,
7176
+ //static
7177
+ POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
7178
+ POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
7179
+ POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
7180
+ POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
7181
+
7182
+ _pointers: [],
7183
+ _pointerDocumentListener: false,
6954
7184
 
6955
- // Provides a touch events wrapper for msPointer events.
7185
+ // Provides a touch events wrapper for (ms)pointer events.
6956
7186
  // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
7187
+ //ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
6957
7188
 
6958
- addMsTouchListener: function (obj, type, handler, id) {
7189
+ addPointerListener: function (obj, type, handler, id) {
6959
7190
 
6960
7191
  switch (type) {
6961
7192
  case 'touchstart':
6962
- return this.addMsTouchListenerStart(obj, type, handler, id);
7193
+ return this.addPointerListenerStart(obj, type, handler, id);
6963
7194
  case 'touchend':
6964
- return this.addMsTouchListenerEnd(obj, type, handler, id);
7195
+ return this.addPointerListenerEnd(obj, type, handler, id);
6965
7196
  case 'touchmove':
6966
- return this.addMsTouchListenerMove(obj, type, handler, id);
7197
+ return this.addPointerListenerMove(obj, type, handler, id);
6967
7198
  default:
6968
7199
  throw 'Unknown touch event type';
6969
7200
  }
6970
7201
  },
6971
7202
 
6972
- addMsTouchListenerStart: function (obj, type, handler, id) {
7203
+ addPointerListenerStart: function (obj, type, handler, id) {
6973
7204
  var pre = '_leaflet_',
6974
- touches = this._msTouches;
7205
+ pointers = this._pointers;
6975
7206
 
6976
7207
  var cb = function (e) {
6977
7208
 
7209
+ L.DomEvent.preventDefault(e);
7210
+
6978
7211
  var alreadyInArray = false;
6979
- for (var i = 0; i < touches.length; i++) {
6980
- if (touches[i].pointerId === e.pointerId) {
7212
+ for (var i = 0; i < pointers.length; i++) {
7213
+ if (pointers[i].pointerId === e.pointerId) {
6981
7214
  alreadyInArray = true;
6982
7215
  break;
6983
7216
  }
6984
7217
  }
6985
7218
  if (!alreadyInArray) {
6986
- touches.push(e);
7219
+ pointers.push(e);
6987
7220
  }
6988
7221
 
6989
- e.touches = touches.slice();
7222
+ e.touches = pointers.slice();
6990
7223
  e.changedTouches = [e];
6991
7224
 
6992
7225
  handler(e);
6993
7226
  };
6994
7227
 
6995
7228
  obj[pre + 'touchstart' + id] = cb;
6996
- obj.addEventListener('MSPointerDown', cb, false);
7229
+ obj.addEventListener(this.POINTER_DOWN, cb, false);
6997
7230
 
6998
- // need to also listen for end events to keep the _msTouches list accurate
7231
+ // need to also listen for end events to keep the _pointers list accurate
6999
7232
  // this needs to be on the body and never go away
7000
- if (!this._msDocumentListener) {
7233
+ if (!this._pointerDocumentListener) {
7001
7234
  var internalCb = function (e) {
7002
- for (var i = 0; i < touches.length; i++) {
7003
- if (touches[i].pointerId === e.pointerId) {
7004
- touches.splice(i, 1);
7235
+ for (var i = 0; i < pointers.length; i++) {
7236
+ if (pointers[i].pointerId === e.pointerId) {
7237
+ pointers.splice(i, 1);
7005
7238
  break;
7006
7239
  }
7007
7240
  }
7008
7241
  };
7009
7242
  //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
7010
- document.documentElement.addEventListener('MSPointerUp', internalCb, false);
7011
- document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
7243
+ document.documentElement.addEventListener(this.POINTER_UP, internalCb, false);
7244
+ document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false);
7012
7245
 
7013
- this._msDocumentListener = true;
7246
+ this._pointerDocumentListener = true;
7014
7247
  }
7015
7248
 
7016
7249
  return this;
7017
7250
  },
7018
7251
 
7019
- addMsTouchListenerMove: function (obj, type, handler, id) {
7252
+ addPointerListenerMove: function (obj, type, handler, id) {
7020
7253
  var pre = '_leaflet_',
7021
- touches = this._msTouches;
7254
+ touches = this._pointers;
7022
7255
 
7023
7256
  function cb(e) {
7024
7257
 
7025
7258
  // don't fire touch moves when mouse isn't down
7026
- if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
7259
+ if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
7027
7260
 
7028
7261
  for (var i = 0; i < touches.length; i++) {
7029
7262
  if (touches[i].pointerId === e.pointerId) {
@@ -7039,14 +7272,14 @@ L.extend(L.DomEvent, {
7039
7272
  }
7040
7273
 
7041
7274
  obj[pre + 'touchmove' + id] = cb;
7042
- obj.addEventListener('MSPointerMove', cb, false);
7275
+ obj.addEventListener(this.POINTER_MOVE, cb, false);
7043
7276
 
7044
7277
  return this;
7045
7278
  },
7046
7279
 
7047
- addMsTouchListenerEnd: function (obj, type, handler, id) {
7280
+ addPointerListenerEnd: function (obj, type, handler, id) {
7048
7281
  var pre = '_leaflet_',
7049
- touches = this._msTouches;
7282
+ touches = this._pointers;
7050
7283
 
7051
7284
  var cb = function (e) {
7052
7285
  for (var i = 0; i < touches.length; i++) {
@@ -7063,26 +7296,26 @@ L.extend(L.DomEvent, {
7063
7296
  };
7064
7297
 
7065
7298
  obj[pre + 'touchend' + id] = cb;
7066
- obj.addEventListener('MSPointerUp', cb, false);
7067
- obj.addEventListener('MSPointerCancel', cb, false);
7299
+ obj.addEventListener(this.POINTER_UP, cb, false);
7300
+ obj.addEventListener(this.POINTER_CANCEL, cb, false);
7068
7301
 
7069
7302
  return this;
7070
7303
  },
7071
7304
 
7072
- removeMsTouchListener: function (obj, type, id) {
7305
+ removePointerListener: function (obj, type, id) {
7073
7306
  var pre = '_leaflet_',
7074
7307
  cb = obj[pre + type + id];
7075
7308
 
7076
7309
  switch (type) {
7077
7310
  case 'touchstart':
7078
- obj.removeEventListener('MSPointerDown', cb, false);
7311
+ obj.removeEventListener(this.POINTER_DOWN, cb, false);
7079
7312
  break;
7080
7313
  case 'touchmove':
7081
- obj.removeEventListener('MSPointerMove', cb, false);
7314
+ obj.removeEventListener(this.POINTER_MOVE, cb, false);
7082
7315
  break;
7083
7316
  case 'touchend':
7084
- obj.removeEventListener('MSPointerUp', cb, false);
7085
- obj.removeEventListener('MSPointerCancel', cb, false);
7317
+ obj.removeEventListener(this.POINTER_UP, cb, false);
7318
+ obj.removeEventListener(this.POINTER_CANCEL, cb, false);
7086
7319
  break;
7087
7320
  }
7088
7321
 
@@ -7096,7 +7329,8 @@ L.extend(L.DomEvent, {
7096
7329
  */
7097
7330
 
7098
7331
  L.Map.mergeOptions({
7099
- touchZoom: L.Browser.touch && !L.Browser.android23
7332
+ touchZoom: L.Browser.touch && !L.Browser.android23,
7333
+ bounceAtZoomLimits: true
7100
7334
  });
7101
7335
 
7102
7336
  L.Map.TouchZoom = L.Handler.extend({
@@ -7149,6 +7383,11 @@ L.Map.TouchZoom = L.Handler.extend({
7149
7383
 
7150
7384
  if (this._scale === 1) { return; }
7151
7385
 
7386
+ if (!map.options.bounceAtZoomLimits) {
7387
+ if ((map.getZoom() === map.getMinZoom() && this._scale < 1) ||
7388
+ (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; }
7389
+ }
7390
+
7152
7391
  if (!this._moved) {
7153
7392
  L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
7154
7393
 
@@ -7252,7 +7491,7 @@ L.Map.Tap = L.Handler.extend({
7252
7491
  this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
7253
7492
 
7254
7493
  // if touching a link, highlight it
7255
- if (el.tagName.toLowerCase() === 'a') {
7494
+ if (el.tagName && el.tagName.toLowerCase() === 'a') {
7256
7495
  L.DomUtil.addClass(el, 'leaflet-active');
7257
7496
  }
7258
7497
 
@@ -7282,7 +7521,7 @@ L.Map.Tap = L.Handler.extend({
7282
7521
  var first = e.changedTouches[0],
7283
7522
  el = first.target;
7284
7523
 
7285
- if (el.tagName.toLowerCase() === 'a') {
7524
+ if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
7286
7525
  L.DomUtil.removeClass(el, 'leaflet-active');
7287
7526
  }
7288
7527
 
@@ -7318,7 +7557,7 @@ L.Map.Tap = L.Handler.extend({
7318
7557
  }
7319
7558
  });
7320
7559
 
7321
- if (L.Browser.touch && !L.Browser.msTouch) {
7560
+ if (L.Browser.touch && !L.Browser.pointer) {
7322
7561
  L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
7323
7562
  }
7324
7563
 
@@ -7337,6 +7576,7 @@ L.Map.BoxZoom = L.Handler.extend({
7337
7576
  this._map = map;
7338
7577
  this._container = map._container;
7339
7578
  this._pane = map._panes.overlayPane;
7579
+ this._moved = false;
7340
7580
  },
7341
7581
 
7342
7582
  addHooks: function () {
@@ -7345,9 +7585,16 @@ L.Map.BoxZoom = L.Handler.extend({
7345
7585
 
7346
7586
  removeHooks: function () {
7347
7587
  L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
7588
+ this._moved = false;
7589
+ },
7590
+
7591
+ moved: function () {
7592
+ return this._moved;
7348
7593
  },
7349
7594
 
7350
7595
  _onMouseDown: function (e) {
7596
+ this._moved = false;
7597
+
7351
7598
  if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
7352
7599
 
7353
7600
  L.DomUtil.disableTextSelection();
@@ -7355,21 +7602,22 @@ L.Map.BoxZoom = L.Handler.extend({
7355
7602
 
7356
7603
  this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
7357
7604
 
7358
- this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
7359
- L.DomUtil.setPosition(this._box, this._startLayerPoint);
7360
-
7361
- //TODO refactor: move cursor to styles
7362
- this._container.style.cursor = 'crosshair';
7363
-
7364
7605
  L.DomEvent
7365
7606
  .on(document, 'mousemove', this._onMouseMove, this)
7366
7607
  .on(document, 'mouseup', this._onMouseUp, this)
7367
7608
  .on(document, 'keydown', this._onKeyDown, this);
7368
-
7369
- this._map.fire('boxzoomstart');
7370
7609
  },
7371
7610
 
7372
7611
  _onMouseMove: function (e) {
7612
+ if (!this._moved) {
7613
+ this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
7614
+ L.DomUtil.setPosition(this._box, this._startLayerPoint);
7615
+
7616
+ //TODO refactor: move cursor to styles
7617
+ this._container.style.cursor = 'crosshair';
7618
+ this._map.fire('boxzoomstart');
7619
+ }
7620
+
7373
7621
  var startPoint = this._startLayerPoint,
7374
7622
  box = this._box,
7375
7623
 
@@ -7382,14 +7630,18 @@ L.Map.BoxZoom = L.Handler.extend({
7382
7630
 
7383
7631
  L.DomUtil.setPosition(box, newPos);
7384
7632
 
7633
+ this._moved = true;
7634
+
7385
7635
  // TODO refactor: remove hardcoded 4 pixels
7386
7636
  box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
7387
7637
  box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
7388
7638
  },
7389
7639
 
7390
7640
  _finish: function () {
7391
- this._pane.removeChild(this._box);
7392
- this._container.style.cursor = '';
7641
+ if (this._moved) {
7642
+ this._pane.removeChild(this._box);
7643
+ this._container.style.cursor = '';
7644
+ }
7393
7645
 
7394
7646
  L.DomUtil.enableTextSelection();
7395
7647
  L.DomUtil.enableImageDrag();
@@ -7447,7 +7699,7 @@ L.Map.Keyboard = L.Handler.extend({
7447
7699
  right: [39],
7448
7700
  down: [40],
7449
7701
  up: [38],
7450
- zoomIn: [187, 107, 61],
7702
+ zoomIn: [187, 107, 61, 171],
7451
7703
  zoomOut: [189, 109, 173]
7452
7704
  },
7453
7705
 
@@ -7602,6 +7854,7 @@ L.Handler.MarkerDrag = L.Handler.extend({
7602
7854
  .on('drag', this._onDrag, this)
7603
7855
  .on('dragend', this._onDragEnd, this);
7604
7856
  this._draggable.enable();
7857
+ L.DomUtil.addClass(this._marker._icon, 'leaflet-marker-draggable');
7605
7858
  },
7606
7859
 
7607
7860
  removeHooks: function () {
@@ -7611,6 +7864,7 @@ L.Handler.MarkerDrag = L.Handler.extend({
7611
7864
  .off('dragend', this._onDragEnd, this);
7612
7865
 
7613
7866
  this._draggable.disable();
7867
+ L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
7614
7868
  },
7615
7869
 
7616
7870
  moved: function () {
@@ -7642,10 +7896,10 @@ L.Handler.MarkerDrag = L.Handler.extend({
7642
7896
  .fire('drag');
7643
7897
  },
7644
7898
 
7645
- _onDragEnd: function () {
7899
+ _onDragEnd: function (e) {
7646
7900
  this._marker
7647
7901
  .fire('moveend')
7648
- .fire('dragend');
7902
+ .fire('dragend', e);
7649
7903
  }
7650
7904
  });
7651
7905
 
@@ -7718,6 +7972,12 @@ L.Control = L.Class.extend({
7718
7972
  }
7719
7973
 
7720
7974
  return this;
7975
+ },
7976
+
7977
+ _refocusOnMap: function () {
7978
+ if (this._map) {
7979
+ this._map.getContainer().focus();
7980
+ }
7721
7981
  }
7722
7982
  });
7723
7983
 
@@ -7769,7 +8029,11 @@ L.Map.include({
7769
8029
 
7770
8030
  L.Control.Zoom = L.Control.extend({
7771
8031
  options: {
7772
- position: 'topleft'
8032
+ position: 'topleft',
8033
+ zoomInText: '+',
8034
+ zoomInTitle: 'Zoom in',
8035
+ zoomOutText: '-',
8036
+ zoomOutTitle: 'Zoom out'
7773
8037
  },
7774
8038
 
7775
8039
  onAdd: function (map) {
@@ -7779,10 +8043,13 @@ L.Control.Zoom = L.Control.extend({
7779
8043
  this._map = map;
7780
8044
 
7781
8045
  this._zoomInButton = this._createButton(
7782
- '+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this);
8046
+ this.options.zoomInText, this.options.zoomInTitle,
8047
+ zoomName + '-in', container, this._zoomIn, this);
7783
8048
  this._zoomOutButton = this._createButton(
7784
- '-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this);
8049
+ this.options.zoomOutText, this.options.zoomOutTitle,
8050
+ zoomName + '-out', container, this._zoomOut, this);
7785
8051
 
8052
+ this._updateDisabled();
7786
8053
  map.on('zoomend zoomlevelschange', this._updateDisabled, this);
7787
8054
 
7788
8055
  return container;
@@ -7813,7 +8080,8 @@ L.Control.Zoom = L.Control.extend({
7813
8080
  .on(link, 'mousedown', stop)
7814
8081
  .on(link, 'dblclick', stop)
7815
8082
  .on(link, 'click', L.DomEvent.preventDefault)
7816
- .on(link, 'click', fn, context);
8083
+ .on(link, 'click', fn, context)
8084
+ .on(link, 'click', this._refocusOnMap, context);
7817
8085
 
7818
8086
  return link;
7819
8087
  },
@@ -7871,6 +8139,12 @@ L.Control.Attribution = L.Control.extend({
7871
8139
  this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
7872
8140
  L.DomEvent.disableClickPropagation(this._container);
7873
8141
 
8142
+ for (var i in map._layers) {
8143
+ if (map._layers[i].getAttribution) {
8144
+ this.addAttribution(map._layers[i].getAttribution());
8145
+ }
8146
+ }
8147
+
7874
8148
  map
7875
8149
  .on('layeradd', this._onLayerAdd, this)
7876
8150
  .on('layerremove', this._onLayerRemove, this);
@@ -8153,8 +8427,9 @@ L.Control.Layers = L.Control.extend({
8153
8427
  container.setAttribute('aria-haspopup', true);
8154
8428
 
8155
8429
  if (!L.Browser.touch) {
8156
- L.DomEvent.disableClickPropagation(container);
8157
- L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
8430
+ L.DomEvent
8431
+ .disableClickPropagation(container)
8432
+ .disableScrollPropagation(container);
8158
8433
  } else {
8159
8434
  L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
8160
8435
  }
@@ -8179,6 +8454,10 @@ L.Control.Layers = L.Control.extend({
8179
8454
  else {
8180
8455
  L.DomEvent.on(link, 'focus', this._expand, this);
8181
8456
  }
8457
+ //Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033
8458
+ L.DomEvent.on(form, 'click', function () {
8459
+ setTimeout(L.bind(this._onInputClick, this), 0);
8460
+ }, this);
8182
8461
 
8183
8462
  this._map.on('click', this._collapse, this);
8184
8463
  // TODO keyboard accessibility
@@ -8313,6 +8592,8 @@ L.Control.Layers = L.Control.extend({
8313
8592
  }
8314
8593
 
8315
8594
  this._handlingClick = false;
8595
+
8596
+ this._refocusOnMap();
8316
8597
  },
8317
8598
 
8318
8599
  _expand: function () {
@@ -8433,8 +8714,8 @@ L.Map.include({
8433
8714
 
8434
8715
  setView: function (center, zoom, options) {
8435
8716
 
8436
- zoom = this._limitZoom(zoom);
8437
- center = L.latLng(center);
8717
+ zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
8718
+ center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
8438
8719
  options = options || {};
8439
8720
 
8440
8721
  if (this._panAnim) {
@@ -8890,7 +9171,8 @@ L.Map.include({
8890
9171
 
8891
9172
  var data = {
8892
9173
  latlng: latlng,
8893
- bounds: bounds
9174
+ bounds: bounds,
9175
+ timestamp: pos.timestamp
8894
9176
  };
8895
9177
 
8896
9178
  for (var i in pos.coords) {