leaflet-mapbox-gl.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. (function (root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. // AMD
  4. define(['leaflet', 'mapbox-gl'], factory);
  5. } else if (typeof exports === 'object') {
  6. // Node, CommonJS-like
  7. module.exports = factory(require('leaflet'), require('mapbox-gl'));
  8. } else {
  9. // Browser globals (root is window)
  10. root.returnExports = factory(window.L, window.mapboxgl);
  11. }
  12. }(this, function (L, mapboxgl) {
  13. L.MapboxGL = L.Layer.extend({
  14. options: {
  15. updateInterval: 32,
  16. // How much to extend the overlay view (relative to map size)
  17. // e.g. 0.1 would be 10% of map view in each direction
  18. padding: 0.1,
  19. // whether or not to register the mouse and keyboard
  20. // events on the mapbox overlay
  21. interactive: false,
  22. // set the tilepane as the default pane to draw gl tiles
  23. pane: 'tilePane'
  24. },
  25. initialize: function (options) {
  26. L.setOptions(this, options);
  27. if (options.accessToken) {
  28. mapboxgl.accessToken = options.accessToken;
  29. }
  30. // setup throttling the update event when panning
  31. this._throttledUpdate = L.Util.throttle(this._update, this.options.updateInterval, this);
  32. },
  33. onAdd: function (map) {
  34. if (!this._container) {
  35. this._initContainer();
  36. }
  37. var paneName = this.getPaneName();
  38. map.getPane(paneName).appendChild(this._container);
  39. this._initGL();
  40. this._offset = this._map.containerPointToLayerPoint([0, 0]);
  41. // work around https://github.com/mapbox/mapbox-gl-leaflet/issues/47
  42. if (map.options.zoomAnimation) {
  43. L.DomEvent.on(map._proxy, L.DomUtil.TRANSITION_END, this._transitionEnd, this);
  44. }
  45. },
  46. onRemove: function (map) {
  47. if (this._map._proxy && this._map.options.zoomAnimation) {
  48. L.DomEvent.off(this._map._proxy, L.DomUtil.TRANSITION_END, this._transitionEnd, this);
  49. }
  50. var paneName = this.getPaneName();
  51. map.getPane(paneName).removeChild(this._container);
  52. this._glMap.remove();
  53. this._glMap = null;
  54. },
  55. getEvents: function () {
  56. return {
  57. move: this._throttledUpdate, // sensibly throttle updating while panning
  58. zoomanim: this._animateZoom, // applys the zoom animation to the <canvas>
  59. zoom: this._pinchZoom, // animate every zoom event for smoother pinch-zooming
  60. zoomstart: this._zoomStart, // flag starting a zoom to disable panning
  61. zoomend: this._zoomEnd,
  62. resize: this._resize
  63. };
  64. },
  65. getMapboxMap: function () {
  66. return this._glMap;
  67. },
  68. getCanvas: function () {
  69. return this._glMap.getCanvas();
  70. },
  71. getSize: function () {
  72. return this._map.getSize().multiplyBy(1 + this.options.padding * 2);
  73. },
  74. getBounds: function () {
  75. var halfSize = this.getSize().multiplyBy(0.5);
  76. var center = this._map.latLngToContainerPoint(this._map.getCenter());
  77. return L.latLngBounds(
  78. this._map.containerPointToLatLng(center.subtract(halfSize)),
  79. this._map.containerPointToLatLng(center.add(halfSize))
  80. );
  81. },
  82. getContainer: function () {
  83. return this._container;
  84. },
  85. // returns the pane name set in options if it is a valid pane, defaults to tilePane
  86. getPaneName: function () {
  87. return this._map.getPane(this.options.pane) ? this.options.pane : 'tilePane';
  88. },
  89. _initContainer: function () {
  90. var container = this._container = L.DomUtil.create('div', 'leaflet-gl-layer');
  91. var size = this.getSize();
  92. var offset = this._map.getSize().multiplyBy(this.options.padding);
  93. container.style.width = size.x + 'px';
  94. container.style.height = size.y + 'px';
  95. var topLeft = this._map.containerPointToLayerPoint([0, 0]).subtract(offset);
  96. L.DomUtil.setPosition(container, topLeft);
  97. },
  98. _initGL: function () {
  99. var center = this._map.getCenter();
  100. var options = L.extend({}, this.options, {
  101. container: this._container,
  102. center: [center.lng, center.lat],
  103. zoom: this._map.getZoom() - 1,
  104. attributionControl: false
  105. });
  106. this._glMap = new mapboxgl.Map(options);
  107. // allow GL base map to pan beyond min/max latitudes
  108. this._glMap.transform.latRange = null;
  109. this._transformGL(this._glMap);
  110. if (this._glMap._canvas.canvas) {
  111. // older versions of mapbox-gl surfaced the canvas differently
  112. this._glMap._actualCanvas = this._glMap._canvas.canvas;
  113. } else {
  114. this._glMap._actualCanvas = this._glMap._canvas;
  115. }
  116. // treat child <canvas> element like L.ImageOverlay
  117. var canvas = this._glMap._actualCanvas;
  118. L.DomUtil.addClass(canvas, 'leaflet-image-layer');
  119. L.DomUtil.addClass(canvas, 'leaflet-zoom-animated');
  120. if (this.options.interactive) {
  121. L.DomUtil.addClass(canvas, 'leaflet-interactive');
  122. }
  123. if (this.options.className) {
  124. L.DomUtil.addClass(canvas, this.options.className);
  125. }
  126. },
  127. _update: function (e) {
  128. // update the offset so we can correct for it later when we zoom
  129. this._offset = this._map.containerPointToLayerPoint([0, 0]);
  130. if (this._zooming) {
  131. return;
  132. }
  133. var size = this.getSize(),
  134. container = this._container,
  135. gl = this._glMap,
  136. offset = this._map.getSize().multiplyBy(this.options.padding),
  137. topLeft = this._map.containerPointToLayerPoint([0, 0]).subtract(offset);
  138. L.DomUtil.setPosition(container, topLeft);
  139. this._transformGL(gl);
  140. if (gl.transform.width !== size.x || gl.transform.height !== size.y) {
  141. container.style.width = size.x + 'px';
  142. container.style.height = size.y + 'px';
  143. if (gl._resize !== null && gl._resize !== undefined){
  144. gl._resize();
  145. } else {
  146. gl.resize();
  147. }
  148. } else {
  149. // older versions of mapbox-gl surfaced update publicly
  150. if (gl._update !== null && gl._update !== undefined){
  151. gl._update();
  152. } else {
  153. gl.update();
  154. }
  155. }
  156. },
  157. _transformGL: function (gl) {
  158. var center = this._map.getCenter();
  159. // gl.setView([center.lat, center.lng], this._map.getZoom() - 1, 0);
  160. // calling setView directly causes sync issues because it uses requestAnimFrame
  161. var tr = gl.transform;
  162. tr.center = mapboxgl.LngLat.convert([center.lng, center.lat]);
  163. tr.zoom = this._map.getZoom() - 1;
  164. },
  165. // update the map constantly during a pinch zoom
  166. _pinchZoom: function (e) {
  167. this._glMap.jumpTo({
  168. zoom: this._map.getZoom() - 1,
  169. center: this._map.getCenter()
  170. });
  171. },
  172. // borrowed from L.ImageOverlay
  173. // https://github.com/Leaflet/Leaflet/blob/master/src/layer/ImageOverlay.js#L139-L144
  174. _animateZoom: function (e) {
  175. var scale = this._map.getZoomScale(e.zoom);
  176. var padding = this._map.getSize().multiplyBy(this.options.padding * scale);
  177. var viewHalf = this.getSize()._divideBy(2);
  178. // corrections for padding (scaled), adapted from
  179. // https://github.com/Leaflet/Leaflet/blob/master/src/map/Map.js#L1490-L1508
  180. var topLeft = this._map.project(e.center, e.zoom)
  181. ._subtract(viewHalf)
  182. ._add(this._map._getMapPanePos()
  183. .add(padding))._round();
  184. var offset = this._map.project(this._map.getBounds().getNorthWest(), e.zoom)
  185. ._subtract(topLeft);
  186. L.DomUtil.setTransform(
  187. this._glMap._actualCanvas,
  188. offset.subtract(this._offset),
  189. scale
  190. );
  191. },
  192. _zoomStart: function (e) {
  193. this._zooming = true;
  194. },
  195. _zoomEnd: function () {
  196. var scale = this._map.getZoomScale(this._map.getZoom());
  197. L.DomUtil.setTransform(
  198. this._glMap._actualCanvas,
  199. // https://github.com/mapbox/mapbox-gl-leaflet/pull/130
  200. null,
  201. scale
  202. );
  203. this._zooming = false;
  204. this._update();
  205. },
  206. _transitionEnd: function (e) {
  207. L.Util.requestAnimFrame(function () {
  208. var zoom = this._map.getZoom();
  209. var center = this._map.getCenter();
  210. var offset = this._map.latLngToContainerPoint(
  211. this._map.getBounds().getNorthWest()
  212. );
  213. // reset the scale and offset
  214. L.DomUtil.setTransform(this._glMap._actualCanvas, offset, 1);
  215. // enable panning once the gl map is ready again
  216. this._glMap.once('moveend', L.Util.bind(function () {
  217. this._zoomEnd();
  218. }, this));
  219. // update the map position
  220. this._glMap.jumpTo({
  221. center: center,
  222. zoom: zoom - 1
  223. });
  224. }, this);
  225. },
  226. _resize: function (e) {
  227. this._transitionEnd(e);
  228. }
  229. });
  230. L.mapboxGL = function (options) {
  231. return new L.MapboxGL(options);
  232. };
  233. }));