maplibre-gl 6.0.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/build/readme.md +0 -2
  2. package/dist/maplibre-gl-dev.mjs +433 -573
  3. package/dist/maplibre-gl-dev.mjs.map +1 -1
  4. package/dist/maplibre-gl-shared-dev.mjs +984 -850
  5. package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
  6. package/dist/maplibre-gl-shared.mjs +2 -2
  7. package/dist/maplibre-gl-shared.mjs.map +1 -1
  8. package/dist/maplibre-gl-worker-dev.mjs +14 -12
  9. package/dist/maplibre-gl-worker-dev.mjs.map +1 -1
  10. package/dist/maplibre-gl-worker.mjs +2 -2
  11. package/dist/maplibre-gl-worker.mjs.map +1 -1
  12. package/dist/maplibre-gl.css +1 -1
  13. package/dist/maplibre-gl.d.ts +119 -42
  14. package/dist/maplibre-gl.mjs +22 -22
  15. package/dist/maplibre-gl.mjs.map +1 -1
  16. package/package.json +20 -22
  17. package/src/css/maplibre-gl.css +4 -0
  18. package/src/data/bucket/circle_bucket.ts +3 -2
  19. package/src/data/bucket/fill_bucket.ts +3 -2
  20. package/src/data/bucket/fill_extrusion_bucket.test.ts +22 -0
  21. package/src/data/bucket/fill_extrusion_bucket.ts +17 -4
  22. package/src/data/bucket/line_bucket.test.ts +5 -5
  23. package/src/data/bucket/line_bucket.ts +3 -2
  24. package/src/data/bucket/round_polygon_corners.test.ts +159 -0
  25. package/src/data/bucket/round_polygon_corners.ts +121 -0
  26. package/src/data/bucket/symbol_bucket.test.ts +7 -7
  27. package/src/data/dem_data.test.ts +23 -23
  28. package/src/data/feature_position_map.test.ts +1 -1
  29. package/src/data/segment.test.ts +14 -14
  30. package/src/geo/bounds.test.ts +4 -4
  31. package/src/geo/lng_lat_bounds.test.ts +1 -1
  32. package/src/geo/projection/covering_tiles.bench.ts +39 -0
  33. package/src/geo/projection/covering_tiles.test.ts +3 -7
  34. package/src/geo/projection/covering_tiles.ts +37 -2
  35. package/src/geo/projection/globe_covering_tiles_details_provider.ts +4 -4
  36. package/src/geo/projection/globe_projection.test.ts +2 -2
  37. package/src/geo/projection/globe_projection.ts +6 -15
  38. package/src/geo/projection/globe_transform.test.ts +3 -6
  39. package/src/geo/projection/globe_transform.ts +4 -7
  40. package/src/geo/projection/mercator_covering_tiles_details_provider.ts +4 -4
  41. package/src/geo/projection/mercator_projection.ts +1 -13
  42. package/src/geo/projection/mercator_transform.test.ts +1 -1
  43. package/src/geo/projection/mercator_transform.ts +2 -1
  44. package/src/geo/projection/mercator_utils.test.ts +8 -8
  45. package/src/geo/projection/projection.ts +0 -27
  46. package/src/geo/projection/projection_data.ts +8 -0
  47. package/src/geo/projection/projection_factory.ts +3 -3
  48. package/src/geo/projection/vertical_perspective_projection.ts +3 -60
  49. package/src/geo/projection/vertical_perspective_transform.ts +16 -23
  50. package/src/geo/transform_interface.ts +1 -2
  51. package/src/index.test.ts +7 -12
  52. package/src/index.ts +4 -2
  53. package/src/render/glyph_manager.test.ts +7 -22
  54. package/src/render/painter.test.ts +2 -0
  55. package/src/render/painter.ts +8 -11
  56. package/src/render/subdivision.bench.ts +39 -0
  57. package/src/render/subdivision.test.ts +22 -23
  58. package/src/render/subdivision.ts +46 -0
  59. package/src/render/terrain.test.ts +68 -34
  60. package/src/render/terrain.ts +69 -26
  61. package/src/shaders/glsl/_projection_globe.fragment.glsl +12 -0
  62. package/src/shaders/glsl/_projection_globe.fragment.glsl.g.ts +2 -0
  63. package/src/shaders/glsl/_projection_globe.vertex.glsl +29 -10
  64. package/src/shaders/glsl/_projection_globe.vertex.glsl.g.ts +1 -1
  65. package/src/shaders/glsl/_projection_mercator.fragment.glsl +4 -0
  66. package/src/shaders/glsl/_projection_mercator.fragment.glsl.g.ts +2 -0
  67. package/src/shaders/glsl/line.fragment.glsl +2 -0
  68. package/src/shaders/glsl/line.fragment.glsl.g.ts +1 -1
  69. package/src/shaders/glsl/line_gradient.fragment.glsl +2 -0
  70. package/src/shaders/glsl/line_gradient.fragment.glsl.g.ts +1 -1
  71. package/src/shaders/glsl/line_gradient_sdf.fragment.glsl +2 -0
  72. package/src/shaders/glsl/line_gradient_sdf.fragment.glsl.g.ts +1 -1
  73. package/src/shaders/glsl/line_pattern.fragment.glsl +2 -0
  74. package/src/shaders/glsl/line_pattern.fragment.glsl.g.ts +1 -1
  75. package/src/shaders/glsl/line_sdf.fragment.glsl +2 -0
  76. package/src/shaders/glsl/line_sdf.fragment.glsl.g.ts +1 -1
  77. package/src/shaders/shaders.ts +4 -6
  78. package/src/source/canvas_source.test.ts +1 -1
  79. package/src/source/geojson_source.test.ts +32 -4
  80. package/src/source/geojson_source.ts +41 -0
  81. package/src/source/geojson_source_diff.test.ts +2 -2
  82. package/src/source/geojson_worker_source.test.ts +9 -5
  83. package/src/source/image_source.test.ts +137 -62
  84. package/src/source/image_source.ts +49 -12
  85. package/src/source/raster_dem_tile_source.test.ts +2 -2
  86. package/src/source/raster_tile_source.test.ts +64 -9
  87. package/src/source/raster_tile_source.ts +2 -1
  88. package/src/source/rtl_text_plugin_main_thread.test.ts +14 -14
  89. package/src/source/source.test.ts +8 -8
  90. package/src/source/vector_tile_source.test.ts +1 -1
  91. package/src/source/vector_tile_worker_source.test.ts +7 -5
  92. package/src/source/video_source.test.ts +1 -1
  93. package/src/source/worker.test.ts +1 -1
  94. package/src/source/worker_tile.test.ts +10 -8
  95. package/src/style/light.test.ts +9 -9
  96. package/src/style/light.ts +3 -3
  97. package/src/style/load_glyph_range.test.ts +8 -8
  98. package/src/style/load_sprite.test.ts +1 -1
  99. package/src/style/properties.test.ts +94 -2
  100. package/src/style/properties.ts +30 -1
  101. package/src/style/sky.test.ts +9 -9
  102. package/src/style/sky.ts +2 -2
  103. package/src/style/style.test.ts +38 -36
  104. package/src/style/style.ts +3 -3
  105. package/src/style/style_layer/color_relief_style_layer.test.ts +1 -1
  106. package/src/style/style_layer/fill_extrusion_style_layer.ts +6 -3
  107. package/src/style/style_layer/fill_extrusion_style_layer_properties.g.ts +14 -1
  108. package/src/style/style_layer/raster_style_layer.test.ts +2 -2
  109. package/src/style/style_layer.test.ts +5 -51
  110. package/src/style/validate_style.test.ts +1 -1
  111. package/src/symbol/collision_index.test.ts +1 -1
  112. package/src/symbol/grid_index.test.ts +1 -1
  113. package/src/tile/terrain_tile_manager.test.ts +2 -2
  114. package/src/tile/tile_id.test.ts +5 -5
  115. package/src/tile/tile_manager.test.ts +113 -72
  116. package/src/tile/tile_manager.ts +2 -2
  117. package/src/tile/tile_manager_in_view_tiles.test.ts +2 -2
  118. package/src/ui/camera.test.ts +138 -135
  119. package/src/ui/control/geolocate_control.test.ts +7 -30
  120. package/src/ui/handler/dblclick_zoom.test.ts +2 -2
  121. package/src/ui/handler/mouse.ts +15 -7
  122. package/src/ui/handler/mouse_handler_interface.test.ts +21 -1
  123. package/src/ui/handler/scroll_zoom.test.ts +2 -0
  124. package/src/ui/handler_manager.test.ts +7 -7
  125. package/src/ui/hash.test.ts +1 -0
  126. package/src/ui/map.ts +45 -20
  127. package/src/ui/map_tests/map_basic.test.ts +11 -23
  128. package/src/ui/map_tests/map_calculate_camera_options.test.ts +6 -6
  129. package/src/ui/map_tests/map_events.test.ts +5 -7
  130. package/src/ui/map_tests/map_global_state.test.ts +3 -3
  131. package/src/ui/map_tests/map_images.test.ts +4 -3
  132. package/src/ui/map_tests/map_is_moving.test.ts +1 -1
  133. package/src/ui/map_tests/map_is_rotating.test.ts +1 -1
  134. package/src/ui/map_tests/map_layer.test.ts +2 -1
  135. package/src/ui/map_tests/map_options.test.ts +1 -5
  136. package/src/ui/map_tests/map_pitch.test.ts +6 -6
  137. package/src/ui/map_tests/map_refresh_tiles.test.ts +2 -2
  138. package/src/ui/map_tests/map_render.test.ts +1 -1
  139. package/src/ui/map_tests/map_set_lod_params.test.ts +1 -1
  140. package/src/ui/map_tests/map_style.test.ts +9 -1
  141. package/src/ui/map_tests/map_terrain.test.ts +41 -1
  142. package/src/ui/map_tests/map_zoom.test.ts +15 -15
  143. package/src/ui/marker.test.ts +71 -14
  144. package/src/ui/marker.ts +30 -6
  145. package/src/ui/popup.test.ts +3 -3
  146. package/src/util/actor.test.ts +6 -6
  147. package/src/util/ajax.test.ts +27 -39
  148. package/src/util/browser.test.ts +7 -9
  149. package/src/util/create_tile_mesh.test.ts +2 -2
  150. package/src/util/dispatcher.test.ts +1 -1
  151. package/src/util/evented.test.ts +4 -1
  152. package/src/util/fast_maths.test.ts +4 -4
  153. package/src/util/image_request.test.ts +6 -4
  154. package/src/util/primitives/bounding_volume_cache.ts +3 -1
  155. package/src/util/struct_array.test.ts +2 -2
  156. package/src/util/task_queue.test.ts +1 -1
  157. package/src/util/test/util.ts +1 -0
  158. package/src/util/time_control.test.ts +2 -2
  159. package/src/util/transferable_grid_index.test.ts +1 -1
  160. package/src/util/util.test.ts +27 -13
  161. package/src/util/verticalize_punctuation.test.ts +4 -4
  162. package/src/util/web_worker_transfer.test.ts +5 -5
  163. package/src/webgl/draw/draw_custom.test.ts +13 -13
  164. package/src/webgl/draw/draw_debug.test.ts +1 -1
  165. package/src/webgl/draw/draw_fill.test.ts +13 -12
  166. package/src/webgl/draw/draw_symbol.test.ts +17 -16
  167. package/src/webgl/program/program_uniforms.ts +0 -3
  168. package/src/webgl/program/projection_program.ts +12 -10
  169. package/src/webgl/program/terrain_program.test.ts +2 -2
  170. package/src/webgl/program.ts +6 -4
  171. package/src/webgl/render_to_texture.test.ts +5 -5
  172. package/src/webgl/state.test.ts +1 -1
  173. package/src/webgl/texture.test.ts +3 -3
  174. package/src/webgl/vertex_buffer.test.ts +1 -1
  175. package/build/rolldown_plugins.ts +0 -18
  176. package/src/geo/projection/globe_projection_error_measurement.ts +0 -229
  177. package/src/shaders/glsl/projection_error_measurement.fragment.glsl +0 -5
  178. package/src/shaders/glsl/projection_error_measurement.fragment.glsl.g.ts +0 -2
  179. package/src/shaders/glsl/projection_error_measurement.vertex.glsl +0 -22
  180. package/src/shaders/glsl/projection_error_measurement.vertex.glsl.g.ts +0 -2
  181. package/src/webgl/program/projection_error_measurement_program.ts +0 -23
