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
@@ -1,4 +1,4 @@
1
- import {describe, beforeEach, it, afterEach, expect, vi, type MockInstance} from 'vitest';
1
+ import {describe, beforeEach, afterEach, expect, vi, type MockInstance, test} from 'vitest';
2
2
  import {type FakeServer, fakeServer} from 'nise';
3
3
  import {rtlMainThreadPluginFactory} from './rtl_text_plugin_main_thread.ts';
4
4
  import {sleep} from '../util/test/util.ts';
@@ -20,7 +20,7 @@ describe('RTLMainThreadPlugin', () => {
20
20
  global.fetch = null;
21
21
  // Reset the singleton instance before each test
22
22
  rtlMainThreadPlugin.clearRTLTextPlugin();
23
- broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(() => Promise.resolve({} as any));
23
+ broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockResolvedValue({} as any);
24
24
  });
25
25
 
26
26
  function broadcastMockSuccess(message: MessageType, payload: PluginState): Promise<PluginState[]> {
@@ -62,36 +62,36 @@ describe('RTLMainThreadPlugin', () => {
62
62
  broadcastSpy.mockRestore();
63
63
  });
64
64
 
65
- it('should get the RTL text plugin status', () => {
65
+ test('should get the RTL text plugin status', () => {
66
66
  const status = rtlMainThreadPlugin.getRTLTextPluginStatus();
67
67
  expect(status).toBe('unavailable');
68
68
  });
69
69
 
70
- it('should set the RTL text plugin and download it', async () => {
70
+ test('should set the RTL text plugin and download it', async () => {
71
71
  broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccess as any);
72
72
  await rtlMainThreadPlugin.setRTLTextPlugin(url);
73
73
  expect(rtlMainThreadPlugin.url).toEqual(url);
74
74
  expect(rtlMainThreadPlugin.status).toBe('loaded');
75
75
  });
76
76
 
77
- it('should set the RTL text plugin but defer downloading', async () => {
77
+ test('should set the RTL text plugin but defer downloading', async () => {
78
78
  await rtlMainThreadPlugin.setRTLTextPlugin(url, true);
79
79
  expect(rtlMainThreadPlugin.status).toBe('deferred');
80
80
  expect(broadcastSpy).toHaveBeenCalledWith(SyncRTLPluginStateMessageName, {pluginStatus: 'deferred', pluginURL: url});
81
81
  });
82
82
 
83
- it('should throw if the plugin is already set', async () => {
83
+ test('should throw if the plugin is already set', async () => {
84
84
  await rtlMainThreadPlugin.setRTLTextPlugin(url, true);
85
85
  await expect(rtlMainThreadPlugin.setRTLTextPlugin(url)).rejects.toThrow('setRTLTextPlugin cannot be called multiple times.');
86
86
  });
87
87
 
88
- it('should throw if the plugin url is not set', async () => {
89
- const spy = vi.spyOn(browser, 'resolveURL').mockImplementation(() => '');
88
+ test('should throw if the plugin url is not set', async () => {
89
+ const spy = vi.spyOn(browser, 'resolveURL').mockReturnValue('');
90
90
  await expect(rtlMainThreadPlugin.setRTLTextPlugin(null)).rejects.toThrow('requested url null is invalid');
91
91
  spy.mockRestore();
92
92
  });
93
93
 
94
- it('should be in error state if download fails', async () => {
94
+ test('should be in error state if download fails', async () => {
95
95
  broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockFailure as any);
96
96
  const resultPromise = rtlMainThreadPlugin.setRTLTextPlugin(url);
97
97
  await expect(resultPromise).rejects.toBe(failedToLoadMessage);
@@ -99,7 +99,7 @@ describe('RTLMainThreadPlugin', () => {
99
99
  expect(rtlMainThreadPlugin.status).toBe('error');
100
100
  });
101
101
 
102
- it('should lazy load the plugin if deferred', async () => {
102
+ test('should lazy load the plugin if deferred', async () => {
103
103
  // use success spy to make sure test case does not throw exception
104
104
  const deferredSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccessDefer as any);
105
105
  await rtlMainThreadPlugin.setRTLTextPlugin(url, true);
@@ -129,12 +129,12 @@ describe('RTLMainThreadPlugin', () => {
129
129
  expect(broadcastSpy).toHaveBeenCalledTimes(1);
130
130
  });
131
131
 
132
- it('should set status to requested if RTL plugin was not set', async () => {
132
+ test('should set status to requested if RTL plugin was not set', async () => {
133
133
  rtlMainThreadPlugin.lazyLoad();
134
134
  expect(rtlMainThreadPlugin.status).toBe('requested');
135
135
  });
136
136
 
137
- it('should immediately download if RTL plugin was already requested, ignoring deferred:true', async () => {
137
+ test('should immediately download if RTL plugin was already requested, ignoring deferred:true', async () => {
138
138
  broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccess as any);
139
139
  rtlMainThreadPlugin.lazyLoad();
140
140
  expect(rtlMainThreadPlugin.status).toBe('requested');
@@ -146,14 +146,14 @@ describe('RTLMainThreadPlugin', () => {
146
146
  expect(broadcastSpy).toHaveBeenCalledWith(SyncRTLPluginStateMessageName, {pluginStatus: 'loading', pluginURL: url});
147
147
  });
148
148
 
149
- it('should allow multiple calls to lazyLoad', async () => {
149
+ test('should allow multiple calls to lazyLoad', async () => {
150
150
  rtlMainThreadPlugin.lazyLoad();
151
151
  expect(rtlMainThreadPlugin.status).toBe('requested');
152
152
  rtlMainThreadPlugin.lazyLoad();
153
153
  expect(rtlMainThreadPlugin.status).toBe('requested');
154
154
  });
155
155
 
156
- it('should be in error state if lazyLoad fails', async () => {
156
+ test('should be in error state if lazyLoad fails', async () => {
157
157
  broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccessDefer);
158
158
  const resultPromise = rtlMainThreadPlugin.setRTLTextPlugin(url, true);
159
159
  await expect(resultPromise).resolves.toBeUndefined();
@@ -27,18 +27,18 @@ describe('addSourceType', () => {
27
27
  await addSourceType('foo2', sourceType);
28
28
  expect(spy).not.toHaveBeenCalled();
29
29
 
30
- expect(() => create('id', {type: 'foo2'} as any, null, null)).toThrow();
30
+ expect(() => create('id', {type: 'foo2'} as any, null, null)).toThrow('Expected Source id to be id instead of undefined');
31
31
  expect(sourceType).toHaveBeenCalled();
32
32
  });
33
33
 
34
34
  test('refuses to add new type over existing name', async () => {
35
35
  const sourceType = function () {} as any as SourceClass;
36
- await expect(addSourceType('canvas', sourceType)).rejects.toThrow();
37
- await expect(addSourceType('geojson', sourceType)).rejects.toThrow();
38
- await expect(addSourceType('image', sourceType)).rejects.toThrow();
39
- await expect(addSourceType('raster', sourceType)).rejects.toThrow();
40
- await expect(addSourceType('raster-dem', sourceType)).rejects.toThrow();
41
- await expect(addSourceType('vector', sourceType)).rejects.toThrow();
42
- await expect(addSourceType('video', sourceType)).rejects.toThrow();
36
+ await expect(addSourceType('canvas', sourceType)).rejects.toThrow('A source type called "canvas" already exists.');
37
+ await expect(addSourceType('geojson', sourceType)).rejects.toThrow('A source type called "geojson" already exists.');
38
+ await expect(addSourceType('image', sourceType)).rejects.toThrow('A source type called "image" already exists.');
39
+ await expect(addSourceType('raster', sourceType)).rejects.toThrow('A source type called "raster" already exists.');
40
+ await expect(addSourceType('raster-dem', sourceType)).rejects.toThrow('A source type called "raster-dem" already exists.');
41
+ await expect(addSourceType('vector', sourceType)).rejects.toThrow('A source type called "vector" already exists.');
42
+ await expect(addSourceType('video', sourceType)).rejects.toThrow('A source type called "video" already exists.');
43
43
  });
44
44
  });
@@ -454,7 +454,7 @@ describe('VectorTileSource', () => {
454
454
 
455
455
  await waitForEvent(source, 'data', (e: MapSourceDataEvent) => e.sourceDataType === 'metadata');
456
456
 
457
- expect(server.requests.length).toBe(2);
457
+ expect(server.requests).toHaveLength(2);
458
458
  expect(server.requests[0].aborted).toBe(true);
459
459
  expect(source.serialize()).toEqual({
460
460
  type: 'vector',
@@ -66,7 +66,7 @@ describe('vector tile worker source', () => {
66
66
 
67
67
  test('VectorTileWorkerSource.reloadTile reloads a previously-loaded tile', async () => {
68
68
  const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
69
- const parse = vi.fn().mockReturnValue(Promise.resolve({} as WorkerTileResult));
69
+ const parse = vi.fn().mockResolvedValue({});
70
70
 
71
71
  source.tileState.loaded = {
72
72
  '0': {
@@ -144,13 +144,14 @@ describe('vector tile worker source', () => {
144
144
  request.respond(200, {'Content-Type': 'application/pbf'}, rawTileData as any);
145
145
  });
146
146
 
147
+ const onSettled = vi.fn();
147
148
  source.loadTile({
148
149
  source: 'source',
149
150
  uid: 0,
150
151
  tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
151
152
  request: {url: 'http://localhost:2900/faketile.pbf'},
152
153
  subdivisionGranularity: SubdivisionGranularitySetting.noSubdivision,
153
- } as any as WorkerTileParameters).then(() => expect(false).toBeTruthy());
154
+ } as any as WorkerTileParameters).then(onSettled, onSettled);
154
155
 
155
156
  server.respond();
156
157
 
@@ -163,6 +164,7 @@ describe('vector tile worker source', () => {
163
164
  tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
164
165
  subdivisionGranularity: SubdivisionGranularitySetting.noSubdivision,
165
166
  } as any as WorkerTileParameters) as WorkerTileWithData;
167
+ expect(onSettled).not.toHaveBeenCalled();
166
168
  expect(res).toBeDefined();
167
169
  expect(res.rawTileData).toBeDefined();
168
170
  expect(res.rawTileData).toStrictEqual(rawTileData);
@@ -391,7 +393,7 @@ describe('vector tile worker source', () => {
391
393
  server.respond();
392
394
 
393
395
  expect(parse).not.toHaveBeenCalled();
394
- expect(await promise).toBeNull();
396
+ await expect(promise).resolves.toBeNull();
395
397
  });
396
398
 
397
399
  test('VectorTileWorkerSource.returns a good error message when failing to parse a tile', async () => {
@@ -412,7 +414,7 @@ describe('vector tile worker source', () => {
412
414
  server.respond();
413
415
 
414
416
  expect(parse).not.toHaveBeenCalled();
415
- await expect(loadTilePromise).rejects.toThrowError(/Unable to parse the tile at/);
417
+ await expect(loadTilePromise).rejects.toThrow(/Unable to parse the tile at/);
416
418
  });
417
419
 
418
420
  test('VectorTileWorkerSource.returns a good error message when failing to parse a gzipped tile', async () => {
@@ -431,7 +433,7 @@ describe('vector tile worker source', () => {
431
433
  server.respond();
432
434
 
433
435
  expect(parse).not.toHaveBeenCalled();
434
- await expect(loadTilePromise).rejects.toThrowError(/gzipped/);
436
+ await expect(loadTilePromise).rejects.toThrow(/gzipped/);
435
437
  });
436
438
 
437
439
  test('VectorTileWorkerSource provides resource timing information', async () => {
@@ -120,7 +120,7 @@ describe('VideoSource', () => {
120
120
  test('onRemove removes playing listener and pauses video', () => {
121
121
  const video = window.document.createElement('video');
122
122
  const removeListenerSpy = vi.spyOn(video, 'removeEventListener');
123
- const pauseSpy = vi.spyOn(video, 'pause');
123
+ const pauseSpy = vi.spyOn(video, 'pause').mockImplementation(() => {});
124
124
 
125
125
  const source = createSource({
126
126
  type: 'video',
@@ -97,7 +97,7 @@ describe('Worker generic testing', () => {
97
97
  test('worker source messages dispatched to the correct map instance', () => {
98
98
  const externalSourceName = 'test';
99
99
 
100
- const sendAsyncSpy = vi.fn().mockReturnValue(Promise.resolve({} as any));
100
+ const sendAsyncSpy = vi.fn().mockResolvedValue({} as any);
101
101
  worker.actor.sendAsync = sendAsyncSpy;
102
102
 
103
103
  _self.registerWorkerSource(externalSourceName, WorkerSourceMock);
@@ -3,14 +3,14 @@ import {WorkerTile} from '../source/worker_tile.ts';
3
3
  import {type Feature, GeoJSONWrapper, type VectorTileLike} from '@maplibre/vt-pbf';
4
4
  import {OverscaledTileID} from '../tile/tile_id.ts';
5
5
  import {StyleLayerIndex} from '../style/style_layer_index.ts';
6
- import {type WorkerTileParameters, type WorkerTileWithData} from './worker_source.ts';
6
+ import {MessageType} from '../util/actor_messages.ts';
7
7
  import {SubdivisionGranularitySetting} from '../render/subdivision_granularity_settings.ts';
8
- import {type EvaluationParameters} from '../style/evaluation_parameters.ts';
9
- import {type PossiblyEvaluated} from '../style/properties.ts';
10
8
  import {Color} from '@maplibre/maplibre-gl-style-spec';
11
- import {type CirclePaintProps, type CirclePaintPropsPossiblyEvaluated} from '../style/style_layer/circle_style_layer_properties.g.ts';
12
- import {type SymbolLayoutProps, type SymbolLayoutPropsPossiblyEvaluated} from '../style/style_layer/symbol_style_layer_properties.g.ts';
13
- import {MessageType} from '../util/actor_messages.ts';
9
+ import type {WorkerTileParameters, WorkerTileWithData} from './worker_source.ts';
10
+ import type {EvaluationParameters} from '../style/evaluation_parameters.ts';
11
+ import type {PossiblyEvaluated} from '../style/properties.ts';
12
+ import type {CirclePaintProps, CirclePaintPropsPossiblyEvaluated} from '../style/style_layer/circle_style_layer_properties.g.ts';
13
+ import type {SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated} from '../style/style_layer/symbol_style_layer_properties.g.ts';
14
14
 
15
15
  function createWorkerTile(params?: {globalState?: Record<string, any>}): WorkerTile {
16
16
  return new WorkerTile({
@@ -299,9 +299,11 @@ describe('worker tile', () => {
299
299
  const actorMock = {
300
300
  sendAsync
301
301
  };
302
- tile.parse(data, layerIndex, ['hello'], actorMock, SubdivisionGranularitySetting.noSubdivision).then(() => expect(false).toBeTruthy());
303
- tile.parse(data, layerIndex, ['hello'], actorMock, SubdivisionGranularitySetting.noSubdivision).then(() => expect(false).toBeTruthy());
302
+ const onSettled = vi.fn();
303
+ tile.parse(data, layerIndex, ['hello'], actorMock, SubdivisionGranularitySetting.noSubdivision).then(onSettled, onSettled);
304
+ tile.parse(data, layerIndex, ['hello'], actorMock, SubdivisionGranularitySetting.noSubdivision).then(onSettled, onSettled);
304
305
  const result = await tile.parse(data, layerIndex, ['hello'], actorMock, SubdivisionGranularitySetting.noSubdivision);
306
+ expect(onSettled).not.toHaveBeenCalled();
305
307
  expect(result).toBeDefined();
306
308
  expect(cancelCount).toBe(6);
307
309
  expect(sendAsync).toHaveBeenCalledTimes(9);
@@ -8,7 +8,7 @@ import {type TransitionParameters} from './properties.ts';
8
8
  const spec = styleSpec.light;
9
9
 
10
10
  test('Light with defaults', () => {
11
- const light = new Light({});
11
+ const light = new Light({}, {});
12
12
  light.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters);
13
13
 
14
14
  expect(light.properties.get('anchor')).toEqual(spec.anchor.default);
@@ -23,7 +23,7 @@ test('Light with options', () => {
23
23
  anchor: 'map',
24
24
  position: [2, 30, 30],
25
25
  intensity: 1
26
- });
26
+ }, {});
27
27
  light.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters);
28
28
 
29
29
  expect(light.properties.get('anchor')).toBe('map');
@@ -38,7 +38,7 @@ test('Light with stops function', () => {
38
38
  intensity: {
39
39
  stops: [[16, 0.2], [17, 0.8]]
40
40
  }
41
- } as LightSpecification);
41
+ } as LightSpecification, {});
42
42
  light.recalculate({zoom: 16.5, zoomHistory: {}} as EvaluationParameters);
43
43
 
44
44
  expect(light.properties.get('intensity')).toBe(0.5);
@@ -50,12 +50,12 @@ test('Light.getLight', () => {
50
50
  defaults[key] = spec[key].default;
51
51
  }
52
52
 
53
- expect(new Light(defaults).getLight()).toEqual(defaults);
53
+ expect(new Light(defaults, {}).getLight()).toEqual(defaults);
54
54
  });
55
55
 
56
56
  describe('Light.setLight', () => {
57
57
  test('sets light', () => {
58
- const light = new Light({});
58
+ const light = new Light({}, {});
59
59
  light.setLight({color: 'red', 'color-transition': {duration: 3000}});
60
60
  light.updateTransitions({transition: true} as any as TransitionParameters);
61
61
  light.recalculate({zoom: 16, zoomHistory: {}, now: 1500} as EvaluationParameters);
@@ -63,9 +63,9 @@ describe('Light.setLight', () => {
63
63
  });
64
64
 
65
65
  test('validates by default', () => {
66
- const light = new Light({});
66
+ const light = new Light({}, {});
67
67
  const lightSpy = vi.spyOn(light, '_validate');
68
- vi.spyOn(console, 'error').mockImplementation(() => { });
68
+ vi.spyOn(console, 'error').mockImplementation(() => {});
69
69
  light.setLight({color: 'notacolor'});
70
70
  light.updateTransitions({transition: false} as any as TransitionParameters);
71
71
  light.recalculate({zoom: 16, zoomHistory: {}, now: 10} as EvaluationParameters);
@@ -75,7 +75,7 @@ describe('Light.setLight', () => {
75
75
  });
76
76
 
77
77
  test('respects validation option', () => {
78
- const light = new Light({});
78
+ const light = new Light({}, {});
79
79
 
80
80
  const lightSpy = vi.spyOn(light, '_validate');
81
81
  light.setLight({color: [999]} as any, {validate: false});
@@ -96,7 +96,7 @@ describe('Light runtime error logging', () => {
96
96
  test('warns with the light property location when an expression errors at runtime', () => {
97
97
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
98
98
 
99
- const light = new Light({});
99
+ const light = new Light({}, {});
100
100
  // global-state defeats constant-folding, so this fails at evaluation time (not parse time).
101
101
  light.setLight({intensity: ['number', ['global-state', 'missing']]} as any, {validate: false});
102
102
  light.updateTransitions({transition: false} as any as TransitionParameters);
@@ -19,9 +19,9 @@ export class Light extends Evented {
19
19
  _transitioning: Transitioning<LightProps>;
20
20
  properties: PossiblyEvaluated<LightProps, LightPropsPossiblyEvaluated>;
21
21
 
22
- constructor(lightOptions?: LightSpecification) {
22
+ constructor(lightOptions: LightSpecification, globalState: Record<string, any>) {
23
23
  super();
24
- this._transitionable = new Transitionable(getProperties(), 'light', undefined);
24
+ this._transitionable = new Transitionable(getProperties(), 'light', globalState);
25
25
  this.setLight(lightOptions);
26
26
  this._transitioning = this._transitionable.untransitioned();
27
27
  }
@@ -37,7 +37,7 @@ export class Light extends Evented {
37
37
  return sphericalToCartesian(this.properties.get('position'));
38
38
  }
39
39
 
40
- setLight(light?: LightSpecification, options: StyleSetterOptions = {}): void {
40
+ setLight(light: LightSpecification, options: StyleSetterOptions = {}): void {
41
41
  if (this._validate(validateStyle.light, light, options)) {
42
42
  return;
43
43
  }
@@ -33,10 +33,10 @@ test('loadGlyphRange', async () => {
33
33
 
34
34
  expect(glyph.id).toBe(Number(id));
35
35
  expect(glyph.metrics).toBeTruthy();
36
- expect(typeof glyph.metrics.width).toBe('number');
37
- expect(typeof glyph.metrics.height).toBe('number');
38
- expect(typeof glyph.metrics.top).toBe('number');
39
- expect(typeof glyph.metrics.advance).toBe('number');
36
+ expect(glyph.metrics.width).toBeTypeOf('number');
37
+ expect(glyph.metrics.height).toBeTypeOf('number');
38
+ expect(glyph.metrics.top).toBeTypeOf('number');
39
+ expect(glyph.metrics.advance).toBeTypeOf('number');
40
40
  }
41
41
  expect(server.requests[0].url).toBe('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf');
42
42
  });
@@ -64,10 +64,10 @@ test('loadGlyphRange with async transformRequest', async () => {
64
64
 
65
65
  expect(glyph.id).toBe(Number(id));
66
66
  expect(glyph.metrics).toBeTruthy();
67
- expect(typeof glyph.metrics.width).toBe('number');
68
- expect(typeof glyph.metrics.height).toBe('number');
69
- expect(typeof glyph.metrics.top).toBe('number');
70
- expect(typeof glyph.metrics.advance).toBe('number');
67
+ expect(glyph.metrics.width).toBeTypeOf('number');
68
+ expect(glyph.metrics.height).toBeTypeOf('number');
69
+ expect(glyph.metrics.top).toBeTypeOf('number');
70
+ expect(glyph.metrics.advance).toBeTypeOf('number');
71
71
  }
72
72
  expect(server.requests[0].url).toBe('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf');
73
73
  expect(server.requests[0].requestHeaders.Authorization).toBe('Bearer token');
@@ -28,7 +28,7 @@ describe('normalizeSpriteURL', () => {
28
28
  ).toBe('http://www.foo.com/bar@2x.png?fresh=true');
29
29
  });
30
30
 
31
- test('test relative URL', () => {
31
+ test('relative URL', () => {
32
32
  expect(
33
33
  () => normalizeSpriteURL('/bar?fresh=true', '@2x', '.png')
34
34
  ).toThrow(/Invalid/i);
@@ -1,6 +1,8 @@
1
- import {describe, test, expect, vi, afterEach} from 'vitest';
2
- import {Layout, PropertyValue, Transitionable} from './properties.ts';
1
+ import {describe, test, expect, vi, afterEach, beforeEach} from 'vitest';
2
+ import {ColorArray} from '@maplibre/maplibre-gl-style-spec';
3
+ import {DataDrivenProperty, Layout, PossiblyEvaluatedPropertyValue, PropertyValue, Transitionable} from './properties.ts';
3
4
  import symbolProperties from './style_layer/symbol_style_layer_properties.g.ts';
5
+ import hillshadeProperties from './style_layer/hillshade_style_layer_properties.g.ts';
4
6
  import {type EvaluationParameters} from './evaluation_parameters.ts';
5
7
 
6
8
  describe('PropertyValue', () => {
@@ -100,3 +102,93 @@ describe('Transitionable', () => {
100
102
  expect(warn.mock.calls[0][0]).toBe('layers[0].paint.text-color: Could not parse color from value \'oops blue\' Falling back to rgba(0,0,0,1).');
101
103
  });
102
104
  });
105
+
106
+ describe('paint property transitions between arrays of different length, issue #6606', () => {
107
+ const transition = {duration: 300, delay: 0};
108
+
109
+ beforeEach(() => {
110
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
111
+ });
112
+
113
+ afterEach(() => {
114
+ vi.restoreAllMocks();
115
+ });
116
+
117
+ function evaluateMidTransition(from: string[], to: string[]) {
118
+ const transitionable = new Transitionable(hillshadeProperties.paint, '', {});
119
+
120
+ transitionable.setValue('hillshade-highlight-color', from);
121
+ let transitioning = transitionable.transitioned(
122
+ {now: 0, transition}, transitionable.untransitioned());
123
+ transitioning.possiblyEvaluate({zoom: 0, now: 0} as EvaluationParameters, undefined, []);
124
+
125
+ transitionable.setValue('hillshade-highlight-color', to);
126
+ transitioning = transitionable.transitioned({now: 0, transition}, transitioning);
127
+
128
+ // Halfway through the 300ms transition, where interpolation is attempted.
129
+ return transitioning
130
+ .possiblyEvaluate({zoom: 0, now: 150} as EvaluationParameters, undefined, [])
131
+ .get('hillshade-highlight-color');
132
+ }
133
+
134
+ test('removing a light snaps to the new colors instead of throwing', () => {
135
+ const result = evaluateMidTransition(
136
+ ['#ffffff', '#ff0000', '#00ff00', '#0000ff'],
137
+ ['#ffffff', '#ff0000', '#00ff00']
138
+ );
139
+
140
+ expect(result.values).toHaveLength(3);
141
+ expect(result).toEqual(ColorArray.parse(['#ffffff', '#ff0000', '#00ff00']));
142
+ });
143
+
144
+ test('adding a light snaps to the new colors instead of throwing', () => {
145
+ const result = evaluateMidTransition(
146
+ ['#ffffff', '#ff0000'],
147
+ ['#ffffff', '#ff0000', '#00ff00']
148
+ );
149
+
150
+ expect(result.values).toHaveLength(3);
151
+ expect(result).toEqual(ColorArray.parse(['#ffffff', '#ff0000', '#00ff00']));
152
+ });
153
+
154
+ test('a same-length change still interpolates normally', () => {
155
+ const result = evaluateMidTransition(
156
+ ['#000000', '#000000'],
157
+ ['#ffffff', '#ffffff']
158
+ );
159
+
160
+ expect(result.values).toHaveLength(2);
161
+ for (const color of result.values) {
162
+ expect(color.r).toBeGreaterThan(0);
163
+ expect(color.r).toBeLessThan(1);
164
+ }
165
+ });
166
+ });
167
+
168
+ describe('DataDrivenProperty.interpolate between arrays of different length, issue #6606', () => {
169
+ const property = new DataDrivenProperty<ColorArray>({type: 'colorArray'} as any, 'color-array');
170
+
171
+ function constant(value: ColorArray) {
172
+ return new PossiblyEvaluatedPropertyValue<ColorArray>(
173
+ property, {kind: 'constant', value}, {} as EvaluationParameters);
174
+ }
175
+
176
+ test('a length change snaps to the target value instead of throwing', () => {
177
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
178
+ const from = constant(ColorArray.parse(['#ffffff', '#ff0000', '#00ff00', '#0000ff']));
179
+ const to = constant(ColorArray.parse(['#ffffff', '#ff0000', '#00ff00']));
180
+
181
+ const result = property.interpolate(from, to, 0.5);
182
+
183
+ expect(result.value).toEqual({kind: 'constant', value: ColorArray.parse(['#ffffff', '#ff0000', '#00ff00'])});
184
+ });
185
+
186
+ test('a same-length change still interpolates normally', () => {
187
+ const a = ColorArray.parse(['#000000', '#000000']);
188
+ const b = ColorArray.parse(['#ffffff', '#ffffff']);
189
+
190
+ const result = property.interpolate(constant(a), constant(b), 0.5);
191
+
192
+ expect(result.value).toEqual({kind: 'constant', value: ColorArray.interpolate(a, b, 0.5)});
193
+ });
194
+ });
@@ -1,4 +1,4 @@
1
- import {clone, extend, easeCubicInOut} from '../util/util.ts';
1
+ import {clone, extend, easeCubicInOut, warnOnce} from '../util/util.ts';
2
2
  import {interpolates, type Color, type StylePropertySpecification, normalizePropertyExpression,
3
3
  type Feature,
4
4
  type FeatureState,
@@ -471,6 +471,26 @@ export class PossiblyEvaluated<Props, PossibleEvaluatedProps> {
471
471
  }
472
472
  }
473
473
 
474
+ /**
475
+ * Returns the length of the array value, or undefined if the value is not an array or a style spec array wrapper.
476
+ */
477
+ function getArrayValueLength(value: unknown): number | undefined {
478
+ if (Array.isArray(value)) {
479
+ return value.length;
480
+ }
481
+ const values = (value as {values?: unknown})?.values;
482
+ return Array.isArray(values) ? values.length : undefined;
483
+ }
484
+
485
+ /**
486
+ * Returns true if the two values are arrays of different length, either bare arrays or style spec array wrappers.
487
+ */
488
+ function isNonInterpolableArrayChange(a: unknown, b: unknown): boolean {
489
+ const lengthA = getArrayValueLength(a);
490
+ const lengthB = getArrayValueLength(b);
491
+ return lengthA !== undefined && lengthB !== undefined && lengthA !== lengthB;
492
+ }
493
+
474
494
  /**
475
495
  * @internal
476
496
  * An implementation of `Property` for properties that do not permit data-driven (source or composite) expressions.
@@ -492,6 +512,10 @@ export class DataConstantProperty<T> implements Property<T, T> {
492
512
  }
493
513
 
494
514
  interpolate(a: T, b: T, t: number): T {
515
+ if (isNonInterpolableArrayChange(a, b)) {
516
+ warnOnce(`Property "${this.name}" is trying to interpolate arrays of different lengths. Rendering may 'jump'.`);
517
+ return b;
518
+ }
495
519
  const interpolationType = this.specification.type as keyof typeof interpolates;
496
520
  const interpolationFn = interpolates[interpolationType] as ((from: T, to: T, t: number) => T) | undefined;
497
521
  if (interpolationFn) {
@@ -553,6 +577,11 @@ export class DataDrivenProperty<T> implements Property<T, PossiblyEvaluatedPrope
553
577
  return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: undefined}, a.parameters);
554
578
  }
555
579
 
580
+ if (isNonInterpolableArrayChange(a.value.value, b.value.value)) {
581
+ warnOnce(`Property "${this.name}" is trying to interpolate arrays of different lengths. Rendering may 'jump'.`);
582
+ return b;
583
+ }
584
+
556
585
  const interpolationType = this.specification.type as keyof typeof interpolates;
557
586
  const interpolationFn = interpolates[interpolationType] as ((from: T, to: T, t: number) => T) | undefined;
558
587
  if (interpolationFn) {
@@ -7,7 +7,7 @@ import {type TransitionParameters} from './properties.ts';
7
7
  const spec = styleSpec.sky;
8
8
 
9
9
  test('Sky with defaults', () => {
10
- const sky = new Sky({});
10
+ const sky = new Sky({}, {});
11
11
  sky.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters);
12
12
 
13
13
  expect(sky.properties.get('atmosphere-blend')).toEqual(spec['atmosphere-blend'].default);
@@ -16,7 +16,7 @@ test('Sky with defaults', () => {
16
16
  test('Sky with options', () => {
17
17
  const sky = new Sky({
18
18
  'atmosphere-blend': 0.4
19
- });
19
+ }, {});
20
20
  sky.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters);
21
21
 
22
22
  expect(sky.properties.get('atmosphere-blend')).toBe(0.4);
@@ -32,7 +32,7 @@ test('Sky with interpolate function', () => {
32
32
  5, 1,
33
33
  7, 0
34
34
  ]
35
- });
35
+ }, {});
36
36
  sky.recalculate({zoom: 6, zoomHistory: {}} as EvaluationParameters);
37
37
 
38
38
  expect(sky.properties.get('atmosphere-blend')).toBe(0.5);
@@ -41,12 +41,12 @@ test('Sky with interpolate function', () => {
41
41
  test('Sky.getSky', () => {
42
42
  const defaults = {'atmosphere-blend': 0.8};
43
43
 
44
- expect(new Sky(defaults).getSky()).toEqual(defaults);
44
+ expect(new Sky(defaults, {}).getSky()).toEqual(defaults);
45
45
  });
46
46
 
47
47
  describe('Sky.setSky', () => {
48
48
  test('sets Sky', () => {
49
- const sky = new Sky({});
49
+ const sky = new Sky({}, {});
50
50
  sky.setSky({'atmosphere-blend': 1});
51
51
  sky.updateTransitions({
52
52
  now: 0,
@@ -60,9 +60,9 @@ describe('Sky.setSky', () => {
60
60
  });
61
61
 
62
62
  test('validates by default', () => {
63
- const sky = new Sky({});
63
+ const sky = new Sky({}, {});
64
64
  const skySpy = vi.spyOn(sky, '_validate');
65
- vi.spyOn(console, 'error').mockImplementation(() => { });
65
+ vi.spyOn(console, 'error').mockImplementation(() => {});
66
66
  sky.setSky({'atmosphere-blend': -1});
67
67
  sky.updateTransitions({transition: false} as any as TransitionParameters);
68
68
  sky.recalculate({zoom: 16, zoomHistory: {}, now: 10} as EvaluationParameters);
@@ -72,7 +72,7 @@ describe('Sky.setSky', () => {
72
72
  });
73
73
 
74
74
  test('respects validation option', () => {
75
- const sky = new Sky({});
75
+ const sky = new Sky({}, {});
76
76
 
77
77
  const skySpy = vi.spyOn(sky, '_validate');
78
78
  sky.setSky({'atmosphere-blend': -1}, {validate: false});
@@ -93,7 +93,7 @@ describe('Sky runtime error logging', () => {
93
93
  test('warns with the sky property location when an expression errors at runtime', () => {
94
94
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
95
95
 
96
- const sky = new Sky({});
96
+ const sky = new Sky({}, {});
97
97
  // global-state defeats constant-folding, so this fails at evaluation time (not parse time).
98
98
  sky.setSky({'sky-color': ['to-color', ['global-state', 'missing']]} as any, {validate: false});
99
99
  sky.updateTransitions({transition: false} as any as TransitionParameters);
package/src/style/sky.ts CHANGED
@@ -18,9 +18,9 @@ export class Sky extends Evented {
18
18
  _transitionable: Transitionable<SkyProps>;
19
19
  _transitioning: Transitioning<SkyProps>;
20
20
 
21
- constructor(sky?: SkySpecification) {
21
+ constructor(sky: SkySpecification | undefined, globalState: Record<string, any>) {
22
22
  super();
23
- this._transitionable = new Transitionable(getProperties(), 'sky', undefined);
23
+ this._transitionable = new Transitionable(getProperties(), 'sky', globalState);
24
24
  this.setSky(sky);
25
25
  this._transitioning = this._transitionable.untransitioned();
26
26
  this.recalculate(new EvaluationParameters(0));