maplibre-gl 6.2.0 → 6.4.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 +1108 -427
- package/dist/maplibre-gl-dev.mjs.map +1 -1
- package/dist/maplibre-gl-shared-dev.mjs +359 -291
- 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 +467 -151
- package/dist/maplibre-gl.mjs +49 -49
- package/dist/maplibre-gl.mjs.map +1 -1
- package/package.json +12 -22
- package/src/data/bucket/fill_extrusion_bucket.ts +1 -13
- package/src/data/bucket/round_polygon_corners.test.ts +84 -36
- package/src/data/bucket/round_polygon_corners.ts +90 -39
- package/src/data/extent_bounds.ts +28 -1
- 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 +154 -1
- package/src/geo/projection/globe_transform.ts +7 -3
- package/src/geo/projection/globe_utils.ts +189 -2
- 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.test.ts +24 -0
- package/src/geo/projection/vertical_perspective_camera_helper.ts +71 -41
- 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 +180 -0
- package/src/render/image_atlas.ts +52 -6
- package/src/render/image_manager.test.ts +111 -0
- package/src/render/image_manager.ts +143 -144
- package/src/render/painter.ts +6 -14
- package/src/render/pattern_atlas.test.ts +131 -0
- package/src/render/pattern_atlas.ts +144 -0
- package/src/render/update_pattern_positions_in_program.test.ts +1 -1
- package/src/shaders/glsl/_prelude.vertex.glsl +10 -11
- 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/color_relief.fragment.glsl +1 -2
- package/src/shaders/glsl/color_relief.fragment.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/hillshade_prepare.fragment.glsl +12 -12
- package/src/shaders/glsl/hillshade_prepare.fragment.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 +246 -38
- package/src/source/image_source.ts +132 -19
- package/src/source/raster_dem_tile_source.test.ts +37 -3
- package/src/source/raster_dem_tile_source.ts +1 -2
- package/src/source/raster_dem_tile_worker_source.test.ts +1 -1
- package/src/source/raster_tile_source.test.ts +5 -26
- package/src/source/raster_tile_source.ts +4 -3
- 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 +75 -10
- package/src/style/style.ts +37 -64
- 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/cross_tile_symbol_index.test.ts +31 -0
- package/src/symbol/cross_tile_symbol_index.ts +67 -37
- 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.test.ts +69 -1
- package/src/tile/tile_manager.ts +16 -11
- 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 +17 -6
- package/src/ui/handler_inertia.test.ts +91 -0
- package/src/ui/handler_inertia.ts +65 -14
- 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 +55 -0
- package/src/ui/map_tests/map_style.test.ts +9 -8
- package/src/ui/map_tests/map_webgl.test.ts +30 -0
- package/src/ui/marker.test.ts +177 -42
- package/src/ui/marker.ts +120 -10
- 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/image_request.ts +31 -1
- 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_background.ts +1 -1
- package/src/webgl/draw/draw_raster.ts +11 -6
- package/src/webgl/program/pattern.ts +3 -3
- 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
package/src/style/style.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {isRasterStyleLayer} from './style_layer/raster_style_layer.ts';
|
|
|
5
5
|
import {createStyleLayer} from './create_style_layer.ts';
|
|
6
6
|
import {loadSprite} from './load_sprite.ts';
|
|
7
7
|
import {ImageManager, type MissingImageRequestHandler} from '../render/image_manager.ts';
|
|
8
|
+
import {PatternAtlas} from '../render/pattern_atlas.ts';
|
|
8
9
|
import {GlyphManager} from '../render/glyph_manager.ts';
|
|
9
10
|
import {Light} from './light.ts';
|
|
10
11
|
import {Sky} from './sky.ts';
|
|
@@ -205,6 +206,7 @@ export class Style extends Evented<MapEventType> {
|
|
|
205
206
|
stylesheet: StyleSpecification;
|
|
206
207
|
dispatcher: Dispatcher;
|
|
207
208
|
imageManager: ImageManager;
|
|
209
|
+
patternAtlas: PatternAtlas;
|
|
208
210
|
glyphManager: GlyphManager;
|
|
209
211
|
lineAtlas: LineAtlas;
|
|
210
212
|
light: Light;
|
|
@@ -229,10 +231,6 @@ export class Style extends Evented<MapEventType> {
|
|
|
229
231
|
_glyphsDidChange: boolean;
|
|
230
232
|
_updatedPaintProps: {[layer: string]: true};
|
|
231
233
|
_layerOrderChanged: boolean;
|
|
232
|
-
// image ids of images loaded from style's sprite
|
|
233
|
-
_spritesImagesIds: {[spriteId: string]: string[]};
|
|
234
|
-
// image ids of all images loaded (sprite + user)
|
|
235
|
-
_availableImages: string[];
|
|
236
234
|
_globalState: Record<string, any>;
|
|
237
235
|
crossTileSymbolIndex: CrossTileSymbolIndex;
|
|
238
236
|
pauseablePlacement: PauseablePlacement;
|
|
@@ -256,6 +254,7 @@ export class Style extends Evented<MapEventType> {
|
|
|
256
254
|
this.imageManager = new ImageManager();
|
|
257
255
|
this.imageManager.setEventedParent(this);
|
|
258
256
|
this.imageManager.setMissingImageResolver(map._missingStyleImageResolver);
|
|
257
|
+
this.patternAtlas = new PatternAtlas(this.imageManager);
|
|
259
258
|
const glyphLang = map._container?.lang || (typeof document !== 'undefined' && document.documentElement?.lang) || undefined;
|
|
260
259
|
this.glyphManager = new GlyphManager(map._requestManager, options.localIdeographFontFamily, glyphLang);
|
|
261
260
|
this.lineAtlas = new LineAtlas(256, 512);
|
|
@@ -293,12 +292,10 @@ export class Style extends Evented<MapEventType> {
|
|
|
293
292
|
}
|
|
294
293
|
|
|
295
294
|
private _setInitialValues() {
|
|
296
|
-
this._spritesImagesIds = {};
|
|
297
295
|
this._layers = {};
|
|
298
296
|
this._order = [];
|
|
299
297
|
this.tileManagers = {};
|
|
300
298
|
this.zoomHistory = new ZoomHistory();
|
|
301
|
-
this._availableImages = [];
|
|
302
299
|
this._imagesListDirty = false;
|
|
303
300
|
this._globalState = {};
|
|
304
301
|
this._serializedLayers = {};
|
|
@@ -529,73 +526,46 @@ export class Style extends Evented<MapEventType> {
|
|
|
529
526
|
}
|
|
530
527
|
}
|
|
531
528
|
|
|
532
|
-
_loadSprite(sprite: SpriteSpecification, isUpdate: boolean = false, completion: (err: Error) => void = undefined): void {
|
|
529
|
+
async _loadSprite(sprite: SpriteSpecification, isUpdate: boolean = false, completion: (err: Error) => void = undefined): Promise<void> {
|
|
533
530
|
this.imageManager.setLoaded(false);
|
|
534
531
|
|
|
535
532
|
const abortController = new AbortController();
|
|
536
533
|
this._spriteRequest = abortController;
|
|
537
534
|
let err: Error;
|
|
538
|
-
|
|
539
|
-
this.
|
|
540
|
-
if (images)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
this.imageManager.removeImage(id);
|
|
548
|
-
this._changedImages[id] = true;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
for (const id in images[spriteId]) {
|
|
552
|
-
// don't prefix images of the "default" sprite
|
|
553
|
-
const imageId = spriteId === 'default' ? id : `${spriteId}:${id}`;
|
|
554
|
-
// save all the sprite's images' ids to be able to delete them in `removeSprite`
|
|
555
|
-
this._spritesImagesIds[spriteId].push(imageId);
|
|
556
|
-
if (imageId in this.imageManager.images) {
|
|
557
|
-
this.imageManager.updateImage(imageId, images[spriteId][id], false);
|
|
558
|
-
} else {
|
|
559
|
-
this.imageManager.addImage(imageId, images[spriteId][id]);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (isUpdate) {
|
|
563
|
-
this._changedImages[imageId] = true;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
535
|
+
try {
|
|
536
|
+
const images = await loadSprite(sprite, this.map._requestManager, this.map.getPixelRatio(), abortController);
|
|
537
|
+
if (!images) return;
|
|
538
|
+
|
|
539
|
+
for (const spriteId in images) {
|
|
540
|
+
const {loaded, removed} = this.imageManager.setSpriteImages(spriteId, images[spriteId]);
|
|
541
|
+
this._markImagesChanged(removed);
|
|
542
|
+
if (isUpdate) {
|
|
543
|
+
this._markImagesChanged(loaded);
|
|
566
544
|
}
|
|
567
545
|
}
|
|
568
|
-
}
|
|
569
|
-
this._spriteRequest = null;
|
|
546
|
+
} catch (error) {
|
|
570
547
|
err = error;
|
|
571
548
|
if (!abortController.signal.aborted) { // ignore abort
|
|
572
549
|
this.fire(new ErrorEvent(err));
|
|
573
550
|
}
|
|
574
|
-
}
|
|
551
|
+
} finally {
|
|
552
|
+
this._spriteRequest = null;
|
|
575
553
|
this.imageManager.setLoaded(true);
|
|
576
|
-
this._availableImages = this.imageManager.listImages();
|
|
577
554
|
|
|
578
555
|
if (isUpdate) {
|
|
579
556
|
this._changed = true;
|
|
580
557
|
}
|
|
581
558
|
|
|
582
|
-
this.dispatcher.broadcast(MessageType.setImages, this.
|
|
559
|
+
this.dispatcher.broadcast(MessageType.setImages, this.imageManager.listImages());
|
|
583
560
|
this.fire(new MapStyleDataEvent('data'));
|
|
584
561
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
});
|
|
562
|
+
completion?.(err);
|
|
563
|
+
}
|
|
589
564
|
}
|
|
590
565
|
|
|
591
566
|
_unloadSprite(): void {
|
|
592
|
-
|
|
593
|
-
this.imageManager.removeImage(id);
|
|
594
|
-
this._changedImages[id] = true;
|
|
595
|
-
}
|
|
567
|
+
this._markImagesChanged(this.imageManager.removeAllSpriteImages());
|
|
596
568
|
|
|
597
|
-
this._spritesImagesIds = {};
|
|
598
|
-
this._availableImages = this.imageManager.listImages();
|
|
599
569
|
this._imagesListDirty = true;
|
|
600
570
|
this._changed = true;
|
|
601
571
|
this.fire(new MapStyleDataEvent('data'));
|
|
@@ -731,7 +701,7 @@ export class Style extends Evented<MapEventType> {
|
|
|
731
701
|
const changed = this._changed;
|
|
732
702
|
if (changed) {
|
|
733
703
|
if (this._imagesListDirty) {
|
|
734
|
-
this.dispatcher.broadcast(MessageType.setImages, this.
|
|
704
|
+
this.dispatcher.broadcast(MessageType.setImages, this.imageManager.listImages());
|
|
735
705
|
this._imagesListDirty = false;
|
|
736
706
|
}
|
|
737
707
|
|
|
@@ -779,10 +749,11 @@ export class Style extends Evented<MapEventType> {
|
|
|
779
749
|
|
|
780
750
|
// loop all layers and find layers that are not hidden at parameters.zoom
|
|
781
751
|
// and set used to true in tileManagers dictionary for the sources of these layers
|
|
752
|
+
const availableImages = this.imageManager.listImages();
|
|
782
753
|
for (const layerId of this._order) {
|
|
783
754
|
const layer = this._layers[layerId];
|
|
784
755
|
|
|
785
|
-
layer.recalculate(parameters,
|
|
756
|
+
layer.recalculate(parameters, availableImages);
|
|
786
757
|
if (!layer.isHidden(parameters.zoom) && layer.source) {
|
|
787
758
|
this.tileManagers[layer.source].used = true;
|
|
788
759
|
}
|
|
@@ -1005,8 +976,17 @@ export class Style extends Evented<MapEventType> {
|
|
|
1005
976
|
this._afterImageUpdated(id);
|
|
1006
977
|
}
|
|
1007
978
|
|
|
979
|
+
/**
|
|
980
|
+
* Queues the tiles that depend on these images to be reloaded on the next update, which is
|
|
981
|
+
* needed whenever an image appears, disappears or changes size.
|
|
982
|
+
*/
|
|
983
|
+
_markImagesChanged(ids: string[]): void {
|
|
984
|
+
for (const id of ids) {
|
|
985
|
+
this._changedImages[id] = true;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
1008
989
|
_afterImageUpdated(id: string): void {
|
|
1009
|
-
this._availableImages = this.imageManager.listImages();
|
|
1010
990
|
this._changedImages[id] = true;
|
|
1011
991
|
this._imagesListDirty = true;
|
|
1012
992
|
this._changed = true;
|
|
@@ -1616,7 +1596,7 @@ export class Style extends Evented<MapEventType> {
|
|
|
1616
1596
|
|
|
1617
1597
|
const sourceResults: QueryRenderedFeaturesResults[] = [];
|
|
1618
1598
|
|
|
1619
|
-
params.availableImages = this.
|
|
1599
|
+
params.availableImages = this.imageManager.listImages();
|
|
1620
1600
|
|
|
1621
1601
|
// LayerSpecification is serialized StyleLayer, and this casting is safe.
|
|
1622
1602
|
const serializedLayers = this._serializedAllLayers() as {[_: string]: StyleLayer};
|
|
@@ -2000,18 +1980,12 @@ export class Style extends Evented<MapEventType> {
|
|
|
2000
1980
|
return;
|
|
2001
1981
|
}
|
|
2002
1982
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
this.imageManager.removeImage(imageId);
|
|
2006
|
-
this._changedImages[imageId] = true;
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
1983
|
+
const changedImages = this.imageManager.removeSpriteImages(id);
|
|
1984
|
+
this._markImagesChanged(changedImages);
|
|
2009
1985
|
|
|
2010
1986
|
internalSpriteRepresentation.splice(internalSpriteRepresentation.findIndex(sprite => sprite.id === id), 1);
|
|
2011
1987
|
this.stylesheet.sprite = internalSpriteRepresentation.length > 0 ? internalSpriteRepresentation : undefined;
|
|
2012
1988
|
|
|
2013
|
-
delete this._spritesImagesIds[id];
|
|
2014
|
-
this._availableImages = this.imageManager.listImages();
|
|
2015
1989
|
this._imagesListDirty = true;
|
|
2016
1990
|
this._changed = true;
|
|
2017
1991
|
this.fire(new MapStyleDataEvent('data'));
|
|
@@ -2082,8 +2056,7 @@ export class Style extends Evented<MapEventType> {
|
|
|
2082
2056
|
if (this.imageManager) {
|
|
2083
2057
|
this.imageManager.setEventedParent(null);
|
|
2084
2058
|
this.imageManager.destroy();
|
|
2085
|
-
this.
|
|
2086
|
-
this._spritesImagesIds = {};
|
|
2059
|
+
this.patternAtlas.destroy();
|
|
2087
2060
|
}
|
|
2088
2061
|
|
|
2089
2062
|
// Destroy glyphManager
|
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
|
+
});
|
|
@@ -236,6 +236,37 @@ describe('CrossTileSymbolIndex.addLayer', () => {
|
|
|
236
236
|
expect(childInstances[0].crossTileID).toBe(1);
|
|
237
237
|
|
|
238
238
|
});
|
|
239
|
+
|
|
240
|
+
test('claims the lowest-index match among coincident symbols', () => {
|
|
241
|
+
const index = new CrossTileSymbolIndex();
|
|
242
|
+
|
|
243
|
+
const mainID = new OverscaledTileID(6, 0, 6, 8, 8);
|
|
244
|
+
const childID = new OverscaledTileID(7, 0, 7, 16, 16);
|
|
245
|
+
|
|
246
|
+
// more instances than KDBUSH_THRESHHOLD to exercise the KDBush-indexed path
|
|
247
|
+
const count = KDBUSH_THRESHHOLD + 2;
|
|
248
|
+
const mainInstances: any[] = [];
|
|
249
|
+
const childInstances: any[] = [];
|
|
250
|
+
for (let i = 0; i < count; i++) {
|
|
251
|
+
// all symbols are coincident and share the same key
|
|
252
|
+
mainInstances.push(makeSymbolInstance(1000, 1000, 'Dense'));
|
|
253
|
+
childInstances.push(makeSymbolInstance(2000, 2000, 'Dense'));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const mainTile = makeTile(mainID, mainInstances);
|
|
257
|
+
const childTile = makeTile(childID, childInstances);
|
|
258
|
+
|
|
259
|
+
index.addLayer(styleLayer, [mainTile], 0);
|
|
260
|
+
index.addLayer(styleLayer, [mainTile, childTile], 0);
|
|
261
|
+
|
|
262
|
+
// Each child symbol should claim the lowest-index unclaimed parent
|
|
263
|
+
// symbol, so parents are claimed in symbol-instance order. (A
|
|
264
|
+
// lexicographic ordering of the candidate indexes would instead claim
|
|
265
|
+
// parent 10 for child 2 once 0 and 1 are taken, because '10' < '2'.)
|
|
266
|
+
for (let i = 0; i < 12; i++) {
|
|
267
|
+
expect(childInstances[i].crossTileID).toBe(mainInstances[i].crossTileID);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
239
270
|
});
|
|
240
271
|
|
|
241
272
|
test('CrossTileSymbolIndex.pruneUnusedLayers', () => {
|