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.
- package/build/readme.md +0 -2
- package/dist/maplibre-gl-dev.mjs +433 -573
- package/dist/maplibre-gl-dev.mjs.map +1 -1
- package/dist/maplibre-gl-shared-dev.mjs +984 -850
- package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
- package/dist/maplibre-gl-shared.mjs +2 -2
- package/dist/maplibre-gl-shared.mjs.map +1 -1
- package/dist/maplibre-gl-worker-dev.mjs +14 -12
- package/dist/maplibre-gl-worker-dev.mjs.map +1 -1
- package/dist/maplibre-gl-worker.mjs +2 -2
- package/dist/maplibre-gl-worker.mjs.map +1 -1
- package/dist/maplibre-gl.css +1 -1
- package/dist/maplibre-gl.d.ts +119 -42
- package/dist/maplibre-gl.mjs +22 -22
- package/dist/maplibre-gl.mjs.map +1 -1
- package/package.json +20 -22
- package/src/css/maplibre-gl.css +4 -0
- package/src/data/bucket/circle_bucket.ts +3 -2
- package/src/data/bucket/fill_bucket.ts +3 -2
- package/src/data/bucket/fill_extrusion_bucket.test.ts +22 -0
- package/src/data/bucket/fill_extrusion_bucket.ts +17 -4
- package/src/data/bucket/line_bucket.test.ts +5 -5
- package/src/data/bucket/line_bucket.ts +3 -2
- package/src/data/bucket/round_polygon_corners.test.ts +159 -0
- package/src/data/bucket/round_polygon_corners.ts +121 -0
- package/src/data/bucket/symbol_bucket.test.ts +7 -7
- package/src/data/dem_data.test.ts +23 -23
- package/src/data/feature_position_map.test.ts +1 -1
- package/src/data/segment.test.ts +14 -14
- package/src/geo/bounds.test.ts +4 -4
- package/src/geo/lng_lat_bounds.test.ts +1 -1
- package/src/geo/projection/covering_tiles.bench.ts +39 -0
- package/src/geo/projection/covering_tiles.test.ts +3 -7
- package/src/geo/projection/covering_tiles.ts +37 -2
- package/src/geo/projection/globe_covering_tiles_details_provider.ts +4 -4
- package/src/geo/projection/globe_projection.test.ts +2 -2
- package/src/geo/projection/globe_projection.ts +6 -15
- package/src/geo/projection/globe_transform.test.ts +3 -6
- package/src/geo/projection/globe_transform.ts +4 -7
- package/src/geo/projection/mercator_covering_tiles_details_provider.ts +4 -4
- package/src/geo/projection/mercator_projection.ts +1 -13
- package/src/geo/projection/mercator_transform.test.ts +1 -1
- package/src/geo/projection/mercator_transform.ts +2 -1
- package/src/geo/projection/mercator_utils.test.ts +8 -8
- package/src/geo/projection/projection.ts +0 -27
- package/src/geo/projection/projection_data.ts +8 -0
- package/src/geo/projection/projection_factory.ts +3 -3
- package/src/geo/projection/vertical_perspective_projection.ts +3 -60
- package/src/geo/projection/vertical_perspective_transform.ts +16 -23
- package/src/geo/transform_interface.ts +1 -2
- package/src/index.test.ts +7 -12
- package/src/index.ts +4 -2
- package/src/render/glyph_manager.test.ts +7 -22
- package/src/render/painter.test.ts +2 -0
- package/src/render/painter.ts +8 -11
- package/src/render/subdivision.bench.ts +39 -0
- package/src/render/subdivision.test.ts +22 -23
- package/src/render/subdivision.ts +46 -0
- package/src/render/terrain.test.ts +68 -34
- package/src/render/terrain.ts +69 -26
- package/src/shaders/glsl/_projection_globe.fragment.glsl +12 -0
- package/src/shaders/glsl/_projection_globe.fragment.glsl.g.ts +2 -0
- package/src/shaders/glsl/_projection_globe.vertex.glsl +29 -10
- package/src/shaders/glsl/_projection_globe.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/_projection_mercator.fragment.glsl +4 -0
- package/src/shaders/glsl/_projection_mercator.fragment.glsl.g.ts +2 -0
- package/src/shaders/glsl/line.fragment.glsl +2 -0
- package/src/shaders/glsl/line.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_gradient.fragment.glsl +2 -0
- package/src/shaders/glsl/line_gradient.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_gradient_sdf.fragment.glsl +2 -0
- package/src/shaders/glsl/line_gradient_sdf.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_pattern.fragment.glsl +2 -0
- package/src/shaders/glsl/line_pattern.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_sdf.fragment.glsl +2 -0
- package/src/shaders/glsl/line_sdf.fragment.glsl.g.ts +1 -1
- package/src/shaders/shaders.ts +4 -6
- package/src/source/canvas_source.test.ts +1 -1
- package/src/source/geojson_source.test.ts +32 -4
- package/src/source/geojson_source.ts +41 -0
- package/src/source/geojson_source_diff.test.ts +2 -2
- package/src/source/geojson_worker_source.test.ts +9 -5
- package/src/source/image_source.test.ts +137 -62
- package/src/source/image_source.ts +49 -12
- package/src/source/raster_dem_tile_source.test.ts +2 -2
- package/src/source/raster_tile_source.test.ts +64 -9
- package/src/source/raster_tile_source.ts +2 -1
- package/src/source/rtl_text_plugin_main_thread.test.ts +14 -14
- package/src/source/source.test.ts +8 -8
- package/src/source/vector_tile_source.test.ts +1 -1
- package/src/source/vector_tile_worker_source.test.ts +7 -5
- package/src/source/video_source.test.ts +1 -1
- package/src/source/worker.test.ts +1 -1
- package/src/source/worker_tile.test.ts +10 -8
- package/src/style/light.test.ts +9 -9
- package/src/style/light.ts +3 -3
- package/src/style/load_glyph_range.test.ts +8 -8
- package/src/style/load_sprite.test.ts +1 -1
- package/src/style/properties.test.ts +94 -2
- package/src/style/properties.ts +30 -1
- package/src/style/sky.test.ts +9 -9
- package/src/style/sky.ts +2 -2
- package/src/style/style.test.ts +38 -36
- package/src/style/style.ts +3 -3
- package/src/style/style_layer/color_relief_style_layer.test.ts +1 -1
- package/src/style/style_layer/fill_extrusion_style_layer.ts +6 -3
- package/src/style/style_layer/fill_extrusion_style_layer_properties.g.ts +14 -1
- package/src/style/style_layer/raster_style_layer.test.ts +2 -2
- package/src/style/style_layer.test.ts +5 -51
- package/src/style/validate_style.test.ts +1 -1
- package/src/symbol/collision_index.test.ts +1 -1
- package/src/symbol/grid_index.test.ts +1 -1
- package/src/tile/terrain_tile_manager.test.ts +2 -2
- package/src/tile/tile_id.test.ts +5 -5
- package/src/tile/tile_manager.test.ts +113 -72
- package/src/tile/tile_manager.ts +2 -2
- package/src/tile/tile_manager_in_view_tiles.test.ts +2 -2
- package/src/ui/camera.test.ts +138 -135
- package/src/ui/control/geolocate_control.test.ts +7 -30
- package/src/ui/handler/dblclick_zoom.test.ts +2 -2
- package/src/ui/handler/mouse.ts +15 -7
- package/src/ui/handler/mouse_handler_interface.test.ts +21 -1
- package/src/ui/handler/scroll_zoom.test.ts +2 -0
- package/src/ui/handler_manager.test.ts +7 -7
- package/src/ui/hash.test.ts +1 -0
- package/src/ui/map.ts +45 -20
- package/src/ui/map_tests/map_basic.test.ts +11 -23
- package/src/ui/map_tests/map_calculate_camera_options.test.ts +6 -6
- package/src/ui/map_tests/map_events.test.ts +5 -7
- package/src/ui/map_tests/map_global_state.test.ts +3 -3
- package/src/ui/map_tests/map_images.test.ts +4 -3
- package/src/ui/map_tests/map_is_moving.test.ts +1 -1
- package/src/ui/map_tests/map_is_rotating.test.ts +1 -1
- package/src/ui/map_tests/map_layer.test.ts +2 -1
- package/src/ui/map_tests/map_options.test.ts +1 -5
- package/src/ui/map_tests/map_pitch.test.ts +6 -6
- package/src/ui/map_tests/map_refresh_tiles.test.ts +2 -2
- package/src/ui/map_tests/map_render.test.ts +1 -1
- package/src/ui/map_tests/map_set_lod_params.test.ts +1 -1
- package/src/ui/map_tests/map_style.test.ts +9 -1
- package/src/ui/map_tests/map_terrain.test.ts +41 -1
- package/src/ui/map_tests/map_zoom.test.ts +15 -15
- package/src/ui/marker.test.ts +71 -14
- package/src/ui/marker.ts +30 -6
- package/src/ui/popup.test.ts +3 -3
- package/src/util/actor.test.ts +6 -6
- package/src/util/ajax.test.ts +27 -39
- package/src/util/browser.test.ts +7 -9
- package/src/util/create_tile_mesh.test.ts +2 -2
- package/src/util/dispatcher.test.ts +1 -1
- package/src/util/evented.test.ts +4 -1
- package/src/util/fast_maths.test.ts +4 -4
- package/src/util/image_request.test.ts +6 -4
- package/src/util/primitives/bounding_volume_cache.ts +3 -1
- package/src/util/struct_array.test.ts +2 -2
- package/src/util/task_queue.test.ts +1 -1
- package/src/util/test/util.ts +1 -0
- package/src/util/time_control.test.ts +2 -2
- package/src/util/transferable_grid_index.test.ts +1 -1
- package/src/util/util.test.ts +27 -13
- package/src/util/verticalize_punctuation.test.ts +4 -4
- package/src/util/web_worker_transfer.test.ts +5 -5
- package/src/webgl/draw/draw_custom.test.ts +13 -13
- package/src/webgl/draw/draw_debug.test.ts +1 -1
- package/src/webgl/draw/draw_fill.test.ts +13 -12
- package/src/webgl/draw/draw_symbol.test.ts +17 -16
- package/src/webgl/program/program_uniforms.ts +0 -3
- package/src/webgl/program/projection_program.ts +12 -10
- package/src/webgl/program/terrain_program.test.ts +2 -2
- package/src/webgl/program.ts +6 -4
- package/src/webgl/render_to_texture.test.ts +5 -5
- package/src/webgl/state.test.ts +1 -1
- package/src/webgl/texture.test.ts +3 -3
- package/src/webgl/vertex_buffer.test.ts +1 -1
- package/build/rolldown_plugins.ts +0 -18
- package/src/geo/projection/globe_projection_error_measurement.ts +0 -229
- package/src/shaders/glsl/projection_error_measurement.fragment.glsl +0 -5
- package/src/shaders/glsl/projection_error_measurement.fragment.glsl.g.ts +0 -2
- package/src/shaders/glsl/projection_error_measurement.vertex.glsl +0 -22
- package/src/shaders/glsl/projection_error_measurement.vertex.glsl.g.ts +0 -2
- package/src/webgl/program/projection_error_measurement_program.ts +0 -23
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import {describe, beforeEach, test, expect, vi} from 'vitest';
|
|
1
|
+
import {describe, beforeEach, afterEach, test, expect, vi, type Mock} from 'vitest';
|
|
2
2
|
import {ImageSource} from './image_source.ts';
|
|
3
|
-
import {Evented} from '../util/evented.ts';
|
|
4
|
-
import {type IReadonlyTransform} from '../geo/transform_interface.ts';
|
|
5
3
|
import {extend, MAX_TILE_ZOOM} from '../util/util.ts';
|
|
6
4
|
import {type FakeServer, fakeServer} from 'nise';
|
|
7
|
-
import {
|
|
8
|
-
import {sleep, stubAjaxGetImage, waitForEvent} from '../util/test/util.ts';
|
|
5
|
+
import {beforeMapTest, createMap, sleep, stubAjaxGetImage, waitForEvent} from '../util/test/util.ts';
|
|
9
6
|
import {Tile} from '../tile/tile.ts';
|
|
10
7
|
import {OverscaledTileID} from '../tile/tile_id.ts';
|
|
11
|
-
import {
|
|
8
|
+
import {ImageRequest} from '../util/image_request.ts';
|
|
9
|
+
import type {Texture} from '../webgl/texture.ts';
|
|
12
10
|
import type {ImageSourceSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
13
|
-
import {
|
|
11
|
+
import type {MapSourceDataEvent} from '../ui/events.ts';
|
|
12
|
+
import type {Map} from '../ui/map.ts';
|
|
14
13
|
|
|
15
14
|
function createSource(options) {
|
|
16
15
|
options = extend({
|
|
@@ -20,36 +19,23 @@ function createSource(options) {
|
|
|
20
19
|
return new ImageSource('id', options, {} as any, options.eventedParent);
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
class StubMap extends Evented {
|
|
24
|
-
transform: IReadonlyTransform;
|
|
25
|
-
painter: any;
|
|
26
|
-
_requestManager: RequestManager;
|
|
27
|
-
|
|
28
|
-
constructor() {
|
|
29
|
-
super();
|
|
30
|
-
this.transform = new MercatorTransform();
|
|
31
|
-
this._requestManager = {
|
|
32
|
-
transformRequest: (url) => {
|
|
33
|
-
return {url};
|
|
34
|
-
}
|
|
35
|
-
} as any as RequestManager;
|
|
36
|
-
this.painter = {
|
|
37
|
-
context: {
|
|
38
|
-
gl: {}
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
22
|
describe('ImageSource', () => {
|
|
45
23
|
stubAjaxGetImage(undefined);
|
|
46
24
|
let server: FakeServer;
|
|
25
|
+
let map: Map;
|
|
47
26
|
|
|
48
27
|
beforeEach(() => {
|
|
28
|
+
beforeMapTest();
|
|
49
29
|
global.fetch = null;
|
|
50
30
|
server = fakeServer.create();
|
|
51
31
|
server.respondWith(new ArrayBuffer(1));
|
|
52
32
|
server.respondWith('/missing-image.png', [404, {}, '']);
|
|
33
|
+
map = createMap({style: null});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
map.remove();
|
|
38
|
+
vi.restoreAllMocks();
|
|
53
39
|
});
|
|
54
40
|
|
|
55
41
|
test('constructor', () => {
|
|
@@ -65,33 +51,66 @@ describe('ImageSource', () => {
|
|
|
65
51
|
source.on('dataloading', (e) => {
|
|
66
52
|
expect(e.dataType).toBe('source');
|
|
67
53
|
});
|
|
68
|
-
source.onAdd(
|
|
54
|
+
source.onAdd(map);
|
|
69
55
|
await sleep(0);
|
|
70
56
|
server.respond();
|
|
71
57
|
await sleep(0);
|
|
72
58
|
expect(source.image).toBeTruthy();
|
|
73
59
|
});
|
|
74
60
|
|
|
61
|
+
test('passes a live AbortController to ImageRequest when the source is aborted during an async transformRequest', async () => {
|
|
62
|
+
const source = createSource({url: '/image.png'});
|
|
63
|
+
let transformStarted: () => void;
|
|
64
|
+
const transformCalled = new Promise<void>((resolve) => {
|
|
65
|
+
transformStarted = resolve;
|
|
66
|
+
});
|
|
67
|
+
let releaseTransform: (params: {url: string}) => void;
|
|
68
|
+
map.setTransformRequest(() => {
|
|
69
|
+
transformStarted();
|
|
70
|
+
return new Promise<{url: string}>((resolve) => {
|
|
71
|
+
releaseTransform = resolve;
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
const image = {width: 1, height: 1} as ImageBitmap;
|
|
75
|
+
let requestController: AbortController;
|
|
76
|
+
let sourceControllerAtRequestTime: AbortController;
|
|
77
|
+
const getImageSpy = vi.spyOn(ImageRequest, 'getImage').mockImplementation(async (_request, abortController) => {
|
|
78
|
+
requestController = abortController;
|
|
79
|
+
sourceControllerAtRequestTime = source._request;
|
|
80
|
+
return {data: image};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
source.onAdd(map);
|
|
84
|
+
await transformCalled; // load() is suspended in the transform
|
|
85
|
+
source.onRemove(); // the abort lands during that suspension
|
|
86
|
+
const loaded = waitForEvent(source, 'data', (e: MapSourceDataEvent) => e.sourceDataType === 'metadata');
|
|
87
|
+
releaseTransform({url: '/image.png'});
|
|
88
|
+
await loaded;
|
|
89
|
+
|
|
90
|
+
expect(getImageSpy).toHaveBeenCalledTimes(1);
|
|
91
|
+
// The request must carry the source's own live controller, so an abort
|
|
92
|
+
// arriving while it is in flight reaches exactly this request.
|
|
93
|
+
expect(requestController).toBeInstanceOf(AbortController);
|
|
94
|
+
expect(requestController).toBe(sourceControllerAtRequestTime);
|
|
95
|
+
});
|
|
96
|
+
|
|
75
97
|
test('transforms url request', () => {
|
|
98
|
+
const transformRequest = vi.fn((url: string, _resourceType?: string) => ({url}));
|
|
76
99
|
const source = createSource({url: '/image.png'});
|
|
77
|
-
|
|
78
|
-
const spy = vi.spyOn(map._requestManager, 'transformRequest');
|
|
100
|
+
map.setTransformRequest(transformRequest);
|
|
79
101
|
source.onAdd(map);
|
|
80
102
|
server.respond();
|
|
81
|
-
expect(
|
|
82
|
-
expect(
|
|
83
|
-
expect(
|
|
103
|
+
expect(transformRequest).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(transformRequest.mock.calls[0][0]).toBe('/image.png');
|
|
105
|
+
expect(transformRequest.mock.calls[0][1]).toBe('Image');
|
|
84
106
|
});
|
|
85
107
|
|
|
86
108
|
test('can asynchronously transform request', async () => {
|
|
87
109
|
const source = createSource({url: '/image.png'});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
headers: {Authorization: 'Bearer token'}
|
|
93
|
-
})
|
|
94
|
-
};
|
|
110
|
+
map.setTransformRequest(async (url) => ({
|
|
111
|
+
url,
|
|
112
|
+
headers: {Authorization: 'Bearer token'}
|
|
113
|
+
}));
|
|
95
114
|
const promise = source.once('data');
|
|
96
115
|
source.onAdd(map);
|
|
97
116
|
await sleep(0);
|
|
@@ -102,24 +121,23 @@ describe('ImageSource', () => {
|
|
|
102
121
|
});
|
|
103
122
|
|
|
104
123
|
test('updates url from updateImage', () => {
|
|
124
|
+
const transformRequest = vi.fn((url: string, _resourceType?: string) => ({url}));
|
|
105
125
|
const source = createSource({url: '/image.png'});
|
|
106
|
-
|
|
107
|
-
const spy = vi.spyOn(map._requestManager, 'transformRequest');
|
|
126
|
+
map.setTransformRequest(transformRequest);
|
|
108
127
|
source.onAdd(map);
|
|
109
128
|
server.respond();
|
|
110
|
-
expect(
|
|
111
|
-
expect(
|
|
112
|
-
expect(
|
|
129
|
+
expect(transformRequest).toHaveBeenCalledTimes(1);
|
|
130
|
+
expect(transformRequest.mock.calls[0][0]).toBe('/image.png');
|
|
131
|
+
expect(transformRequest.mock.calls[0][1]).toBe('Image');
|
|
113
132
|
source.updateImage({url: '/image2.png'});
|
|
114
133
|
server.respond();
|
|
115
|
-
expect(
|
|
116
|
-
expect(
|
|
117
|
-
expect(
|
|
134
|
+
expect(transformRequest).toHaveBeenCalledTimes(2);
|
|
135
|
+
expect(transformRequest.mock.calls[1][0]).toBe('/image2.png');
|
|
136
|
+
expect(transformRequest.mock.calls[1][1]).toBe('Image');
|
|
118
137
|
});
|
|
119
138
|
|
|
120
139
|
test('sets coordinates', () => {
|
|
121
140
|
const source = createSource({url: '/image.png'});
|
|
122
|
-
const map = new StubMap() as any;
|
|
123
141
|
source.onAdd(map);
|
|
124
142
|
server.respond();
|
|
125
143
|
const beforeSerialized = source.serialize();
|
|
@@ -131,7 +149,6 @@ describe('ImageSource', () => {
|
|
|
131
149
|
|
|
132
150
|
test('sets coordinates via updateImage', async () => {
|
|
133
151
|
const source = createSource({url: '/image.png'});
|
|
134
|
-
const map = new StubMap() as any;
|
|
135
152
|
source.onAdd(map);
|
|
136
153
|
server.respond();
|
|
137
154
|
const beforeSerialized = source.serialize();
|
|
@@ -150,7 +167,7 @@ describe('ImageSource', () => {
|
|
|
150
167
|
test('fires data event when content is loaded', async () => {
|
|
151
168
|
const source = createSource({url: '/image.png'});
|
|
152
169
|
const promise = waitForEvent(source, 'data', (e) => e.dataType === 'source' && e.sourceDataType === 'content');
|
|
153
|
-
source.onAdd(
|
|
170
|
+
source.onAdd(map);
|
|
154
171
|
await sleep(0);
|
|
155
172
|
server.respond();
|
|
156
173
|
await promise;
|
|
@@ -160,7 +177,7 @@ describe('ImageSource', () => {
|
|
|
160
177
|
test('fires data event when metadata is loaded', async () => {
|
|
161
178
|
const source = createSource({url: '/image.png'});
|
|
162
179
|
const promise = waitForEvent(source, 'data', (e) => e.dataType === 'source' && e.sourceDataType === 'metadata');
|
|
163
|
-
source.onAdd(
|
|
180
|
+
source.onAdd(map);
|
|
164
181
|
await sleep(0);
|
|
165
182
|
server.respond();
|
|
166
183
|
await expect(promise).resolves.toBeDefined();
|
|
@@ -170,7 +187,7 @@ describe('ImageSource', () => {
|
|
|
170
187
|
const source = createSource({url: '/image.png'});
|
|
171
188
|
const tile = new Tile(new OverscaledTileID(1, 0, 1, 0, 0), 512);
|
|
172
189
|
const promise = waitForEvent(source, 'data', (e) => e.dataType === 'source' && e.sourceDataType === 'idle');
|
|
173
|
-
source.onAdd(
|
|
190
|
+
source.onAdd(map);
|
|
174
191
|
server.respond();
|
|
175
192
|
|
|
176
193
|
source.tiles[String(tile.tileID.wrap)] = tile;
|
|
@@ -192,7 +209,6 @@ describe('ImageSource', () => {
|
|
|
192
209
|
});
|
|
193
210
|
|
|
194
211
|
test('allows using updateImage before initial image is loaded', async () => {
|
|
195
|
-
const map = new StubMap() as any;
|
|
196
212
|
const source = createSource({url: '/image.png', eventedParent: map});
|
|
197
213
|
|
|
198
214
|
// Suppress errors because we're aborting when updating.
|
|
@@ -208,7 +224,6 @@ describe('ImageSource', () => {
|
|
|
208
224
|
});
|
|
209
225
|
|
|
210
226
|
test('cancels request if updateImage is used', async () => {
|
|
211
|
-
const map = new StubMap() as any;
|
|
212
227
|
const source = createSource({url: '/image.png', eventedParent: map});
|
|
213
228
|
|
|
214
229
|
// Suppress errors because we're aborting.
|
|
@@ -223,7 +238,6 @@ describe('ImageSource', () => {
|
|
|
223
238
|
});
|
|
224
239
|
|
|
225
240
|
test('marks the source as loaded when the request has received a response', async () => {
|
|
226
|
-
const map = new StubMap() as any;
|
|
227
241
|
const source = createSource({url: '/image.png', eventedParent: map});
|
|
228
242
|
|
|
229
243
|
expect(source.loaded()).toBe(false);
|
|
@@ -248,7 +262,6 @@ describe('ImageSource', () => {
|
|
|
248
262
|
});
|
|
249
263
|
|
|
250
264
|
test('does not throw when updateImage is called while a request is pending', async () => {
|
|
251
|
-
const map = new StubMap() as any;
|
|
252
265
|
const source = createSource({url: '/image.png', eventedParent: map});
|
|
253
266
|
|
|
254
267
|
const errorHandler = vi.fn();
|
|
@@ -262,10 +275,75 @@ describe('ImageSource', () => {
|
|
|
262
275
|
expect(errorHandler).not.toHaveBeenCalled();
|
|
263
276
|
});
|
|
264
277
|
|
|
278
|
+
describe('updateImage with a decoded image', () => {
|
|
279
|
+
let source: ImageSource;
|
|
280
|
+
let transformRequest: Mock<(url: string, resourceType?: string) => any>;
|
|
281
|
+
|
|
282
|
+
beforeEach(() => {
|
|
283
|
+
transformRequest = vi.fn((url: string, _resourceType?: string) => ({url}));
|
|
284
|
+
map.setTransformRequest(transformRequest);
|
|
285
|
+
// Suppress errors from aborting the initial (never-responded) request.
|
|
286
|
+
map.on('error', () => {});
|
|
287
|
+
source = createSource({url: '/image.png', eventedParent: map});
|
|
288
|
+
// onAdd starts the initial load synchronously up to its first await, so
|
|
289
|
+
// this._request is set and transformRequest is called once. Clear that call
|
|
290
|
+
// so tests can assert the image path issues no further request.
|
|
291
|
+
source.onAdd(map);
|
|
292
|
+
transformRequest.mockClear();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('sets the image directly without a network request and fires metadata', () => {
|
|
296
|
+
const handler = vi.fn();
|
|
297
|
+
source.on('data', handler);
|
|
298
|
+
const bitmap = new ImageBitmap();
|
|
299
|
+
const result = source.updateImage({image: bitmap});
|
|
300
|
+
|
|
301
|
+
expect(result).toBe(source);
|
|
302
|
+
// The image path must not trigger a request.
|
|
303
|
+
expect(transformRequest).not.toHaveBeenCalled();
|
|
304
|
+
expect(source.image).toBe(bitmap);
|
|
305
|
+
expect(source.loaded()).toBe(true);
|
|
306
|
+
const firedMetadata = handler.mock.calls.some(
|
|
307
|
+
([e]) => e.dataType === 'source' && e.sourceDataType === 'metadata'
|
|
308
|
+
);
|
|
309
|
+
expect(firedMetadata).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('resets the texture so the new image is uploaded on the next prepare', () => {
|
|
313
|
+
source.texture = {} as Texture;
|
|
314
|
+
source.updateImage({image: new ImageBitmap()});
|
|
315
|
+
|
|
316
|
+
expect(source.texture).toBeNull();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('updates coordinates alongside the image', () => {
|
|
320
|
+
source.updateImage({
|
|
321
|
+
image: new ImageBitmap(),
|
|
322
|
+
coordinates: [[0, 0], [-1, 0], [-1, -1], [0, -1]]
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
expect(source.serialize().coordinates).toEqual([[0, 0], [-1, 0], [-1, -1], [0, -1]]);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('cancels a pending request', () => {
|
|
329
|
+
const spy = vi.spyOn(source._request, 'abort');
|
|
330
|
+
source.updateImage({image: new ImageBitmap()});
|
|
331
|
+
expect(spy).toHaveBeenCalled();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('accepts an ImageData instance', () => {
|
|
335
|
+
const imageData = new ImageData(1, 1);
|
|
336
|
+
source.updateImage({image: imageData});
|
|
337
|
+
|
|
338
|
+
expect(transformRequest).not.toHaveBeenCalled();
|
|
339
|
+
expect(source.image).toBe(imageData);
|
|
340
|
+
expect(source.loaded()).toBe(true);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
265
344
|
describe('terrainTileRanges', () => {
|
|
266
345
|
test('sets tile ranges for all zoom levels', () => {
|
|
267
346
|
const source = createSource({url: '/image.png'});
|
|
268
|
-
const map = new StubMap() as any;
|
|
269
347
|
source.onAdd(map);
|
|
270
348
|
server.respond();
|
|
271
349
|
source.setCoordinates([[-10, 10], [10, 10], [10, -10], [-10, -10]]);
|
|
@@ -277,7 +355,6 @@ describe('ImageSource', () => {
|
|
|
277
355
|
|
|
278
356
|
test('calculates tile ranges properly', () => {
|
|
279
357
|
const source = createSource({url: '/image.png'});
|
|
280
|
-
const map = new StubMap() as any;
|
|
281
358
|
source.onAdd(map);
|
|
282
359
|
server.respond();
|
|
283
360
|
source.setCoordinates([[11.39585,47.30074],[11.46585,47.30074],[11.46585,47.25074],[11.39585,47.25074]]);
|
|
@@ -317,7 +394,6 @@ describe('ImageSource', () => {
|
|
|
317
394
|
|
|
318
395
|
test('calculates tile ranges for an image exceeds the world bounds - east', () => {
|
|
319
396
|
const source = createSource({url: '/image.png'});
|
|
320
|
-
const map = new StubMap() as any;
|
|
321
397
|
source.onAdd(map);
|
|
322
398
|
server.respond();
|
|
323
399
|
source.setCoordinates([[-180, 60], [270, 60], [270, -60], [-180, -60]]);
|
|
@@ -341,7 +417,6 @@ describe('ImageSource', () => {
|
|
|
341
417
|
|
|
342
418
|
test('calculates tile ranges for an image exceeds the world bounds - west', () => {
|
|
343
419
|
const source = createSource({url: '/image.png'});
|
|
344
|
-
const map = new StubMap() as any;
|
|
345
420
|
source.onAdd(map);
|
|
346
421
|
server.respond();
|
|
347
422
|
source.setCoordinates([[120, 60], [-270, 60], [-270, -60], [120, -60]]);
|
|
@@ -29,18 +29,34 @@ import {isAbortError} from '../util/abort_error.ts';
|
|
|
29
29
|
export type Coordinates = [[number, number], [number, number], [number, number], [number, number]];
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* An already-decoded image that can be handed to an {@link ImageSource} directly,
|
|
33
|
+
* without a network request.
|
|
34
|
+
*/
|
|
35
|
+
export type ImageSourceImage = HTMLImageElement | HTMLCanvasElement | ImageBitmap | ImageData;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The options object for the {@link ImageSource.updateImage} method.
|
|
39
|
+
*
|
|
40
|
+
* Provide exactly one of `url` (to load an image over the network) or `image`
|
|
41
|
+
* (an already-decoded image to display directly, without a network request).
|
|
33
42
|
*/
|
|
34
43
|
export type UpdateImageOptions = {
|
|
35
44
|
/**
|
|
36
|
-
*
|
|
45
|
+
* The image coordinates
|
|
46
|
+
*/
|
|
47
|
+
coordinates?: Coordinates;
|
|
48
|
+
} & ({
|
|
49
|
+
/**
|
|
50
|
+
* The image URL to load.
|
|
37
51
|
*/
|
|
38
52
|
url: string;
|
|
53
|
+
} | {
|
|
39
54
|
/**
|
|
40
|
-
*
|
|
55
|
+
* An already-decoded image (`HTMLImageElement`, `HTMLCanvasElement`, `ImageBitmap` or `ImageData`)
|
|
56
|
+
* to display directly, without a network request.
|
|
41
57
|
*/
|
|
42
|
-
|
|
43
|
-
};
|
|
58
|
+
image: ImageSourceImage;
|
|
59
|
+
});
|
|
44
60
|
|
|
45
61
|
export type CanonicalTileRange = {
|
|
46
62
|
minTileY: number;
|
|
@@ -96,6 +112,10 @@ export type CanonicalTileRange = {
|
|
|
96
112
|
* ]
|
|
97
113
|
* })
|
|
98
114
|
*
|
|
115
|
+
* // update with an already-decoded image (no network request)
|
|
116
|
+
* const bitmap = await createImageBitmap(myCanvas);
|
|
117
|
+
* mySource.updateImage({image: bitmap});
|
|
118
|
+
*
|
|
99
119
|
* map.removeSource('some id'); // remove
|
|
100
120
|
* ```
|
|
101
121
|
*/
|
|
@@ -118,7 +138,7 @@ export class ImageSource extends Evented<SourceEventType> implements Source {
|
|
|
118
138
|
dispatcher: Dispatcher;
|
|
119
139
|
map: Map;
|
|
120
140
|
texture: Texture | null;
|
|
121
|
-
image:
|
|
141
|
+
image: ImageSourceImage;
|
|
122
142
|
tileID: CanonicalTileID;
|
|
123
143
|
tileCoords: Point[];
|
|
124
144
|
flippedWindingOrder: boolean = false;
|
|
@@ -150,9 +170,10 @@ export class ImageSource extends Evented<SourceEventType> implements Source {
|
|
|
150
170
|
|
|
151
171
|
this.url = this.options.url;
|
|
152
172
|
|
|
173
|
+
const request = await this.map._requestManager.transformRequest(this.url, ResourceType.Image);
|
|
153
174
|
this._request = new AbortController();
|
|
154
175
|
try {
|
|
155
|
-
const image = await ImageRequest.getImage(
|
|
176
|
+
const image = await ImageRequest.getImage(request, this._request);
|
|
156
177
|
this._request = null;
|
|
157
178
|
this._loaded = true;
|
|
158
179
|
|
|
@@ -177,21 +198,37 @@ export class ImageSource extends Evented<SourceEventType> implements Source {
|
|
|
177
198
|
}
|
|
178
199
|
|
|
179
200
|
/**
|
|
180
|
-
* Updates the image
|
|
201
|
+
* Updates the image and, optionally, the coordinates. To avoid having the image flash after changing,
|
|
181
202
|
* set the `raster-fade-duration` paint property on the raster layer to 0.
|
|
182
203
|
*
|
|
204
|
+
* Provide exactly one of `url` (to fetch a new image over the network) or `image` (an
|
|
205
|
+
* already-decoded `HTMLImageElement`, `HTMLCanvasElement`, `ImageBitmap` or `ImageData` to
|
|
206
|
+
* display directly, without a network request).
|
|
207
|
+
*
|
|
183
208
|
* @param options - The options object.
|
|
184
209
|
*/
|
|
185
210
|
updateImage(options: UpdateImageOptions): this {
|
|
186
|
-
if (!options.url) {
|
|
187
|
-
return this;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
211
|
if (this._request) {
|
|
191
212
|
this._request.abort();
|
|
192
213
|
this._request = null;
|
|
193
214
|
}
|
|
194
215
|
|
|
216
|
+
if ('image' in options) {
|
|
217
|
+
// Use the already-decoded image directly, skipping the network request.
|
|
218
|
+
this._loaded = true;
|
|
219
|
+
this.image = options.image;
|
|
220
|
+
if (options.coordinates) {
|
|
221
|
+
this.coordinates = options.coordinates;
|
|
222
|
+
}
|
|
223
|
+
this.texture = null;
|
|
224
|
+
this._finishLoading();
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!options.url) {
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
195
232
|
this.options.url = options.url;
|
|
196
233
|
this.load(options.coordinates).finally(() => this.texture = null);
|
|
197
234
|
return this;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {describe, beforeEach, afterEach, test, expect, vi
|
|
1
|
+
import {describe, beforeEach, afterEach, test, expect, vi} from 'vitest';
|
|
2
2
|
import {fakeServer, type FakeServer} from 'nise';
|
|
3
3
|
import {RasterDEMTileSource} from './raster_dem_tile_source.ts';
|
|
4
4
|
import {OverscaledTileID} from '../tile/tile_id.ts';
|
|
@@ -198,7 +198,7 @@ describe('RasterDEMTileSource', () => {
|
|
|
198
198
|
]);
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
test('serializes options', () => {
|
|
202
202
|
const source = createSource({
|
|
203
203
|
tiles: ['http://localhost:2900/raster-dem/{z}/{x}/{y}.png'],
|
|
204
204
|
minzoom: 2,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {describe, beforeEach, afterEach, test, expect, vi
|
|
1
|
+
import {describe, beforeEach, afterEach, test, expect, vi} from 'vitest';
|
|
2
2
|
import {RasterTileSource} from './raster_tile_source.ts';
|
|
3
3
|
import {OverscaledTileID} from '../tile/tile_id.ts';
|
|
4
4
|
import {RequestManager} from '../util/request_manager.ts';
|
|
@@ -216,15 +216,21 @@ describe('RasterTileSource', () => {
|
|
|
216
216
|
expect(tile.state).toBe('loaded');
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
-
test('supports updating tiles', () => {
|
|
219
|
+
test('supports updating tiles', async () => {
|
|
220
|
+
server.respondWith('/source.json', JSON.stringify({
|
|
221
|
+
minzoom: 0,
|
|
222
|
+
maxzoom: 22,
|
|
223
|
+
attribution: 'MapLibre',
|
|
224
|
+
tiles: ['http://example.com/{z}/{x}/{y}.png'],
|
|
225
|
+
}));
|
|
220
226
|
const source = createSource({url: '/source.json'});
|
|
227
|
+
|
|
228
|
+
server.respondImmediately = true;
|
|
229
|
+
const promise = waitForEvent(source, 'data', (e: MapSourceDataEvent) => e.sourceDataType === 'metadata');
|
|
221
230
|
source.setTiles(['http://example.com/{z}/{x}/{y}.png?updated=true']);
|
|
231
|
+
await promise;
|
|
222
232
|
|
|
223
|
-
source.
|
|
224
|
-
if (e.sourceDataType === 'metadata') {
|
|
225
|
-
expect(source.tiles[0]).toBe('http://example.com/{z}/{x}/{y}.png?updated=true');
|
|
226
|
-
}
|
|
227
|
-
});
|
|
233
|
+
expect(source.tiles[0]).toBe('http://example.com/{z}/{x}/{y}.png?updated=true');
|
|
228
234
|
});
|
|
229
235
|
|
|
230
236
|
test('cancels TileJSON request if removed', async () => {
|
|
@@ -256,7 +262,7 @@ describe('RasterTileSource', () => {
|
|
|
256
262
|
|
|
257
263
|
await waitForEvent(source, 'data', (e: MapSourceDataEvent) => e.sourceDataType === 'metadata');
|
|
258
264
|
|
|
259
|
-
expect(server.requests
|
|
265
|
+
expect(server.requests).toHaveLength(2);
|
|
260
266
|
expect(server.requests[0].aborted).toBe(true);
|
|
261
267
|
expect(source.serialize()).toEqual({
|
|
262
268
|
type: 'raster',
|
|
@@ -265,7 +271,7 @@ describe('RasterTileSource', () => {
|
|
|
265
271
|
expect(errorHandler).not.toHaveBeenCalled();
|
|
266
272
|
});
|
|
267
273
|
|
|
268
|
-
|
|
274
|
+
test('serializes options', () => {
|
|
269
275
|
const source = createSource({
|
|
270
276
|
tiles: ['http://localhost:2900/raster/{z}/{x}/{y}.png'],
|
|
271
277
|
minzoom: 2,
|
|
@@ -439,6 +445,55 @@ describe('RasterTileSource', () => {
|
|
|
439
445
|
expect(expiryDataSpy).toHaveBeenCalledTimes(1);
|
|
440
446
|
});
|
|
441
447
|
|
|
448
|
+
test('passes a live AbortController to ImageRequest when the tile is aborted during an async transformRequest', async () => {
|
|
449
|
+
let transformStarted: () => void;
|
|
450
|
+
const transformCalled = new Promise<void>((resolve) => {
|
|
451
|
+
transformStarted = resolve;
|
|
452
|
+
});
|
|
453
|
+
let releaseTransform: (params: {url: string}) => void;
|
|
454
|
+
const source = createSource({
|
|
455
|
+
tiles: ['http://example.com/{z}/{x}/{y}.png']
|
|
456
|
+
}, (url, type) => {
|
|
457
|
+
if (type !== 'Tile') return {url};
|
|
458
|
+
transformStarted();
|
|
459
|
+
return new Promise<{url: string}>((resolve) => {
|
|
460
|
+
releaseTransform = resolve;
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
source.tiles = ['http://example.com/{z}/{x}/{y}.png'];
|
|
464
|
+
|
|
465
|
+
const image = {width: 256, height: 256} as ImageBitmap;
|
|
466
|
+
let requestController: AbortController;
|
|
467
|
+
let tileControllerAtRequestTime: AbortController;
|
|
468
|
+
const getImageSpy = vi.spyOn(ImageRequest, 'getImage').mockImplementation(async (_request, abortController) => {
|
|
469
|
+
requestController = abortController;
|
|
470
|
+
tileControllerAtRequestTime = tile.abortController;
|
|
471
|
+
return {data: image};
|
|
472
|
+
});
|
|
473
|
+
source.map.painter = {
|
|
474
|
+
context: {gl: {}},
|
|
475
|
+
getTileTexture: () => ({update: vi.fn()})
|
|
476
|
+
} as any;
|
|
477
|
+
|
|
478
|
+
const tile = {
|
|
479
|
+
tileID: new OverscaledTileID(10, 0, 10, 5, 5),
|
|
480
|
+
state: 'loading',
|
|
481
|
+
setExpiryData() {}
|
|
482
|
+
} as any as Tile;
|
|
483
|
+
|
|
484
|
+
const loadPromise = source.loadTile(tile);
|
|
485
|
+
await transformCalled; // loadTile is suspended in the transform
|
|
486
|
+
await source.abortTile(tile); // the abort lands during that suspension
|
|
487
|
+
releaseTransform({url: 'http://example.com/10/5/5.png'});
|
|
488
|
+
await expect(loadPromise).resolves.toBeUndefined();
|
|
489
|
+
|
|
490
|
+
expect(getImageSpy).toHaveBeenCalledTimes(1);
|
|
491
|
+
// The request must carry the tile's own live controller, so an abort
|
|
492
|
+
// arriving while it is in flight reaches exactly this request.
|
|
493
|
+
expect(requestController).toBeInstanceOf(AbortController);
|
|
494
|
+
expect(requestController).toBe(tileControllerAtRequestTime);
|
|
495
|
+
});
|
|
496
|
+
|
|
442
497
|
test('does not throw when tile is aborted', async () => {
|
|
443
498
|
const source = createSource({
|
|
444
499
|
minzoom: 0,
|
|
@@ -206,10 +206,11 @@ export class RasterTileSource extends Evented<SourceEventType> implements Source
|
|
|
206
206
|
const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
|
|
207
207
|
const premultiply = this._premultiplyAlpha;
|
|
208
208
|
const imageBitmapOptions = premultiply ? undefined : {premultiplyAlpha: 'none'} as const;
|
|
209
|
+
const request = await this.map._requestManager.transformRequest(url, ResourceType.Tile);
|
|
209
210
|
tile.abortController = new AbortController();
|
|
210
211
|
try {
|
|
211
212
|
const response = await ImageRequest.getImage(
|
|
212
|
-
|
|
213
|
+
request,
|
|
213
214
|
tile.abortController,
|
|
214
215
|
this.map._refreshExpiredTiles,
|
|
215
216
|
imageBitmapOptions
|