maplibre-gl 6.2.0 → 6.3.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/dist/maplibre-gl-dev.mjs +522 -223
- package/dist/maplibre-gl-dev.mjs.map +1 -1
- package/dist/maplibre-gl-shared-dev.mjs +103 -58
- 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 +23 -49
- 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 +214 -59
- package/dist/maplibre-gl.mjs +44 -44
- package/dist/maplibre-gl.mjs.map +1 -1
- package/package.json +10 -20
- package/src/data/feature_index.bench.ts +21 -0
- package/src/data/segment.test.ts +2 -2
- package/src/geo/edge_insets.test.ts +1 -1
- package/src/geo/lng_lat.test.ts +7 -7
- package/src/geo/lng_lat_bounds.test.ts +4 -4
- package/src/geo/mercator_coordinate.test.ts +2 -2
- package/src/geo/projection/camera_helper.ts +5 -0
- package/src/geo/projection/globe_transform.test.ts +25 -0
- package/src/geo/projection/globe_transform.ts +7 -3
- package/src/geo/projection/mercator_camera_helper.ts +1 -1
- package/src/geo/projection/mercator_transform.ts +21 -5
- package/src/geo/projection/vertical_perspective_camera_helper.ts +60 -31
- package/src/geo/projection/vertical_perspective_transform.ts +7 -1
- package/src/geo/transform_interface.ts +13 -1
- package/src/index.ts +7 -2
- package/src/render/image_atlas.test.ts +72 -0
- package/src/render/image_atlas.ts +17 -2
- package/src/render/image_manager.test.ts +46 -0
- package/src/render/image_manager.ts +9 -2
- package/src/render/painter.ts +1 -12
- package/src/render/update_pattern_positions_in_program.test.ts +1 -1
- package/src/shaders/glsl/_prelude.vertex.glsl +2 -3
- package/src/shaders/glsl/_prelude.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/circle.vertex.glsl +4 -4
- package/src/shaders/glsl/circle.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/fill_extrusion.vertex.glsl +3 -3
- package/src/shaders/glsl/fill_extrusion.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl +5 -5
- package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/heatmap.vertex.glsl +4 -4
- package/src/shaders/glsl/heatmap.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line.vertex.glsl +7 -7
- package/src/shaders/glsl/line.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_gradient.vertex.glsl +6 -6
- package/src/shaders/glsl/line_gradient.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_gradient_sdf.vertex.glsl +7 -7
- package/src/shaders/glsl/line_gradient_sdf.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_pattern.vertex.glsl +7 -7
- package/src/shaders/glsl/line_pattern.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_sdf.vertex.glsl +7 -7
- package/src/shaders/glsl/line_sdf.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/raster.fragment.glsl +4 -4
- package/src/shaders/glsl/raster.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/raster.vertex.glsl +11 -6
- package/src/shaders/glsl/raster.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/symbol_icon.vertex.glsl +5 -5
- package/src/shaders/glsl/symbol_icon.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/symbol_sdf.vertex.glsl +5 -5
- package/src/shaders/glsl/symbol_sdf.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl +6 -6
- package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl.g.ts +1 -1
- package/src/source/canvas_source.test.ts +31 -0
- package/src/source/canvas_source.ts +2 -1
- package/src/source/geojson_worker_source.test.ts +88 -0
- package/src/source/geojson_worker_source.ts +22 -39
- package/src/source/image_source.test.ts +205 -7
- package/src/source/image_source.ts +118 -4
- package/src/source/raster_dem_tile_worker_source.test.ts +1 -1
- package/src/source/vector_tile_worker_source.test.ts +94 -2
- package/src/source/vector_tile_worker_source.ts +18 -27
- package/src/source/video_source.test.ts +12 -8
- package/src/source/worker_tile.bench.ts +10 -0
- package/src/source/worker_tile.ts +0 -3
- package/src/style/create_style_layer.bench.ts +15 -0
- package/src/style/properties.bench.ts +81 -0
- package/src/style/style.test.ts +11 -10
- package/src/style/style_image.ts +67 -9
- package/src/style/style_layer.bench.ts +65 -0
- package/src/style/style_layer.test.ts +1 -1
- package/src/style/style_layer.ts +2 -2
- package/src/style/validate_style.bench.ts +11 -0
- package/src/style/validate_style.test.ts +2 -2
- package/src/symbol/anchor.test.ts +2 -2
- package/src/symbol/collision_index.bench.ts +116 -0
- package/src/symbol/cross_tile_symbol_index.bench.ts +49 -0
- package/src/symbol/shaping.test.ts +2 -1
- package/src/symbol/symbol_layout.bench.ts +30 -0
- package/src/tile/tile_id.test.ts +1 -1
- package/src/tile/tile_manager.ts +2 -2
- package/src/ui/control/fullscreen_control.test.ts +10 -10
- package/src/ui/control/geolocate_control.test.ts +3 -5
- package/src/ui/control/globe_control.test.ts +6 -6
- package/src/ui/events.ts +1 -1
- package/src/ui/handler/scroll_zoom.test.ts +50 -0
- package/src/ui/handler/two_fingers_touch.test.ts +3 -3
- package/src/ui/handler_manager.test.ts +243 -3
- package/src/ui/handler_manager.ts +79 -16
- package/src/ui/map.ts +17 -3
- package/src/ui/map_tests/map_cross_window.test.ts +2 -2
- package/src/ui/map_tests/map_events.test.ts +3 -3
- package/src/ui/map_tests/map_images.test.ts +29 -0
- package/src/ui/map_tests/map_style.test.ts +9 -8
- package/src/ui/map_tests/map_webgl.test.ts +29 -0
- package/src/ui/marker.test.ts +26 -42
- package/src/ui/popup.test.ts +14 -14
- package/src/util/evented.test.ts +33 -28
- package/src/util/evented.ts +69 -43
- package/src/util/test/null_gl.ts +10 -0
- package/src/util/test/util.ts +2 -1
- package/src/util/util.test.ts +1 -1
- package/src/util/web_worker_transfer.test.ts +5 -5
- package/src/webgl/context.ts +20 -0
- package/src/webgl/draw/draw_raster.ts +11 -6
- package/src/webgl/program/raster_program.ts +11 -0
- package/src/webgl/program.ts +31 -16
- package/src/webgl/texture.test.ts +16 -1
- package/src/webgl/texture.ts +3 -0
- package/src/webgl/vertex_buffer.test.ts +17 -2
- package/src/webgl/vertex_buffer.ts +24 -13
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {describe,
|
|
2
|
-
import {VideoSource} from './video_source.ts';
|
|
3
|
-
import {extend} from '../util/util.ts';
|
|
1
|
+
import {describe, expect, test, vi} from 'vitest';
|
|
4
2
|
import {getMockDispatcher, waitForEvent} from '../util/test/util.ts';
|
|
5
|
-
|
|
6
|
-
import
|
|
3
|
+
import {extend} from '../util/util.ts';
|
|
4
|
+
import {VideoSource} from './video_source.ts';
|
|
5
|
+
import {MercatorTransform} from '../geo/projection/mercator_transform.ts';
|
|
7
6
|
import {Tile} from '../tile/tile.ts';
|
|
8
7
|
import {OverscaledTileID} from '../tile/tile_id.ts';
|
|
9
8
|
import {Evented} from '../util/evented.ts';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
9
|
+
import type {IReadonlyTransform} from '../geo/transform_interface.ts';
|
|
10
|
+
import type {Coordinates} from './image_source.ts';
|
|
12
11
|
|
|
13
12
|
class StubMap extends Evented {
|
|
14
13
|
transform: IReadonlyTransform;
|
|
@@ -117,7 +116,7 @@ describe('VideoSource', () => {
|
|
|
117
116
|
expect(tile.state).toBe('loaded');
|
|
118
117
|
});
|
|
119
118
|
|
|
120
|
-
test('onRemove removes playing listener and
|
|
119
|
+
test('onRemove removes playing listener, pauses video and deletes the texture', () => {
|
|
121
120
|
const video = window.document.createElement('video');
|
|
122
121
|
const removeListenerSpy = vi.spyOn(video, 'removeEventListener');
|
|
123
122
|
const pauseSpy = vi.spyOn(video, 'pause').mockImplementation(() => {});
|
|
@@ -129,9 +128,14 @@ describe('VideoSource', () => {
|
|
|
129
128
|
});
|
|
130
129
|
source.video = video;
|
|
131
130
|
|
|
131
|
+
const texture = {destroy: vi.fn()} as any;
|
|
132
|
+
source.texture = texture;
|
|
133
|
+
|
|
132
134
|
source.onRemove();
|
|
133
135
|
|
|
134
136
|
expect(removeListenerSpy).toHaveBeenCalledWith('playing', expect.any(Function));
|
|
135
137
|
expect(pauseSpy).toHaveBeenCalled();
|
|
138
|
+
expect(texture.destroy).toHaveBeenCalledTimes(1);
|
|
139
|
+
expect(source.texture).toBeNull();
|
|
136
140
|
});
|
|
137
141
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {bench} from 'vitest';
|
|
2
|
+
import {fixtureTiles, parseTile} from '../../test/bench/lib/parse_tile.ts';
|
|
3
|
+
|
|
4
|
+
await Promise.all(fixtureTiles.map(tile => parseTile(tile, false)));
|
|
5
|
+
|
|
6
|
+
bench('WorkerTile.parse', async () => {
|
|
7
|
+
for (const tile of fixtureTiles) {
|
|
8
|
+
await parseTile(tile, false);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
@@ -37,7 +37,6 @@ export class WorkerTile {
|
|
|
37
37
|
collectResourceTiming: boolean;
|
|
38
38
|
returnDependencies: boolean;
|
|
39
39
|
|
|
40
|
-
status: 'parsing' | 'done';
|
|
41
40
|
data: VectorTileLike;
|
|
42
41
|
collisionBoxArray: CollisionBoxArray;
|
|
43
42
|
|
|
@@ -61,7 +60,6 @@ export class WorkerTile {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
async parse(data: VectorTileLike, layerIndex: StyleLayerIndex, availableImages: string[], actor: IActor, subdivisionGranularity: SubdivisionGranularitySetting): Promise<WorkerTileResult> {
|
|
64
|
-
this.status = 'parsing';
|
|
65
63
|
this.data = data;
|
|
66
64
|
|
|
67
65
|
this.collisionBoxArray = new CollisionBoxArray();
|
|
@@ -192,7 +190,6 @@ export class WorkerTile {
|
|
|
192
190
|
}
|
|
193
191
|
}
|
|
194
192
|
|
|
195
|
-
this.status = 'done';
|
|
196
193
|
return {
|
|
197
194
|
buckets: Object.values(buckets).filter(b => !b.isEmpty()),
|
|
198
195
|
featureIndex,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {bench} from 'vitest';
|
|
2
|
+
import {derefLayers} from '@maplibre/maplibre-gl-style-spec';
|
|
3
|
+
import {createStyleLayer} from './create_style_layer.ts';
|
|
4
|
+
import brightV9 from '../../test/integration/assets/styles/bright-v9.json' with {type: 'json'};
|
|
5
|
+
|
|
6
|
+
import type {StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
7
|
+
|
|
8
|
+
const style = brightV9 as unknown as StyleSpecification;
|
|
9
|
+
const layers = derefLayers(style.layers);
|
|
10
|
+
|
|
11
|
+
bench('createStyleLayer', () => {
|
|
12
|
+
for (const layer of layers) {
|
|
13
|
+
createStyleLayer(layer, {});
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {bench, describe} from 'vitest';
|
|
2
|
+
import {latest as spec, convertFunction, isFunction, createFunction, createPropertyExpression} from '@maplibre/maplibre-gl-style-spec';
|
|
3
|
+
import brightV9 from '../../test/integration/assets/styles/bright-v9.json' with {type: 'json'};
|
|
4
|
+
|
|
5
|
+
import type {StyleSpecification, StylePropertyExpression, StylePropertySpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
6
|
+
|
|
7
|
+
type Sample = {
|
|
8
|
+
propertySpec: StylePropertySpecification;
|
|
9
|
+
rawValue: unknown;
|
|
10
|
+
rawExpression: unknown;
|
|
11
|
+
compiledFunction: StylePropertyExpression;
|
|
12
|
+
compiledExpression: StylePropertyExpression;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function sample(rawValue: unknown, propertySpec: StylePropertySpecification): Sample {
|
|
16
|
+
const rawExpression = convertFunction(rawValue, propertySpec);
|
|
17
|
+
const compiledFunction = createFunction(rawValue, propertySpec) as StylePropertyExpression;
|
|
18
|
+
const compiledExpression = createPropertyExpression(rawExpression, 'expression', propertySpec);
|
|
19
|
+
if (compiledExpression.result === 'error') {
|
|
20
|
+
throw new Error(compiledExpression.value.map(err => `${err.key}: ${err.message}`).join(', '));
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
propertySpec,
|
|
24
|
+
rawValue,
|
|
25
|
+
rawExpression,
|
|
26
|
+
compiledFunction,
|
|
27
|
+
compiledExpression: compiledExpression.value
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function collect(style: StyleSpecification): Sample[] {
|
|
32
|
+
const samples: Sample[] = [];
|
|
33
|
+
|
|
34
|
+
for (const layer of style.layers) {
|
|
35
|
+
if (!layer.type) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const key in layer.paint) {
|
|
40
|
+
if (isFunction(layer.paint[key])) {
|
|
41
|
+
samples.push(sample(layer.paint[key], spec[`paint_${layer.type}`][key]));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const key in layer.layout) {
|
|
46
|
+
if (isFunction(layer.layout[key])) {
|
|
47
|
+
samples.push(sample(layer.layout[key], spec[`layout_${layer.type}`][key]));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return samples;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const samples = collect(brightV9 as unknown as StyleSpecification);
|
|
56
|
+
|
|
57
|
+
describe('style property functions', () => {
|
|
58
|
+
bench('createFunction', () => {
|
|
59
|
+
for (const {rawValue, propertySpec} of samples) {
|
|
60
|
+
createFunction(rawValue, propertySpec);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
bench('evaluate function', () => {
|
|
65
|
+
for (const {compiledFunction} of samples) {
|
|
66
|
+
compiledFunction.evaluate({zoom: 0});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
bench('createPropertyExpression', () => {
|
|
71
|
+
for (const {rawExpression, propertySpec} of samples) {
|
|
72
|
+
createPropertyExpression(rawExpression, 'expression', propertySpec);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
bench('evaluate expression', () => {
|
|
77
|
+
for (const {compiledExpression} of samples) {
|
|
78
|
+
compiledExpression.evaluate({zoom: 0});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
package/src/style/style.test.ts
CHANGED
|
@@ -3,8 +3,9 @@ import {Style} from './style.ts';
|
|
|
3
3
|
import {TileManager} from '../tile/tile_manager.ts';
|
|
4
4
|
import {StyleLayer} from './style_layer.ts';
|
|
5
5
|
import {extend} from '../util/util.ts';
|
|
6
|
-
import {Event} from '../util/evented.ts';
|
|
6
|
+
import {ErrorEvent, Event} from '../util/evented.ts';
|
|
7
7
|
import {type AJAXError} from '../util/ajax.ts';
|
|
8
|
+
import {MapSourceDataEvent} from '../ui/events.ts';
|
|
8
9
|
import {RGBAImage} from '../util/image.ts';
|
|
9
10
|
import {rtlMainThreadPluginFactory} from '../source/rtl_text_plugin_main_thread.ts';
|
|
10
11
|
import {browser} from '../util/browser.ts';
|
|
@@ -374,7 +375,7 @@ describe('Style.loadJSON', () => {
|
|
|
374
375
|
}));
|
|
375
376
|
|
|
376
377
|
await style.once('style.load');
|
|
377
|
-
expect(style.tileManagers['mapLibre']
|
|
378
|
+
expect(style.tileManagers['mapLibre']).toBeInstanceOf(TileManager);
|
|
378
379
|
});
|
|
379
380
|
|
|
380
381
|
test('creates layers', async () => {
|
|
@@ -396,7 +397,7 @@ describe('Style.loadJSON', () => {
|
|
|
396
397
|
});
|
|
397
398
|
|
|
398
399
|
await style.once('style.load');
|
|
399
|
-
expect(style.getLayer('fill')
|
|
400
|
+
expect(style.getLayer('fill')).toBeInstanceOf(StyleLayer);
|
|
400
401
|
});
|
|
401
402
|
|
|
402
403
|
test('transforms sprite json and image URLs before request', async () => {
|
|
@@ -460,7 +461,7 @@ describe('Style.loadJSON', () => {
|
|
|
460
461
|
const errorPromise = style.once('error');
|
|
461
462
|
|
|
462
463
|
await style.once('style.load');
|
|
463
|
-
style._layers.background.fire(new
|
|
464
|
+
style._layers.background.fire(new ErrorEvent(new Error('test'), {mapLibre: true}));
|
|
464
465
|
|
|
465
466
|
const e = await errorPromise;
|
|
466
467
|
expect((e as any).layer).toEqual({id: 'background'});
|
|
@@ -1347,8 +1348,8 @@ describe('Style.addSource', () => {
|
|
|
1347
1348
|
});
|
|
1348
1349
|
|
|
1349
1350
|
style.addSource('source-id', source); // fires data twice
|
|
1350
|
-
style.tileManagers['source-id'].fire(new
|
|
1351
|
-
style.tileManagers['source-id'].fire(new
|
|
1351
|
+
style.tileManagers['source-id'].fire(new ErrorEvent(new Error('test')));
|
|
1352
|
+
style.tileManagers['source-id'].fire(new MapSourceDataEvent('data'));
|
|
1352
1353
|
|
|
1353
1354
|
await expect(Promise.all(promises)).resolves.toBeDefined();
|
|
1354
1355
|
});
|
|
@@ -1446,8 +1447,8 @@ describe('Style.removeSource', () => {
|
|
|
1446
1447
|
|
|
1447
1448
|
style.on('data', () => expect(false).toBeTruthy());
|
|
1448
1449
|
style.on('error', () => expect(false).toBeTruthy());
|
|
1449
|
-
tileManager.fire(new
|
|
1450
|
-
tileManager.fire(new
|
|
1450
|
+
tileManager.fire(new MapSourceDataEvent('data'));
|
|
1451
|
+
tileManager.fire(new ErrorEvent(new Error('test')));
|
|
1451
1452
|
});
|
|
1452
1453
|
});
|
|
1453
1454
|
|
|
@@ -2297,7 +2298,7 @@ describe('Style.addLayer', () => {
|
|
|
2297
2298
|
type: 'background'
|
|
2298
2299
|
});
|
|
2299
2300
|
|
|
2300
|
-
style._layers.background.fire(new
|
|
2301
|
+
style._layers.background.fire(new ErrorEvent(new Error('test'), {mapLibre: true}));
|
|
2301
2302
|
|
|
2302
2303
|
const e = await errorPromise;
|
|
2303
2304
|
expect((e as any).layer).toEqual({id: 'background'});
|
|
@@ -2614,7 +2615,7 @@ describe('Style.removeLayer', () => {
|
|
|
2614
2615
|
// Bind a listener to prevent fallback Evented error reporting.
|
|
2615
2616
|
layer.on('error', () => {});
|
|
2616
2617
|
|
|
2617
|
-
layer.fire(new
|
|
2618
|
+
layer.fire(new ErrorEvent(new Error('test'), {mapLibre: true}));
|
|
2618
2619
|
|
|
2619
2620
|
expect(styleErrorListener).not.toHaveBeenCalled();
|
|
2620
2621
|
});
|
package/src/style/style_image.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type StyleImageData = {
|
|
|
27
27
|
data: RGBAImage;
|
|
28
28
|
version?: number;
|
|
29
29
|
hasRenderCallback?: boolean;
|
|
30
|
+
isWebGLImage?: boolean;
|
|
30
31
|
userImage?: StyleImageInterface;
|
|
31
32
|
spriteData?: SpriteOnDemandStyleImage;
|
|
32
33
|
};
|
|
@@ -91,6 +92,48 @@ export type StyleImageMetadata = {
|
|
|
91
92
|
*/
|
|
92
93
|
export type StyleImage = StyleImageData & StyleImageMetadata;
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Where a {@link StyleImageWebGLData.renderWithWebGL} callback writes its pixels.
|
|
97
|
+
*/
|
|
98
|
+
export type StyleImageWebGLTarget = {
|
|
99
|
+
gl: WebGL2RenderingContext;
|
|
100
|
+
/**
|
|
101
|
+
* The icon atlas to write into. MapLibre does not bind it for you, so start with
|
|
102
|
+
* `gl.bindTexture(gl.TEXTURE_2D, texture)` or attach it to your own framebuffer.
|
|
103
|
+
*/
|
|
104
|
+
texture: WebGLTexture;
|
|
105
|
+
x: number;
|
|
106
|
+
y: number;
|
|
107
|
+
width: number;
|
|
108
|
+
height: number;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* What a {@link StyleImageInterface} gives as its `data` when it renders itself with WebGL rather
|
|
113
|
+
* than handing over an array of pixels.
|
|
114
|
+
*
|
|
115
|
+
* @see [Animate an icon on the GPU.](https://maplibre.org/maplibre-gl-js/docs/examples/animate-an-icon-on-the-gpu/)
|
|
116
|
+
*/
|
|
117
|
+
export type StyleImageWebGLData = {
|
|
118
|
+
/**
|
|
119
|
+
* Render exactly `width` x `height` premultiplied-alpha pixels at (`x`, `y`) of
|
|
120
|
+
* `target.texture`. That rectangle is the only part of the shared atlas that belongs to this
|
|
121
|
+
* image; drawing outside it corrupts the others.
|
|
122
|
+
*
|
|
123
|
+
* This is the image's counterpart to {@link CustomLayerInterface.render}, and the context
|
|
124
|
+
* arrives in the same state a custom layer's is given: cull face, active texture and the pixel
|
|
125
|
+
* store settings at their WebGL defaults, and no vertex array bound. Everything is yours to
|
|
126
|
+
* change, and MapLibre restores its own state afterwards. The scissor test is the one
|
|
127
|
+
* exception: MapLibre never touches it, so an image that enables it has to disable it again.
|
|
128
|
+
*
|
|
129
|
+
* Called before the first frame the image is used in, again whenever
|
|
130
|
+
* {@link StyleImageInterface.render} returns `true`, and again for each atlas holding a slot
|
|
131
|
+
* this image has never rendered into, so one change may mean several calls with different
|
|
132
|
+
* targets.
|
|
133
|
+
*/
|
|
134
|
+
renderWithWebGL: (target: StyleImageWebGLTarget) => void;
|
|
135
|
+
};
|
|
136
|
+
|
|
94
137
|
/**
|
|
95
138
|
* Interface for dynamically generated style images. This is a specification for
|
|
96
139
|
* implementers to model: it is not an exported method or class.
|
|
@@ -148,7 +191,13 @@ export type StyleImage = StyleImageData & StyleImageMetadata;
|
|
|
148
191
|
export interface StyleImageInterface {
|
|
149
192
|
width: number;
|
|
150
193
|
height: number;
|
|
151
|
-
|
|
194
|
+
/**
|
|
195
|
+
* The image's pixels, in the same format as `ImageData`, or a {@link StyleImageWebGLData}
|
|
196
|
+
* callback that renders them with WebGL. A WebGL image renders straight into its slot of the
|
|
197
|
+
* shared icon atlas. Nothing new is possible that pixels could not express, but an image that
|
|
198
|
+
* changes often, such as an animated icon, gets much cheaper: no CPU pixel work and no upload.
|
|
199
|
+
*/
|
|
200
|
+
data: Uint8Array | Uint8ClampedArray | StyleImageWebGLData;
|
|
152
201
|
/**
|
|
153
202
|
* This method is called once before every frame where the icon will be used.
|
|
154
203
|
* The method can optionally update the image's `data` member with a new image.
|
|
@@ -156,6 +205,9 @@ export interface StyleImageInterface {
|
|
|
156
205
|
* If the method updates the image it must return `true` to commit the change.
|
|
157
206
|
* If the method returns `false` or nothing the image is assumed to not have changed.
|
|
158
207
|
*
|
|
208
|
+
* An image whose `data` renders with WebGL has nothing to update here; returning `true` is how
|
|
209
|
+
* it asks for {@link StyleImageWebGLData.renderWithWebGL} to be called again.
|
|
210
|
+
*
|
|
159
211
|
* If updates are infrequent it maybe easier to use {@link Map.updateImage} to update
|
|
160
212
|
* the image instead of implementing this method.
|
|
161
213
|
*
|
|
@@ -171,18 +223,24 @@ export interface StyleImageInterface {
|
|
|
171
223
|
/**
|
|
172
224
|
* Optional method called when the icon is removed from the map with {@link Map.removeImage}.
|
|
173
225
|
* This gives the image a chance to clean up resources and event listeners.
|
|
226
|
+
*
|
|
227
|
+
* This also fires when the WebGL context is lost, after which the same image is added back
|
|
228
|
+
* without a matching `onAdd`, so the image has to be able to build again whatever it
|
|
229
|
+
* released here.
|
|
174
230
|
*/
|
|
175
231
|
onRemove?: () => void;
|
|
176
232
|
}
|
|
177
233
|
|
|
234
|
+
export function isStyleImageWebGLData(data: StyleImageInterface['data']): data is StyleImageWebGLData {
|
|
235
|
+
return typeof (data as StyleImageWebGLData)?.renderWithWebGL === 'function';
|
|
236
|
+
}
|
|
237
|
+
|
|
178
238
|
export function renderStyleImage(image: StyleImage): boolean {
|
|
179
239
|
const {userImage} = image;
|
|
180
|
-
if (userImage?.render)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
return false;
|
|
240
|
+
if (!userImage?.render) return false;
|
|
241
|
+
const updated = userImage.render();
|
|
242
|
+
if (!updated) return false;
|
|
243
|
+
|
|
244
|
+
if (!isStyleImageWebGLData(userImage.data)) image.data.replace(new Uint8Array(userImage.data.buffer));
|
|
245
|
+
return true;
|
|
188
246
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {bench, describe} from 'vitest';
|
|
2
|
+
import {readFileSync} from 'fs';
|
|
3
|
+
import {VectorTile} from '@mapbox/vector-tile';
|
|
4
|
+
import {PbfReader} from 'pbf';
|
|
5
|
+
import {featureFilter} from '@maplibre/maplibre-gl-style-spec';
|
|
6
|
+
import filters from '../../test/bench/data/filters.json' with {type: 'json'};
|
|
7
|
+
|
|
8
|
+
import type {VectorTileFeature} from '@mapbox/vector-tile';
|
|
9
|
+
import type {FeatureFilter, FilterSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
10
|
+
|
|
11
|
+
type FilteredLayer = {
|
|
12
|
+
features: VectorTileFeature[];
|
|
13
|
+
filters: FeatureFilter[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function readLayers(): FilteredLayer[] {
|
|
17
|
+
const data = readFileSync(new URL('../../test/bench/data/785.vector.pbf', import.meta.url));
|
|
18
|
+
const tile = new VectorTile(new PbfReader(new Uint8Array(data)));
|
|
19
|
+
const layers: FilteredLayer[] = [];
|
|
20
|
+
|
|
21
|
+
for (const name in tile.layers) {
|
|
22
|
+
const layer = tile.layers[name];
|
|
23
|
+
if (!layer.length) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const features = [];
|
|
28
|
+
for (let i = 0; i < layer.length; i++) {
|
|
29
|
+
features.push(layer.feature(i));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const layerFilters = [];
|
|
33
|
+
for (const filter of filters) {
|
|
34
|
+
if (filter.layer === name) {
|
|
35
|
+
layerFilters.push(featureFilter(filter.filter as FilterSpecification, 'filter'));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
layers.push({features, filters: layerFilters});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return layers;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const layers = readLayers();
|
|
46
|
+
|
|
47
|
+
describe('featureFilter', () => {
|
|
48
|
+
bench('create', () => {
|
|
49
|
+
for (const filter of filters) {
|
|
50
|
+
featureFilter(filter.filter as FilterSpecification, 'filter');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
bench('evaluate', () => {
|
|
55
|
+
for (const layer of layers) {
|
|
56
|
+
for (const filter of layer.filters) {
|
|
57
|
+
for (const feature of layer.features) {
|
|
58
|
+
if (typeof filter.filter({zoom: 0}, feature) !== 'boolean') {
|
|
59
|
+
throw new Error('Expected boolean result from filter');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -13,7 +13,7 @@ describe('StyleLayer', () => {
|
|
|
13
13
|
test('instantiates the correct subclass', () => {
|
|
14
14
|
const layer = createStyleLayer({type: 'fill'} as LayerSpecification, {});
|
|
15
15
|
|
|
16
|
-
expect(layer
|
|
16
|
+
expect(layer).toBeInstanceOf(FillStyleLayer);
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
|
package/src/style/style_layer.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {filterObject} from '../util/util.ts';
|
|
|
2
2
|
|
|
3
3
|
import {createVisibilityExpression, featureFilter, supportsPropertyExpression} from '@maplibre/maplibre-gl-style-spec';
|
|
4
4
|
import {validateStyle, validateAndEmit, type Validator} from './validate_style.ts';
|
|
5
|
-
import {Evented, ErrorEvent} from '../util/evented.ts';
|
|
5
|
+
import {Evented, ErrorEvent, type ErrorEventType} from '../util/evented.ts';
|
|
6
6
|
import {Layout, Transitionable, type Transitioning, type Properties, PossiblyEvaluated, PossiblyEvaluatedPropertyValue, TRANSITION_SUFFIX} from './properties.ts';
|
|
7
7
|
|
|
8
8
|
import type {Bucket, BucketParameters} from '../data/bucket.ts';
|
|
@@ -83,7 +83,7 @@ const ERROR_LAYOUT_NOT_PAINT = ' is a LAYOUT property not a PAINT property. Use
|
|
|
83
83
|
/**
|
|
84
84
|
* A base class for style layers
|
|
85
85
|
*/
|
|
86
|
-
export abstract class StyleLayer extends Evented {
|
|
86
|
+
export abstract class StyleLayer extends Evented<ErrorEventType> {
|
|
87
87
|
id: string;
|
|
88
88
|
metadata: unknown;
|
|
89
89
|
type: LayerSpecification['type'] | CustomLayerInterface['type'];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {bench} from 'vitest';
|
|
2
|
+
import {validateStyle} from './validate_style.ts';
|
|
3
|
+
import brightV9 from '../../test/integration/assets/styles/bright-v9.json' with {type: 'json'};
|
|
4
|
+
|
|
5
|
+
import type {StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
6
|
+
|
|
7
|
+
const style = brightV9 as unknown as StyleSpecification;
|
|
8
|
+
|
|
9
|
+
bench('validateStyle', () => {
|
|
10
|
+
validateStyle(style);
|
|
11
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {describe, test, expect, vi, afterEach} from 'vitest';
|
|
2
2
|
import {validateAndEmit, validateStyle, validateStyleAndEmit} from './validate_style.ts';
|
|
3
|
-
import {Evented} from '../util/evented.ts';
|
|
3
|
+
import {Evented, type ErrorEventType} from '../util/evented.ts';
|
|
4
4
|
import type {StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
5
5
|
|
|
6
|
-
class TestEmitter extends Evented {}
|
|
6
|
+
class TestEmitter extends Evented<ErrorEventType> {}
|
|
7
7
|
|
|
8
8
|
afterEach(() => {
|
|
9
9
|
vi.restoreAllMocks();
|
|
@@ -3,8 +3,8 @@ import {Anchor} from './anchor.ts';
|
|
|
3
3
|
|
|
4
4
|
describe('Anchor', () => {
|
|
5
5
|
test('constructor', () => {
|
|
6
|
-
expect(new Anchor(0, 0, 0)
|
|
7
|
-
expect(new Anchor(0, 0, 0, 0)
|
|
6
|
+
expect(new Anchor(0, 0, 0)).toBeInstanceOf(Anchor);
|
|
7
|
+
expect(new Anchor(0, 0, 0, 0)).toBeInstanceOf(Anchor);
|
|
8
8
|
});
|
|
9
9
|
test('clone', () => {
|
|
10
10
|
const a = new Anchor(1, 2, 3);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {bench, describe} from 'vitest';
|
|
2
|
+
import Point from '@mapbox/point-geometry';
|
|
3
|
+
import {CollisionIndex} from './collision_index.ts';
|
|
4
|
+
import {EXTENT} from '../data/extent.ts';
|
|
5
|
+
import {OverscaledTileID} from '../tile/tile_id.ts';
|
|
6
|
+
import {MercatorTransform} from '../geo/projection/mercator_transform.ts';
|
|
7
|
+
import {GlobeTransform} from '../geo/projection/globe_transform.ts';
|
|
8
|
+
|
|
9
|
+
import type {mat4} from 'gl-matrix';
|
|
10
|
+
import type {ITransform} from '../geo/transform_interface.ts';
|
|
11
|
+
import type {UnwrappedTileID} from '../tile/tile_id.ts';
|
|
12
|
+
import type {SingleCollisionBox} from '../data/bucket/symbol_bucket.ts';
|
|
13
|
+
import type {OverlapMode} from '../style/style_layer/overlap_mode.ts';
|
|
14
|
+
|
|
15
|
+
type TestSymbol = {
|
|
16
|
+
collisionBox: SingleCollisionBox;
|
|
17
|
+
overlapMode: OverlapMode;
|
|
18
|
+
textPixelRatio: number;
|
|
19
|
+
tileID: OverscaledTileID;
|
|
20
|
+
unwrappedTileID: UnwrappedTileID;
|
|
21
|
+
pitchWithMap: boolean;
|
|
22
|
+
rotateWithMap: boolean;
|
|
23
|
+
translation: [number, number];
|
|
24
|
+
shift?: Point;
|
|
25
|
+
simpleProjectionMatrix?: mat4;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// https://stackoverflow.com/a/47593316
|
|
29
|
+
function splitmix32(a: number): () => number {
|
|
30
|
+
return function() {
|
|
31
|
+
a |= 0;
|
|
32
|
+
a = a + 0x9e3779b9 | 0;
|
|
33
|
+
let t = a ^ a >>> 16;
|
|
34
|
+
t = Math.imul(t, 0x21f0aaad);
|
|
35
|
+
t = t ^ t >>> 15;
|
|
36
|
+
t = Math.imul(t, 0x735a2d97);
|
|
37
|
+
return ((t = t ^ t >>> 15) >>> 0) / 4294967296;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const symbolCount = 20000;
|
|
42
|
+
|
|
43
|
+
function createSymbols(transform: ITransform, calculatePosMatrix: (tileID: UnwrappedTileID) => mat4 | undefined): TestSymbol[] {
|
|
44
|
+
transform.resize(1024, 1024, true);
|
|
45
|
+
|
|
46
|
+
const tileID = new OverscaledTileID(0, 0, 0, 0, 0);
|
|
47
|
+
const unwrappedTileID = tileID.toUnwrapped();
|
|
48
|
+
const rng = splitmix32(0xdeadbeef);
|
|
49
|
+
const rndRange = (min: number, max: number) => rng() * (max - min) + min;
|
|
50
|
+
const symbols: TestSymbol[] = [];
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < symbolCount; i++) {
|
|
53
|
+
symbols.push({
|
|
54
|
+
collisionBox: {
|
|
55
|
+
anchorPointX: rndRange(4, EXTENT - 4),
|
|
56
|
+
anchorPointY: rndRange(4, EXTENT - 4),
|
|
57
|
+
x1: rndRange(-20, -2),
|
|
58
|
+
y1: rndRange(-20, -2),
|
|
59
|
+
x2: rndRange(2, 20),
|
|
60
|
+
y2: rndRange(2, 20)
|
|
61
|
+
},
|
|
62
|
+
overlapMode: 'never',
|
|
63
|
+
textPixelRatio: 1,
|
|
64
|
+
tileID,
|
|
65
|
+
unwrappedTileID,
|
|
66
|
+
pitchWithMap: rng() > 0.5,
|
|
67
|
+
rotateWithMap: rng() > 0.5,
|
|
68
|
+
translation: [
|
|
69
|
+
rndRange(-20, 20),
|
|
70
|
+
rndRange(-20, 20)
|
|
71
|
+
],
|
|
72
|
+
shift: rng() > 0.5 ? new Point(rndRange(-20, 20), rndRange(-20, 20)) : undefined,
|
|
73
|
+
simpleProjectionMatrix: calculatePosMatrix(unwrappedTileID),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return symbols;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function placeAll(transform: ITransform, symbols: TestSymbol[]): void {
|
|
81
|
+
const collisionIndex = new CollisionIndex(transform);
|
|
82
|
+
collisionIndex.grid.hitTest = () => true;
|
|
83
|
+
|
|
84
|
+
for (const symbol of symbols) {
|
|
85
|
+
collisionIndex.placeCollisionBox(
|
|
86
|
+
symbol.collisionBox,
|
|
87
|
+
symbol.overlapMode,
|
|
88
|
+
symbol.textPixelRatio,
|
|
89
|
+
symbol.tileID,
|
|
90
|
+
symbol.unwrappedTileID,
|
|
91
|
+
symbol.pitchWithMap,
|
|
92
|
+
symbol.rotateWithMap,
|
|
93
|
+
symbol.translation,
|
|
94
|
+
null,
|
|
95
|
+
null,
|
|
96
|
+
symbol.shift,
|
|
97
|
+
symbol.simpleProjectionMatrix,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const mercatorTransform = new MercatorTransform({minZoom: 0, maxZoom: 22, minPitch: 0, maxPitch: 60, renderWorldCopies: true});
|
|
103
|
+
const mercatorSymbols = createSymbols(mercatorTransform, (tileID) => mercatorTransform.calculatePosMatrix(tileID, false));
|
|
104
|
+
|
|
105
|
+
const globeTransform = new GlobeTransform();
|
|
106
|
+
const globeSymbols = createSymbols(globeTransform, () => undefined);
|
|
107
|
+
|
|
108
|
+
describe('placeCollisionBox', () => {
|
|
109
|
+
bench('mercator', () => {
|
|
110
|
+
placeAll(mercatorTransform, mercatorSymbols);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
bench('globe', () => {
|
|
114
|
+
placeAll(globeTransform, globeSymbols);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {bench} from 'vitest';
|
|
2
|
+
import {CrossTileSymbolIndex} from './cross_tile_symbol_index.ts';
|
|
3
|
+
import {OverscaledTileID} from '../tile/tile_id.ts';
|
|
4
|
+
|
|
5
|
+
import type {Tile} from '../tile/tile.ts';
|
|
6
|
+
import type {StyleLayer} from '../style/style_layer.ts';
|
|
7
|
+
|
|
8
|
+
type TestSymbolInstance = {
|
|
9
|
+
anchorX: number;
|
|
10
|
+
anchorY: number;
|
|
11
|
+
key: string;
|
|
12
|
+
crossTileID: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const styleLayer = {id: 'test'} as StyleLayer;
|
|
16
|
+
const symbolCount = 3000;
|
|
17
|
+
|
|
18
|
+
function createTile(tileID: OverscaledTileID, symbolInstances: TestSymbolInstance[]): Tile {
|
|
19
|
+
const bucket = {
|
|
20
|
+
symbolInstances: {
|
|
21
|
+
get(i: number) { return symbolInstances[i]; },
|
|
22
|
+
length: symbolInstances.length
|
|
23
|
+
},
|
|
24
|
+
layerIds: ['test']
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
tileID,
|
|
28
|
+
getBucket: () => bucket,
|
|
29
|
+
latestFeatureIndex: {}
|
|
30
|
+
} as unknown as Tile;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createInstances(): TestSymbolInstance[] {
|
|
34
|
+
return Array.from({length: symbolCount}, (_, i) => ({
|
|
35
|
+
anchorX: 0,
|
|
36
|
+
anchorY: 0,
|
|
37
|
+
key: '',
|
|
38
|
+
crossTileID: i + 1
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const mainTile = createTile(new OverscaledTileID(6, 0, 6, 8, 8), createInstances());
|
|
43
|
+
const childTile = createTile(new OverscaledTileID(7, 0, 7, 16, 16), createInstances());
|
|
44
|
+
|
|
45
|
+
bench('CrossTileSymbolIndex.addLayer', () => {
|
|
46
|
+
const index = new CrossTileSymbolIndex();
|
|
47
|
+
index.addLayer(styleLayer, [mainTile], 0);
|
|
48
|
+
index.addLayer(styleLayer, [childTile], 0);
|
|
49
|
+
});
|