leaflet-rails 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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) {