@@ -3,7 +3,7 @@ import geolocation from 'mock-geolocation';
3
3
  import {LngLatBounds} from '../../geo/lng_lat_bounds.ts';
4
4
  import {createMap, beforeMapTest, sleep} from '../../util/test/util.ts';
5
5
  import {GeolocateControl} from './geolocate_control.ts';
6
- vi.mock('../../util/geolocation_support', () => (
6
+ vi.mock(import('../../util/geolocation_support'), () => (
7
7
  {
8
8
  checkGeolocationSupport: vi.fn()
9
9
  }
@@ -61,7 +61,7 @@ describe('GeolocateControl with no options', () => {
61
61
  beforeEach(() => {
62
62
  beforeMapTest();
63
63
  map = createMap();
64
- (checkGeolocationSupport as unknown as MockInstance).mockImplementationOnce(() => Promise.resolve(true));
64
+ (checkGeolocationSupport as unknown as MockInstance).mockResolvedValueOnce(true);
65
65
  createResizeObserverEntryMock();
66
66
  });
67
67
 
@@ -70,7 +70,7 @@ describe('GeolocateControl with no options', () => {
70
70
  });
71
71
 
72
72
  test('is disabled when there is no support', async () => {
73
- (checkGeolocationSupport as unknown as MockInstance).mockReset().mockImplementationOnce(() => Promise.resolve(false));
73
+ (checkGeolocationSupport as unknown as MockInstance).mockReset().mockResolvedValueOnce(false);
74
74
  const geolocate = new GeolocateControl(undefined);
75
75
  const spy = vi.spyOn(console, 'warn').mockImplementation(() => {});
76
76
  map.addControl(geolocate);
@@ -237,13 +237,13 @@ describe('GeolocateControl with no options', () => {
237
237
 
238
238
  test('does not throw if removed quickly', () => {
239
239
  (checkGeolocationSupport as unknown as MockInstance).mockReset()
240
- .mockImplementationOnce(() => {
241
- return sleep(10);
242
- });
240
+ .mockReturnValueOnce(sleep(10));
243
241
 
244
242
  const geolocate = new GeolocateControl(undefined);
245
243
  map.addControl(geolocate);
246
244
  map.removeControl(geolocate);
245
+
246
+ expect(map.hasControl(geolocate)).toBe(false);
247
247
  });
248
248
 
249
249
  test('outofmaxbounds event in waiting active state', async () => {
@@ -422,7 +422,7 @@ describe('GeolocateControl with no options', () => {
422
422
  });
423
423
 
424
424
  test('trigger before added to map', () => {
425
- vi.spyOn(console, 'warn').mockImplementation(() => { });
425
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
426
426
 
427
427
  const geolocate = new GeolocateControl(undefined);
428
428
 
@@ -857,29 +857,6 @@ describe('GeolocateControl with no options', () => {
857
857
  expect(geolocate._circleElement.style.width).toBeTruthy();
858
858
  });
859
859
 
860
- test('shown even if trackUserLocation = false', async () => {
861
- const geolocate = new GeolocateControl({
862
- trackUserLocation: false,
863
- showUserLocation: true,
864
- showAccuracyCircle: true,
865
- });
866
- map.addControl(geolocate);
867
- await sleep(0);
868
- const click = new window.Event('click');
869
-
870
- const geolocatePromise = geolocate.once('geolocate');
871
- geolocate._geolocateButton.dispatchEvent(click);
872
- geolocation.send({latitude: 10, longitude: 20, accuracy: 700});
873
- await geolocatePromise;
874
- map.jumpTo({
875
- center: [10, 20]
876
- });
877
- const zoomendPromise = map.once('zoomend');
878
- map.zoomTo(10, {duration: 0});
879
- await zoomendPromise;
880
- expect(geolocate._circleElement.style.width).toBeTruthy();
881
- });
882
-
883
860
  test('GeolocateControl does not crash on movestart after removal in ACTIVE_LOCK state', async () => {
884
861
  const geolocate = new GeolocateControl({trackUserLocation: true});
885
862
  map.addControl(geolocate);
@@ -191,7 +191,7 @@ describe('dbclick_zoom', () => {
191
191
  expect(zoom).not.toHaveBeenCalled();
192
192
  });
193
193
 
194
- test('DoubleClickZoomHandler snaps to nearest zoomSnap', () => {
194
+ test('DoubleClickZoomHandler snaps to nearest zoomSnap on a non-interactive map', () => {
195
195
  const map = createMap({zoom: 9.7, zoomSnap: 1.0});
196
196
  const spy = vi.spyOn(map, 'easeTo');
197
197
 
@@ -203,7 +203,7 @@ describe('dbclick_zoom', () => {
203
203
  map.remove();
204
204
  });
205
205
 
206
- test('DoubleClickZoomHandler double-tap snaps to nearest zoomSnap', async () => {
206
+ test('DoubleClickZoomHandler double-tap snaps to nearest zoomSnap on a non-interactive map', async () => {
207
207
  const map = createMap({zoom: 9.7, zoomSnap: 1.0});
208
208
  const spy = vi.spyOn(map, 'easeTo');
209
209
 
@@ -51,12 +51,16 @@ export function generateMousePanHandler({enable, clickTolerance}: {
51
51
  });
52
52
  };
53
53
 
54
- export function generateMouseRotationHandler({enable, clickTolerance, aroundCenter = true, minPixelCenterThreshold = 100, rotateDegreesPerPixelMoved = 0.8}: {
54
+ export function generateMouseRotationHandler({enable, clickTolerance, aroundCenter = true, minPixelCenterThreshold = 100, rotateSpeed = 0.8}: {
55
55
  clickTolerance: number;
56
56
  enable?: boolean;
57
57
  aroundCenter?: boolean;
58
58
  minPixelCenterThreshold?: number;
59
- rotateDegreesPerPixelMoved?: number;
59
+ /**
60
+ * Degrees the bearing changes per pixel of horizontal drag.
61
+ * @defaultValue 0.8
62
+ */
63
+ rotateSpeed?: number;
60
64
  }, getCenter: () => Point): MouseRotateHandler {
61
65
  const mouseMoveStateManager = new MouseMoveStateManager({
62
66
  checkCorrectEvent: (e: MouseEvent): boolean =>
@@ -71,7 +75,7 @@ export function generateMouseRotationHandler({enable, clickTolerance, aroundCent
71
75
  // Avoid rotation related to y axis since it is "saved" for pitch
72
76
  return {bearingDelta: getAngleDelta(new Point(lastPoint.x, currentPoint.y), currentPoint, center)};
73
77
  }
74
- let bearingDelta = (currentPoint.x - lastPoint.x) * rotateDegreesPerPixelMoved;
78
+ let bearingDelta = (currentPoint.x - lastPoint.x) * rotateSpeed;
75
79
  if (aroundCenter && currentPoint.y < center.y) {
76
80
  bearingDelta = -bearingDelta;
77
81
  }
@@ -85,9 +89,13 @@ export function generateMouseRotationHandler({enable, clickTolerance, aroundCent
85
89
  });
86
90
  };
87
91
 
88
- export function generateMousePitchHandler({enable, clickTolerance, pitchDegreesPerPixelMoved = -0.5}: {
92
+ export function generateMousePitchHandler({enable, clickTolerance, pitchSpeed = -0.5}: {
89
93
  clickTolerance: number;
90
- pitchDegreesPerPixelMoved?: number;
94
+ /**
95
+ * Degrees the pitch changes per pixel of vertical drag.
96
+ * @defaultValue -0.5
97
+ */
98
+ pitchSpeed?: number;
91
99
  enable?: boolean;
92
100
  }): MousePitchHandler {
93
101
  const mouseMoveStateManager = new MouseMoveStateManager({
@@ -97,8 +105,8 @@ export function generateMousePitchHandler({enable, clickTolerance, pitchDegreesP
97
105
  });
98
106
  return new DragHandler<DragPitchResult, MouseEvent>({
99
107
  clickTolerance,
100
- move: (lastPoint: Point, point: Point) =>
101
- ({pitchDelta: (point.y - lastPoint.y) * pitchDegreesPerPixelMoved}),
108
+ move: (lastPoint: Point, point: Point) =>
109
+ ({pitchDelta: (point.y - lastPoint.y) * pitchSpeed}),
102
110
  // prevent browser context menu when necessary; we don't allow it with rotation
103
111
  // because we can't discern rotation gesture start from contextmenu on Mac
104
112
  moveStateManager: mouseMoveStateManager,
@@ -2,7 +2,7 @@ import {describe, test, expect} from 'vitest';
2
2
  import Point from '@mapbox/point-geometry';
3
3
 
4
4
  import {generateMousePanHandler, generateMousePitchHandler, generateMouseRollHandler, generateMouseRotationHandler} from './mouse.ts';
5
- import {type DragRotateResult} from './drag_handler.ts';
5
+ import {type DragPitchResult, type DragRotateResult} from './drag_handler.ts';
6
6
 
7
7
  describe('mouse handler tests', () => {
8
8
  test('MouseRotateHandler', () => {
@@ -40,6 +40,26 @@ describe('mouse handler tests', () => {
40
40
  expect(mouseRotate.isActive()).toBe(false);
41
41
  });
42
42
 
43
+ test('MouseRotateHandler rotates 2 degrees per dragged pixel when rotateSpeed is 2', () => {
44
+ const mouseRotate = generateMouseRotationHandler({clickTolerance: 2, rotateSpeed: 2}, () => new Point(10, 10));
45
+
46
+ mouseRotate.enable();
47
+ mouseRotate.dragStart(new MouseEvent('mousedown', {buttons: 2, button: 2}), new Point(0, 0));
48
+
49
+ const overToleranceMove = new MouseEvent('mousemove', {buttons: 2, clientX: 10, clientY: 10});
50
+ expect((mouseRotate.dragMove(overToleranceMove, new Point(10, 10)) as DragRotateResult).bearingDelta).toBeCloseTo(20);
51
+ });
52
+
53
+ test('MousePitchHandler pitches 2 degrees per dragged pixel when pitchSpeed is -2', () => {
54
+ const mousePitch = generateMousePitchHandler({clickTolerance: 2, pitchSpeed: -2});
55
+
56
+ mousePitch.enable();
57
+ mousePitch.dragStart(new MouseEvent('mousedown', {buttons: 2, button: 2}), new Point(0, 0));
58
+
59
+ const overToleranceMove = new MouseEvent('mousemove', {buttons: 2, clientX: 0, clientY: 10});
60
+ expect((mousePitch.dragMove(overToleranceMove, new Point(0, 10)) as DragPitchResult).pitchDelta).toBeCloseTo(-20);
61
+ });
62
+
43
63
  test('MousePitchHandler', () => {
44
64
  const mousePitch = generateMousePitchHandler({clickTolerance: 2});
45
65
 
@@ -359,6 +359,7 @@ describe('ScrollZoomHandler', () => {
359
359
 
360
360
  const map = createMap();
361
361
  map._renderTaskQueue.run();
362
+ const startZoom = map.getZoom();
362
363
 
363
364
  simulate.wheel(map.getCanvas(), {deltaY: -1});
364
365
  simulate.wheel(map.getCanvas(), {deltaY: -1});
@@ -372,6 +373,7 @@ describe('ScrollZoomHandler', () => {
372
373
  timeControlNow.mockReturnValue(now);
373
374
  map._renderTaskQueue.run();
374
375
 
376
+ expect(map.getZoom()).toBeCloseTo(startZoom);
375
377
  map.remove();
376
378
  });
377
379
 
@@ -1,4 +1,4 @@
1
- import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
1
+ import {afterEach, beforeEach, describe, expect, vi, test} from 'vitest';
2
2
  import Point from '@mapbox/point-geometry';
3
3
 
4
4
  import type {HandlerManager, MapControlsScenarioOptions, EventInProgress, EventsInProgress} from './handler_manager.ts';
@@ -25,7 +25,7 @@ afterEach(() => {
25
25
  });
26
26
 
27
27
  describe('HandlerManager terrain scenarios', () => {
28
- it('_handleMapControls keeps terrain movement disabled when terrain is not enabled', () => {
28
+ test('_handleMapControls keeps terrain movement disabled when terrain is not enabled', () => {
29
29
  const handleZoom = vi.fn();
30
30
  const handlePan = vi.fn();
31
31
  map._camera.cameraHelper = {
@@ -73,7 +73,7 @@ describe('HandlerManager terrain scenarios', () => {
73
73
  expect(setCenterMock).not.toHaveBeenCalled();
74
74
  });
75
75
 
76
- it('_handleMapControls enables terrain movement for globe terrain handling', () => {
76
+ test('_handleMapControls enables terrain movement for globe terrain handling', () => {
77
77
  const handleZoom = vi.fn();
78
78
  const handlePan = vi.fn();
79
79
  map._camera.cameraHelper = {
@@ -114,7 +114,7 @@ describe('HandlerManager terrain scenarios', () => {
114
114
  expect(handlePan).toHaveBeenCalledWith(options.deltasForHelper, options.tr, options.preZoomAroundLoc);
115
115
  });
116
116
 
117
- it('_handleMapControls keeps terrain movement state when globe terrain is already active', () => {
117
+ test('_handleMapControls keeps terrain movement state when globe terrain is already active', () => {
118
118
  const handleZoom = vi.fn();
119
119
  const handlePan = vi.fn();
120
120
  map._camera.cameraHelper = {
@@ -155,7 +155,7 @@ describe('HandlerManager terrain scenarios', () => {
155
155
  expect(handlePan).toHaveBeenCalledWith(options.deltasForHelper, options.tr, options.preZoomAroundLoc);
156
156
  });
157
157
 
158
- it('_handleMapControls activates terrain movement on first drag in mercator terrain', () => {
158
+ test('_handleMapControls activates terrain movement on first drag in mercator terrain', () => {
159
159
  const handleZoom = vi.fn();
160
160
  const handlePan = vi.fn();
161
161
  map._camera.cameraHelper = {
@@ -199,7 +199,7 @@ describe('HandlerManager terrain scenarios', () => {
199
199
  expect(setCenterMock).not.toHaveBeenCalled();
200
200
  });
201
201
 
202
- it('_handleMapControls drags using transform when already moving in mercator terrain', () => {
202
+ test('_handleMapControls drags using transform when already moving in mercator terrain', () => {
203
203
  const handleZoom = vi.fn();
204
204
  const handlePan = vi.fn();
205
205
  map._camera.cameraHelper = {
@@ -245,7 +245,7 @@ describe('HandlerManager terrain scenarios', () => {
245
245
  expect(handlePan).not.toHaveBeenCalled();
246
246
  });
247
247
 
248
- it('_handleMapControls falls back to helper panning when not dragging in mercator terrain', () => {
248
+ test('_handleMapControls falls back to helper panning when not dragging in mercator terrain', () => {
249
249
  const handleZoom = vi.fn();
250
250
  const handlePan = vi.fn();
251
251
  map._camera.cameraHelper = {
@@ -522,6 +522,7 @@ describe('hash', () => {
522
522
 
523
523
  map.remove();
524
524
 
525
+ expect(map._removed).toBe(true);
525
526
  });
526
527
 
527
528
  test('hash with URL in other parameter does not change except normalization', () => {
package/src/ui/map.ts CHANGED
@@ -356,6 +356,17 @@ export type MapOptions = {
356
356
  * @defaultValue false
357
357
  */
358
358
  rollEnabled?: boolean;
359
+ /**
360
+ * Degrees the map's bearing changes per pixel of horizontal drag when rotating.
361
+ * @defaultValue 0.8
362
+ */
363
+ rotateSpeed?: number;
364
+ /**
365
+ * Degrees the map's pitch changes per pixel of vertical drag. Negative, so that
366
+ * dragging up pitches the map toward the horizon.
367
+ * @defaultValue -0.5
368
+ */
369
+ pitchSpeed?: number;
359
370
  /**
360
371
  * If `true`, gesture inertia (such as panning) is disabled. If not provided, gesture inertia defaults to the user's device settings.
361
372
  * @defaultValue undefined
@@ -531,6 +542,8 @@ const defaultOptions: Readonly<Partial<MapOptions>> = {
531
542
  localIdeographFontFamily: 'sans-serif',
532
543
  pitchWithRotate: true,
533
544
  rollEnabled: false,
545
+ rotateSpeed: 0.8,
546
+ pitchSpeed: -0.5,
534
547
  reduceMotion: undefined,
535
548
  validateStyle: true,
536
549
  /**Because GL MAX_TEXTURE_SIZE is usually at least 4096px. */
@@ -2930,29 +2943,15 @@ export class Map extends Evented<MapEventType> {
2930
2943
  warnOnce('You are using the same source for a color-relief layer and for 3D terrain. Please consider using two separate sources to improve rendering quality.');
2931
2944
  }
2932
2945
  }
2946
+ if (this.terrain) {
2947
+ this.terrain.destroy();
2948
+ }
2933
2949
  this.terrain = new Terrain(this.painter, tileManager, options, this._terrainSkirtLength);
2934
2950
  this.painter.renderToTexture = new RenderToTexture(this.painter, this.terrain);
2935
2951
  this._camera.terrain = this.terrain;
2936
2952
  this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
2937
2953
  this._camera.transform.setElevation(this.terrain.getElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
2938
- this._terrainDataCallback = e => {
2939
- if (e.dataType === 'style') {
2940
- this.terrain.tileManager.releaseAllRTT();
2941
- } else if (e.dataType === 'source' && e.tile) {
2942
- if (e.sourceId === options.source && !this._camera.elevationFreeze) {
2943
- this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
2944
- if (this.getCenterClampedToGround()) {
2945
- this._camera.transform.setElevation(this.terrain.getElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
2946
- }
2947
- }
2948
-
2949
- if (e.source?.type === 'image') {
2950
- this.terrain.tileManager.releaseAllRTT();
2951
- } else {
2952
- this.terrain.tileManager.releaseRTT(e.tile.tileID);
2953
- }
2954
- }
2955
- };
2954
+ this._terrainDataCallback = e => this._handleTerrainDataEvent(e, options.source);
2956
2955
  this.style.on('data', this._terrainDataCallback);
2957
2956
  }
2958
2957
 
@@ -2960,6 +2959,31 @@ export class Map extends Evented<MapEventType> {
2960
2959
  return this;
2961
2960
  }
2962
2961
 
2962
+ private _handleTerrainDataEvent(event: MapStyleDataEvent | MapSourceDataEvent, terrainSourceId: string): void {
2963
+ if (event.dataType === 'style') {
2964
+ this.terrain.tileManager.releaseAllRTT();
2965
+ return;
2966
+ }
2967
+
2968
+ const isTerrainSourceEvent = event.sourceId === terrainSourceId;
2969
+ if (isTerrainSourceEvent) {
2970
+ this.terrain.resetElevationCache();
2971
+ }
2972
+ if (isTerrainSourceEvent && event.tile && !this._camera.elevationFreeze) {
2973
+ this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
2974
+ if (this.getCenterClampedToGround()) {
2975
+ this._camera.transform.setElevation(this.terrain.getElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
2976
+ }
2977
+ }
2978
+
2979
+ if (!event.tile) return;
2980
+ if (event.source?.type === 'image') {
2981
+ this.terrain.tileManager.releaseAllRTT();
2982
+ return;
2983
+ }
2984
+ this.terrain.tileManager.releaseRTT(event.tile.tileID);
2985
+ }
2986
+
2963
2987
  /**
2964
2988
  * Get the terrain-options if terrain is loaded
2965
2989
  * @returns the TerrainSpecification passed to setTerrain
@@ -4242,8 +4266,7 @@ export class Map extends Evented<MapEventType> {
4242
4266
  }
4243
4267
 
4244
4268
  const globeRenderingChanged = this.style.projection?.transitionState > 0 !== isGlobeRendering;
4245
- this.style.projection?.setErrorQueryLatitudeDegrees(this._camera.transform.center.lat);
4246
- this._camera.transform.setTransitionState(this.style.projection?.transitionState, this.style.projection?.latitudeErrorCorrectionRadians);
4269
+ this._camera.transform.setTransitionState(this.style.projection?.transitionState);
4247
4270
 
4248
4271
  // If we are in _render for any reason other than an in-progress paint
4249
4272
  // transition, update tile managers to check for and load any tiles we
@@ -4256,6 +4279,8 @@ export class Map extends Evented<MapEventType> {
4256
4279
  // update terrain stuff
4257
4280
  if (this.terrain) {
4258
4281
  this.terrain.tileManager.update(this._camera.transform, this.terrain);
4282
+ // Tile selection can change with the transform or cache state without a data event.
4283
+ this.terrain.resetElevationCache();
4259
4284
  this._camera.transform.setMinElevationForCurrentTile(this.terrain.getMinTileElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
4260
4285
  if (!this._camera.elevationFreeze && this.getCenterClampedToGround()) {
4261
4286
  this._camera.transform.setElevation(this.terrain.getElevationForLngLatZoom(this._camera.transform.center, this._camera.transform.tileZoom));
@@ -1,6 +1,6 @@
1
1
  import {describe, beforeEach, test, expect, vi} from 'vitest';
2
2
  import {Map} from '../map.ts';
3
- import {createMap, beforeMapTest, createStyle, createStyleSource, sleep} from '../../util/test/util.ts';
3
+ import {createMap, beforeMapTest, createStyle, createStyleSource, sleep, waitForEvent} from '../../util/test/util.ts';
4
4
  import {Tile} from '../../tile/tile.ts';
5
5
  import {OverscaledTileID} from '../../tile/tile_id.ts';
6
6
  import {fixedLngLat} from '../../../test/unit/lib/fixed.ts';
@@ -47,14 +47,6 @@ describe('Map', () => {
47
47
  );
48
48
  });
49
49
 
50
- test('bad map-specific token breaks map', () => {
51
- const container = window.document.createElement('div');
52
- Object.defineProperty(container, 'offsetWidth', {value: 512});
53
- Object.defineProperty(container, 'offsetHeight', {value: 512});
54
- createMap();
55
- //t.error();
56
- });
57
-
58
50
  describe('setTransformRequest', () => {
59
51
  test('returns self', () => {
60
52
  const transformRequest = (() => {}) as any as RequestTransformFunction;
@@ -69,6 +61,8 @@ describe('Map', () => {
69
61
  const transformRequest = (() => {}) as any as RequestTransformFunction;
70
62
  map.setTransformRequest(transformRequest);
71
63
  map.setTransformRequest(transformRequest);
64
+
65
+ expect(map._requestManager._transformRequestFn).toBe(transformRequest);
72
66
  });
73
67
 
74
68
  test('removes function when called with null', () => {
@@ -89,17 +83,12 @@ describe('Map', () => {
89
83
  const map = createMap({style});
90
84
 
91
85
  await map.once('load');
92
- const promise = new Promise<void>((resolve) => {
93
- map.on('data', (e) => {
94
- if (e.dataType === 'source' && e.sourceDataType === 'idle') {
95
- expect(map.isSourceLoaded('geojson')).toBe(true);
96
- resolve();
97
- }
98
- });
99
- });
86
+ const idlePromise = waitForEvent(map, 'data', (e) => e.dataType === 'source' && e.sourceDataType === 'idle');
100
87
  map.addSource('geojson', createStyleSource());
101
88
  expect(map.isSourceLoaded('geojson')).toBe(false);
102
- await promise;
89
+
90
+ await idlePromise;
91
+ expect(map.isSourceLoaded('geojson')).toBe(true);
103
92
  });
104
93
 
105
94
  test('Map.isSourceLoaded (equivalent to event.isSourceLoaded)', async () => {
@@ -109,11 +98,10 @@ describe('Map', () => {
109
98
  await map.once('load');
110
99
  const promise = new Promise<void>((resolve) => {
111
100
  map.on('data', (e: MapSourceDataEvent) => {
112
- if (e.dataType === 'source' && 'source' in e) {
113
- expect(map.isSourceLoaded('geojson')).toBe(e.isSourceLoaded);
114
- if (e.sourceDataType === 'idle') {
115
- resolve();
116
- }
101
+ if (e.dataType !== 'source' || !('source' in e)) return;
102
+ expect(map.isSourceLoaded('geojson')).toBe(e.isSourceLoaded);
103
+ if (e.sourceDataType === 'idle') {
104
+ resolve();
117
105
  }
118
106
  });
119
107
  });
@@ -27,7 +27,7 @@ describe('calculateCameraOptionsFromTo', () => {
27
27
  const cameraOptions: CameraOptions = map.calculateCameraOptionsFromTo(new LngLat(1, 0), 111200, new LngLat(0, 0));
28
28
  expect(cameraOptions).toBeDefined();
29
29
  expect(cameraOptions.pitch).toBeCloseTo(90);
30
- expect(mockedGetElevation.mock.calls).toHaveLength(1);
30
+ expect(mockedGetElevation).toHaveBeenCalledTimes(1);
31
31
  });
32
32
 
33
33
  test('pitch 153.435 with terrain', () => {
@@ -44,7 +44,7 @@ describe('calculateCameraOptionsFromTo', () => {
44
44
  const cameraOptions: CameraOptions = map.calculateCameraOptionsFromTo(new LngLat(1, 0), 111200, new LngLat(0, 0));
45
45
  expect(cameraOptions).toBeDefined();
46
46
  expect(cameraOptions.pitch).toBeCloseTo(153.435);
47
- expect(mockedGetElevation.mock.calls).toHaveLength(1);
47
+ expect(mockedGetElevation).toHaveBeenCalledTimes(1);
48
48
  });
49
49
 
50
50
  test('pitch 63 with terrain', () => {
@@ -62,7 +62,7 @@ describe('calculateCameraOptionsFromTo', () => {
62
62
  const cameraOptions: CameraOptions = map.calculateCameraOptionsFromTo(new LngLat(0, 0), 111200, new LngLat(1, 0));
63
63
  expect(cameraOptions).toBeDefined();
64
64
  expect(cameraOptions.pitch).toBeCloseTo(63.435);
65
- expect(mockedGetElevation.mock.calls).toHaveLength(1);
65
+ expect(mockedGetElevation).toHaveBeenCalledTimes(1);
66
66
  });
67
67
 
68
68
  test('zoom distance 1000', () => {
@@ -79,7 +79,7 @@ describe('calculateCameraOptionsFromTo', () => {
79
79
 
80
80
  expect(cameraOptions).toBeDefined();
81
81
  expect(cameraOptions.zoom).toBeCloseTo(expectedZoom);
82
- expect(mockedGetElevation.mock.calls).toHaveLength(1);
82
+ expect(mockedGetElevation).toHaveBeenCalledTimes(1);
83
83
  });
84
84
 
85
85
  test('don\'t call getElevation when altitude supplied', () => {
@@ -94,7 +94,7 @@ describe('calculateCameraOptionsFromTo', () => {
94
94
  const cameraOptions = map.calculateCameraOptionsFromTo(new LngLat(0, 0), 0, new LngLat(0, 0), 1000);
95
95
 
96
96
  expect(cameraOptions).toBeDefined();
97
- expect(mockedGetElevation.mock.calls).toHaveLength(0);
97
+ expect(mockedGetElevation).toHaveBeenCalledTimes(0);
98
98
  });
99
99
 
100
100
  test('don\'t call getElevation when altitude 0 supplied', () => {
@@ -109,6 +109,6 @@ describe('calculateCameraOptionsFromTo', () => {
109
109
  const cameraOptions = map.calculateCameraOptionsFromTo(new LngLat(0, 0), 0, new LngLat(1, 0), 0);
110
110
 
111
111
  expect(cameraOptions).toBeDefined();
112
- expect(mockedGetElevation.mock.calls).toHaveLength(0);
112
+ expect(mockedGetElevation).toHaveBeenCalledTimes(0);
113
113
  });
114
114
  });
@@ -6,7 +6,6 @@ import {type MapGeoJSONFeature} from '../../util/vectortile_to_geojson.ts';
6
6
  import {type MapLibreEvent} from '../events.ts';
7
7
  import {Map} from '../map.ts';
8
8
  import {Event as EventedEvent, ErrorEvent} from '../../util/evented.ts';
9
- import {GlobeProjection} from '../../geo/projection/globe_projection.ts';
10
9
  import {type StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
11
10
 
12
11
  type IsAny<T> = 0 extends T & 1 ? T : never;
@@ -248,7 +247,7 @@ describe('map events', () => {
248
247
  expect(handler.onMove).toHaveBeenCalledTimes(1);
249
248
  });
250
249
 
251
- test('Map.on allows a listener to infer the event type ', () => {
250
+ test('Map.on allows a listener to infer the event type', () => {
252
251
  const map = createMap();
253
252
 
254
253
  const spy = vi.fn();
@@ -412,7 +411,7 @@ describe('map events', () => {
412
411
  expect(handler.onMove).toHaveBeenCalledTimes(0);
413
412
  });
414
413
 
415
- test('Map.off allows a listener to infer the event type ', () => {
414
+ test('Map.off allows a listener to infer the event type', () => {
416
415
  const map = createMap();
417
416
 
418
417
  const spy = vi.fn();
@@ -441,7 +440,7 @@ describe('map events', () => {
441
440
  expect(handler.onMoveOnce).toHaveBeenCalledTimes(1);
442
441
  });
443
442
 
444
- test('Map.once allows a listener to infer the event type ', () => {
443
+ test('Map.once allows a listener to infer the event type', () => {
445
444
  const map = createMap();
446
445
 
447
446
  const spy = vi.fn();
@@ -965,7 +964,7 @@ describe('map events', () => {
965
964
  map.on('load', loadHandler);
966
965
  await sleep(1);
967
966
 
968
- expect(loadHandler).toThrowError();
967
+ expect(loadHandler).toThrow('Error in load handler');
969
968
  });
970
969
 
971
970
  test('no idle event during move', async () => {
@@ -982,7 +981,7 @@ describe('map events', () => {
982
981
  const map = createMap();
983
982
  const sourcePromise = map.once('sourcedataabort');
984
983
  map.fire(new EventedEvent('dataabort'));
985
- await sourcePromise;
984
+ await expect(sourcePromise).resolves.toBeDefined();
986
985
  });
987
986
 
988
987
  test('getZoom on moveend is the same as after the map end moving, with terrain on', async () => {
@@ -1139,7 +1138,6 @@ describe('map events', () => {
1139
1138
  });
1140
1139
  test('projectiontransition is fired when globe transitions to mercator', async () => {
1141
1140
  const map = createMap();
1142
- vi.spyOn(GlobeProjection.prototype, 'updateGPUdependent').mockImplementation(() => {});
1143
1141
  await map.once('load');
1144
1142
 
1145
1143
  const spy = vi.fn();
@@ -1,4 +1,4 @@
1
- import {describe, beforeEach, test, expect, vitest} from 'vitest';
1
+ import {describe, beforeEach, test, expect, vi} from 'vitest';
2
2
  import {createMap, beforeMapTest} from '../../util/test/util.ts';
3
3
 
4
4
  beforeEach(() => {
@@ -27,7 +27,7 @@ describe('setGlobalStateProperty', () => {
27
27
  });
28
28
 
29
29
  await map.once('style.load');
30
- map._update = vitest.fn();
30
+ map._update = vi.fn();
31
31
 
32
32
  map.setGlobalStateProperty('backgroundColor', 'blue');
33
33
 
@@ -56,7 +56,7 @@ describe('setGlobalStateProperty', () => {
56
56
  });
57
57
 
58
58
  await map.once('style.load');
59
- map._update = vitest.fn();
59
+ map._update = vi.fn();
60
60
 
61
61
  map.setGlobalStateProperty('backgroundColor', 'blue');
62
62
 
@@ -7,6 +7,7 @@ import {MessageType} from '../../util/actor_messages.ts';
7
7
  beforeEach(() => {
8
8
  beforeMapTest();
9
9
  global.fetch = null;
10
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
10
11
  });
11
12
 
12
13
  test('listImages', async () => {
@@ -89,7 +90,7 @@ test('map fires `styleimagemissing` when missing style image resolver returns no
89
90
  const generatedImage = await map.style.imageManager.getImages([id]);
90
91
  expect(resolver).toHaveBeenCalledWith(id);
91
92
  expect(generatedImage[id]).toBeUndefined();
92
- expect(missingImageEventSpy).toHaveBeenCalledOnce();
93
+ expect(missingImageEventSpy).toHaveBeenCalledTimes(1);
93
94
  expect(missingImageEventSpy.mock.calls[0][0].id).toBe(id);
94
95
  expect(map.hasImage(id)).toBeFalsy();
95
96
  });
@@ -244,7 +245,7 @@ test('setImages broadcasts even when getImages is called between addImage and up
244
245
 
245
246
  await map.once('load');
246
247
 
247
- const broadcastSpy = vi.fn().mockReturnValue(Promise.resolve({}));
248
+ const broadcastSpy = vi.fn().mockResolvedValue({});
248
249
  map.style.dispatcher.broadcast = broadcastSpy;
249
250
 
250
251
  map.addImage('new-image', {width: 1, height: 1, data: new Uint8Array(4)});
@@ -270,7 +271,7 @@ test('setImages broadcasts after missing style image resolver adds an image', as
270
271
 
271
272
  await map.once('load');
272
273
 
273
- const broadcastSpy = vi.fn().mockReturnValue(Promise.resolve({}));
274
+ const broadcastSpy = vi.fn().mockResolvedValue({});
274
275
  map.style.dispatcher.broadcast = broadcastSpy;
275
276
 
276
277
  map.setMissingStyleImageResolver((id) => {
@@ -69,7 +69,7 @@ describe('Map.isMoving', () => {
69
69
 
70
70
  test('returns true when drag rotating', async () => {
71
71
  // Prevent inertial rotation.
72
- vi.spyOn(timeControl, 'now').mockImplementation(() => 0);
72
+ vi.spyOn(timeControl, 'now').mockReturnValue(0);
73
73
 
74
74
  map.on('movestart', () => {
75
75
  expect(map.isMoving()).toBe(true);
@@ -40,7 +40,7 @@ describe('Map.isRotating', () => {
40
40
 
41
41
  test('returns true when drag rotating', async () => {
42
42
  // Prevent inertial rotation.
43
- vi.spyOn(timeControl, 'now').mockImplementation(() => 0);
43
+ vi.spyOn(timeControl, 'now').mockReturnValue(0);
44
44
 
45
45
  map.on('rotatestart', () => {
46
46
  expect(map.isRotating()).toBe(true);
@@ -98,7 +98,8 @@ test('removeLayer restores Map.loaded() to true', async () => {
98
98
 
99
99
  await map.once('render');
100
100
  map.removeLayer('layerId');
101
- await waitForEvent(map, 'render', () => map.loaded());
101
+ await expect(waitForEvent(map, 'render', () => map.loaded())).resolves.toBeDefined();
102
+
102
103
  map.remove();
103
104
  });
104
105