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
|
@@ -31,6 +31,12 @@ import type {Terrain} from '../render/terrain.ts';
|
|
|
31
31
|
|
|
32
32
|
const isMoving = (p: EventsInProgress) => p.zoom || p.drag || p.roll || p.pitch || p.rotate;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* A terrain gesture's anchor plane must stay below this fraction of the camera's
|
|
36
|
+
* altitude over the center-elevation plane, or the ray-plane solve degenerates.
|
|
37
|
+
*/
|
|
38
|
+
const TERRAIN_ANCHOR_MAX_CAMERA_ALTITUDE_FRACTION = 0.9;
|
|
39
|
+
|
|
34
40
|
class RenderFrameEvent extends Event {
|
|
35
41
|
type: 'renderFrame';
|
|
36
42
|
timeStamp: number;
|
|
@@ -162,6 +168,11 @@ export class HandlerManager {
|
|
|
162
168
|
_updatingCamera: boolean;
|
|
163
169
|
_changes: Array<[HandlerResult, EventsInProgress, {[handlerName: string]: Event}]>;
|
|
164
170
|
_terrainMovement: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Elevation in meters of the terrain point grabbed at gesture start; kept for the
|
|
173
|
+
* same lifetime as the elevation freeze. Null when that terrain was not available.
|
|
174
|
+
*/
|
|
175
|
+
_terrainGestureAnchorElevation: number | null = null;
|
|
165
176
|
_zoom: {handlerName: string};
|
|
166
177
|
_previousActiveHandlers: {[x: string]: Handler};
|
|
167
178
|
_listeners: Array<[Window | Document | HTMLElement, string, {
|
|
@@ -542,17 +553,10 @@ export class HandlerManager {
|
|
|
542
553
|
// stop any ongoing camera animations (easeTo, flyTo)
|
|
543
554
|
this._camera.stop(true);
|
|
544
555
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (pinchAround !== undefined) {
|
|
548
|
-
around = pinchAround;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
around ||= this._camera.transform.centerPoint;
|
|
556
|
+
const {panDelta, zoomDelta, bearingDelta, pitchDelta, rollDelta} = combinedResult;
|
|
552
557
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
558
|
+
let {around, aroundOnSurface} = this._resolveAround(combinedResult, terrain, tr);
|
|
559
|
+
const aroundElevation = terrain ? this._terrainGestureElevation(terrain, around, aroundOnSurface, tr, combinedEventsInProgress) : undefined;
|
|
556
560
|
|
|
557
561
|
const deltasForHelper: MapControlsDeltas = {
|
|
558
562
|
panDelta,
|
|
@@ -561,17 +565,14 @@ export class HandlerManager {
|
|
|
561
565
|
pitchDelta,
|
|
562
566
|
bearingDelta,
|
|
563
567
|
around,
|
|
568
|
+
aroundElevation,
|
|
564
569
|
};
|
|
565
570
|
|
|
566
571
|
// Pre-zoom location under the mouse cursor is required for accurate mercator panning and zooming
|
|
567
572
|
if (this._camera.cameraHelper.useGlobeControls && !tr.isPointOnMapSurface(around)) {
|
|
568
573
|
around = tr.centerPoint;
|
|
569
574
|
}
|
|
570
|
-
|
|
571
|
-
// center directly, instead of computing it from the screen point
|
|
572
|
-
const preZoomAroundLoc = around.distSqr(tr.centerPoint) < 1.0e-2 ?
|
|
573
|
-
tr.center :
|
|
574
|
-
tr.screenPointToLocation(panDelta ? around.sub(panDelta) : around);
|
|
575
|
+
const preZoomAroundLoc = this._computePreZoomAroundLoc(tr, around, panDelta, aroundElevation);
|
|
575
576
|
|
|
576
577
|
this._handleMapControls({
|
|
577
578
|
terrain,
|
|
@@ -590,6 +591,65 @@ export class HandlerManager {
|
|
|
590
591
|
|
|
591
592
|
}
|
|
592
593
|
|
|
594
|
+
/**
|
|
595
|
+
* The gesture's anchor point: the pinch midpoint when pinching, otherwise the
|
|
596
|
+
* pan's anchor, clamped to the center point when it does not lie on the map.
|
|
597
|
+
*/
|
|
598
|
+
_resolveAround(combinedResult: HandlerResult, terrain: Terrain | null, tr: ITransform): {around: Point; aroundOnSurface: boolean} {
|
|
599
|
+
let around = combinedResult.pinchAround !== undefined ? combinedResult.pinchAround : combinedResult.around;
|
|
600
|
+
around ||= this._camera.transform.centerPoint;
|
|
601
|
+
if (terrain && !tr.isPointOnMapSurface(around)) {
|
|
602
|
+
return {around: tr.centerPoint, aroundOnSurface: false};
|
|
603
|
+
}
|
|
604
|
+
return {around, aroundOnSurface: true};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* The elevation of the plane a terrain gesture is solved on: the elevation of the
|
|
609
|
+
* terrain point grabbed at gesture start, captured here on the gesture's first
|
|
610
|
+
* frame. Undefined means the gesture is solved at the center's elevation instead —
|
|
611
|
+
* when the grabbed terrain is not loaded, the anchor is the center point (a
|
|
612
|
+
* center-point anchor is skipped by the camera helper and would lose the pan), or
|
|
613
|
+
* the anchor plane is too close to the camera for a stable ray-plane solve.
|
|
614
|
+
*/
|
|
615
|
+
_terrainGestureElevation(terrain: Terrain, around: Point, aroundOnSurface: boolean, tr: ITransform, combinedEventsInProgress: EventsInProgress): number | undefined {
|
|
616
|
+
if (!aroundOnSurface) {
|
|
617
|
+
return undefined;
|
|
618
|
+
}
|
|
619
|
+
if (!this._terrainMovement && (combinedEventsInProgress.drag || combinedEventsInProgress.zoom)) {
|
|
620
|
+
const anchor = terrain.pointCoordinate(around);
|
|
621
|
+
this._terrainGestureAnchorElevation = anchor ? anchor.z : null;
|
|
622
|
+
}
|
|
623
|
+
if (this._terrainGestureAnchorElevation === null) {
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|
|
626
|
+
const elevation = this._terrainGestureAnchorElevation;
|
|
627
|
+
if (around.distSqr(tr.centerPoint) < 1.0e-2) {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
if (elevation - tr.elevation >= TERRAIN_ANCHOR_MAX_CAMERA_ALTITUDE_FRACTION * (tr.getCameraAltitude() - tr.elevation)) {
|
|
631
|
+
return undefined;
|
|
632
|
+
}
|
|
633
|
+
return elevation;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* The location that was under `around` before this frame's deltas — the point the
|
|
638
|
+
* camera helper re-anchors after zooming — solved at `aroundElevation` when a
|
|
639
|
+
* terrain gesture provides one. When rotating about the center point, the
|
|
640
|
+
* transform's center is used directly to avoid numerical issues near the horizon.
|
|
641
|
+
*/
|
|
642
|
+
_computePreZoomAroundLoc(tr: ITransform, around: Point, panDelta: Point | undefined, aroundElevation: number | undefined): LngLat {
|
|
643
|
+
if (around.distSqr(tr.centerPoint) < 1.0e-2) {
|
|
644
|
+
return tr.center;
|
|
645
|
+
}
|
|
646
|
+
const aroundPreviousPoint = panDelta ? around.sub(panDelta) : around;
|
|
647
|
+
if (aroundElevation !== undefined) {
|
|
648
|
+
return tr.screenPointToLocationAtElevation(aroundPreviousPoint, aroundElevation);
|
|
649
|
+
}
|
|
650
|
+
return tr.screenPointToLocation(aroundPreviousPoint);
|
|
651
|
+
}
|
|
652
|
+
|
|
593
653
|
_handleMapControls({
|
|
594
654
|
terrain,
|
|
595
655
|
tr,
|
|
@@ -623,7 +683,9 @@ export class HandlerManager {
|
|
|
623
683
|
return;
|
|
624
684
|
}
|
|
625
685
|
|
|
626
|
-
if (
|
|
686
|
+
if (deltasForHelper.aroundElevation === undefined &&
|
|
687
|
+
combinedEventsInProgress.drag && this._terrainMovement && panDelta) {
|
|
688
|
+
// no usable anchor: drag by pixel-delta on the center-elevation plane
|
|
627
689
|
tr.setCenter(tr.screenPointToLocation(tr.centerPoint.sub(panDelta)));
|
|
628
690
|
return;
|
|
629
691
|
}
|
|
@@ -685,6 +747,7 @@ export class HandlerManager {
|
|
|
685
747
|
if (finishedMoving && this._terrainMovement) {
|
|
686
748
|
this._camera.elevationFreeze = false;
|
|
687
749
|
this._terrainMovement = false;
|
|
750
|
+
this._terrainGestureAnchorElevation = null;
|
|
688
751
|
const tr = this._camera.getTransformForUpdate();
|
|
689
752
|
if (this._map.getCenterClampedToGround()) {
|
|
690
753
|
tr.recalculateZoomAndCenter(this._map.terrain);
|
package/src/ui/map.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {defaultLocale} from './default_locale.ts';
|
|
|
31
31
|
import {isAbortError} from '../util/abort_error.ts';
|
|
32
32
|
import {coveringTiles, type CoveringTilesOptions, createCalculateTileZoomFunction} from '../geo/projection/covering_tiles.ts';
|
|
33
33
|
import {CanonicalTileID, type OverscaledTileID} from '../tile/tile_id.ts';
|
|
34
|
+
import {isStyleImageWebGLData} from '../style/style_image.ts';
|
|
34
35
|
|
|
35
36
|
import type {PaddingOptions} from '../geo/edge_insets.ts';
|
|
36
37
|
import type {Source} from '../source/source.ts';
|
|
@@ -3208,9 +3209,11 @@ export class Map extends Evented<MapEventType> {
|
|
|
3208
3209
|
} else {
|
|
3209
3210
|
const {width, height, data} = image as ImageData;
|
|
3210
3211
|
const userImage = (image as any as StyleImageInterface);
|
|
3212
|
+
const isWebGLImage = isStyleImageWebGLData(userImage.data);
|
|
3211
3213
|
|
|
3212
3214
|
return {
|
|
3213
|
-
|
|
3215
|
+
// A WebGL image paints its own slot, so its pixels are only ever transparent padding.
|
|
3216
|
+
data: isWebGLImage ? new RGBAImage({width, height}) : new RGBAImage({width, height}, new Uint8Array(data)),
|
|
3214
3217
|
pixelRatio,
|
|
3215
3218
|
stretchX,
|
|
3216
3219
|
stretchY,
|
|
@@ -3219,6 +3222,7 @@ export class Map extends Evented<MapEventType> {
|
|
|
3219
3222
|
textFitHeight,
|
|
3220
3223
|
sdf,
|
|
3221
3224
|
version,
|
|
3225
|
+
isWebGLImage,
|
|
3222
3226
|
userImage
|
|
3223
3227
|
};
|
|
3224
3228
|
}
|
|
@@ -3267,8 +3271,13 @@ export class Map extends Evented<MapEventType> {
|
|
|
3267
3271
|
'The width and height of the updated image must be that same as the previous version of the image')));
|
|
3268
3272
|
}
|
|
3269
3273
|
|
|
3270
|
-
|
|
3271
|
-
existingImage.
|
|
3274
|
+
existingImage.isWebGLImage = isStyleImageWebGLData(data);
|
|
3275
|
+
if (existingImage.isWebGLImage) {
|
|
3276
|
+
existingImage.userImage = image as StyleImageInterface;
|
|
3277
|
+
} else {
|
|
3278
|
+
const copy = !(image instanceof HTMLImageElement || isImageBitmap(image));
|
|
3279
|
+
existingImage.data.replace(data as Uint8Array | Uint8ClampedArray, copy);
|
|
3280
|
+
}
|
|
3272
3281
|
|
|
3273
3282
|
this.style.updateImage(id, existingImage);
|
|
3274
3283
|
return this;
|
|
@@ -4146,6 +4155,11 @@ export class Map extends Evented<MapEventType> {
|
|
|
4146
4155
|
|
|
4147
4156
|
if (this._lostContextStyle.images && this.style) {
|
|
4148
4157
|
this.style.imageManager.images = this._lostContextStyle.images;
|
|
4158
|
+
// The atlas textures died with the old context, so images that render themselves with WebGL owe every atlas a fresh render.
|
|
4159
|
+
for (const id in this._lostContextStyle.images) {
|
|
4160
|
+
const image = this._lostContextStyle.images[id];
|
|
4161
|
+
if (image.isWebGLImage) this.style.imageManager.updateImage(id, image, false);
|
|
4162
|
+
}
|
|
4149
4163
|
}
|
|
4150
4164
|
|
|
4151
4165
|
this._lostContextStyle = {style: null, images: null};
|
|
@@ -36,8 +36,8 @@ describe('Map cross-window support', () => {
|
|
|
36
36
|
expect(iframeDocument).not.toBe(window.document);
|
|
37
37
|
expect(iframeWindow).not.toBe(window);
|
|
38
38
|
|
|
39
|
-
expect(container
|
|
40
|
-
expect(container
|
|
39
|
+
expect(container).not.toBeInstanceOf( HTMLElement);
|
|
40
|
+
expect(container).toBeInstanceOf(iframeDocument.defaultView.HTMLElement);
|
|
41
41
|
|
|
42
42
|
const map = new Map({
|
|
43
43
|
container,
|
|
@@ -3,9 +3,9 @@ import simulate from '../../../test/unit/lib/simulate_interaction.ts';
|
|
|
3
3
|
import {type StyleLayer} from '../../style/style_layer.ts';
|
|
4
4
|
import {createMap, beforeMapTest, createStyle, sleep, createTerrain} from '../../util/test/util.ts';
|
|
5
5
|
import {type MapGeoJSONFeature} from '../../util/vectortile_to_geojson.ts';
|
|
6
|
-
import {type MapLibreEvent} from '../events.ts';
|
|
6
|
+
import {type MapLibreEvent, MapSourceDataEvent} from '../events.ts';
|
|
7
7
|
import {Map} from '../map.ts';
|
|
8
|
-
import {
|
|
8
|
+
import {ErrorEvent} from '../../util/evented.ts';
|
|
9
9
|
import {type StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
10
10
|
|
|
11
11
|
type IsAny<T> = 0 extends T & 1 ? T : never;
|
|
@@ -980,7 +980,7 @@ describe('map events', () => {
|
|
|
980
980
|
test('fires sourcedataabort event on dataabort event', async () => {
|
|
981
981
|
const map = createMap();
|
|
982
982
|
const sourcePromise = map.once('sourcedataabort');
|
|
983
|
-
map.fire(new
|
|
983
|
+
map.fire(new MapSourceDataEvent('dataabort'));
|
|
984
984
|
await expect(sourcePromise).resolves.toBeDefined();
|
|
985
985
|
});
|
|
986
986
|
|
|
@@ -215,6 +215,35 @@ test('map getImage matches addImage, StyleImageInterface SDF', () => {
|
|
|
215
215
|
expect(gotImage.sdf).toBe(true);
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
+
test('map addImage packs a placeholder for a WebGL image, which brings its own pixels', () => {
|
|
219
|
+
const map = createMap();
|
|
220
|
+
const id = 'add-get-webgl-style-image';
|
|
221
|
+
const onAdd = vi.fn();
|
|
222
|
+
const inputImage: StyleImageInterface = {width: 3, height: 2, data: {renderWithWebGL: vi.fn()}, onAdd};
|
|
223
|
+
|
|
224
|
+
map.addImage(id, inputImage);
|
|
225
|
+
|
|
226
|
+
const gotImage = map.getImage(id);
|
|
227
|
+
expect(gotImage.data.width).toBe(3);
|
|
228
|
+
expect(gotImage.data.height).toBe(2);
|
|
229
|
+
expect([...gotImage.data.data]).toEqual(new Array(3 * 2 * 4).fill(0));
|
|
230
|
+
expect(gotImage.isWebGLImage).toBe(true);
|
|
231
|
+
expect(gotImage.userImage).toBe(inputImage);
|
|
232
|
+
expect(onAdd).toHaveBeenCalledWith(map, id);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('map updateImage swaps a WebGL image\'s renderWithWebGL callback instead of copying pixels', () => {
|
|
236
|
+
const map = createMap();
|
|
237
|
+
const replacement: StyleImageInterface = {width: 1, height: 1, data: {renderWithWebGL: vi.fn()}};
|
|
238
|
+
|
|
239
|
+
map.addImage('webgl', {width: 1, height: 1, data: {renderWithWebGL: vi.fn()}});
|
|
240
|
+
const version = map.getImage('webgl').version;
|
|
241
|
+
map.updateImage('webgl', replacement);
|
|
242
|
+
|
|
243
|
+
expect(map.getImage('webgl').userImage).toBe(replacement);
|
|
244
|
+
expect(map.getImage('webgl').version).toBe(version + 1);
|
|
245
|
+
});
|
|
246
|
+
|
|
218
247
|
test('map does not fire `styleimagemissing` for empty icon values', async () => {
|
|
219
248
|
const map = createMap();
|
|
220
249
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {describe, beforeEach, afterEach, test, expect, vi} from 'vitest';
|
|
2
2
|
import {Map, type MapOptions} from '../map.ts';
|
|
3
3
|
import {createMap, beforeMapTest, createStyle, createStyleSource, sleep} from '../../util/test/util.ts';
|
|
4
|
-
import {
|
|
4
|
+
import {ErrorEvent} from '../../util/evented.ts';
|
|
5
|
+
import {MapSourceDataEvent, MapStyleDataEvent} from '../events.ts';
|
|
5
6
|
import {fixedLngLat, fixedNum} from '../../../test/unit/lib/fixed.ts';
|
|
6
7
|
import {extend} from '../../util/util.ts';
|
|
7
8
|
import {fakeServer, type FakeServer} from 'nise';
|
|
@@ -42,9 +43,9 @@ describe('setStyle', () => {
|
|
|
42
43
|
map.on('data', recordEvent);
|
|
43
44
|
map.on('dataloading', recordEvent);
|
|
44
45
|
|
|
45
|
-
map.style.fire(new
|
|
46
|
-
map.style.fire(new
|
|
47
|
-
map.style.fire(new
|
|
46
|
+
map.style.fire(new ErrorEvent(new Error('test')));
|
|
47
|
+
map.style.fire(new MapSourceDataEvent('data'));
|
|
48
|
+
map.style.fire(new MapSourceDataEvent('dataloading'));
|
|
48
49
|
|
|
49
50
|
expect(events).toEqual([
|
|
50
51
|
'error',
|
|
@@ -65,10 +66,10 @@ describe('setStyle', () => {
|
|
|
65
66
|
map.on('sourcedata', recordEvent);
|
|
66
67
|
map.on('sourcedataloading', recordEvent);
|
|
67
68
|
|
|
68
|
-
map.style.fire(new
|
|
69
|
-
map.style.fire(new
|
|
70
|
-
map.style.fire(new
|
|
71
|
-
map.style.fire(new
|
|
69
|
+
map.style.fire(new MapStyleDataEvent('data'));
|
|
70
|
+
map.style.fire(new MapStyleDataEvent('dataloading'));
|
|
71
|
+
map.style.fire(new MapSourceDataEvent('data'));
|
|
72
|
+
map.style.fire(new MapSourceDataEvent('dataloading'));
|
|
72
73
|
|
|
73
74
|
expect(events).toEqual([
|
|
74
75
|
'styledata',
|
|
@@ -93,6 +93,35 @@ test('does not fire "webglcontextrestored" after remove has been called', async
|
|
|
93
93
|
expect(spy).not.toHaveBeenCalled();
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
test('a WebGL style image is told to release its GPU resources on context loss, and survives it', async () => {
|
|
97
|
+
const map = createMap();
|
|
98
|
+
const canvas = map.getCanvas();
|
|
99
|
+
await map.once('load');
|
|
100
|
+
|
|
101
|
+
const userImage = {
|
|
102
|
+
width: 2, height: 2,
|
|
103
|
+
data: {renderWithWebGL: vi.fn()},
|
|
104
|
+
onRemove: vi.fn()
|
|
105
|
+
};
|
|
106
|
+
map.addImage('gpu-image', userImage);
|
|
107
|
+
|
|
108
|
+
const contextLostPromise = map.once('webglcontextlost');
|
|
109
|
+
canvas.dispatchEvent(new window.Event('webglcontextlost'));
|
|
110
|
+
await contextLostPromise;
|
|
111
|
+
|
|
112
|
+
expect(userImage.onRemove).toHaveBeenCalled();
|
|
113
|
+
|
|
114
|
+
const contextRestoredPromise = map.once('webglcontextrestored');
|
|
115
|
+
canvas.dispatchEvent(new window.Event('webglcontextrestored'));
|
|
116
|
+
await contextRestoredPromise;
|
|
117
|
+
|
|
118
|
+
expect(map.hasImage('gpu-image')).toBe(true);
|
|
119
|
+
expect(map.getImage('gpu-image').userImage).toBe(userImage);
|
|
120
|
+
expect(map.style.imageManager.updatedImages['gpu-image']).toBe(true);
|
|
121
|
+
|
|
122
|
+
map.remove();
|
|
123
|
+
});
|
|
124
|
+
|
|
96
125
|
test('WebGL2 context creation error fires ErrorEvent with structured GPUInitializationError', () => {
|
|
97
126
|
HTMLCanvasElement.prototype.getContext = function (type: string) {
|
|
98
127
|
if (type === 'webgl2') {
|
package/src/ui/marker.test.ts
CHANGED
|
@@ -96,20 +96,20 @@ describe('marker', () => {
|
|
|
96
96
|
.addTo(map);
|
|
97
97
|
|
|
98
98
|
const markerElement = marker.getElement();
|
|
99
|
-
expect(markerElement.classList.
|
|
100
|
-
expect(markerElement.classList.
|
|
99
|
+
expect(markerElement.classList).toContain('some');
|
|
100
|
+
expect(markerElement.classList).toContain('classes');
|
|
101
101
|
|
|
102
102
|
marker.addClassName('addedClass');
|
|
103
|
-
expect(markerElement.classList.
|
|
103
|
+
expect(markerElement.classList).toContain('addedClass');
|
|
104
104
|
|
|
105
105
|
marker.removeClassName('addedClass');
|
|
106
|
-
expect(
|
|
106
|
+
expect(markerElement.classList).not.toContain('addedClass');
|
|
107
107
|
|
|
108
108
|
marker.toggleClassName('toggle');
|
|
109
|
-
expect(markerElement.classList.
|
|
109
|
+
expect(markerElement.classList).toContain('toggle');
|
|
110
110
|
|
|
111
111
|
marker.toggleClassName('toggle');
|
|
112
|
-
expect(
|
|
112
|
+
expect(markerElement.classList).not.toContain('toggle');
|
|
113
113
|
|
|
114
114
|
expect(() => marker.addClassName('should throw exception')).toThrow(window.DOMException);
|
|
115
115
|
expect(() => marker.removeClassName('should throw exception')).toThrow(window.DOMException);
|
|
@@ -123,19 +123,19 @@ describe('marker', () => {
|
|
|
123
123
|
test('Marker provides LngLat accessors', () => {
|
|
124
124
|
expect(new Marker().getLngLat()).toBeUndefined();
|
|
125
125
|
|
|
126
|
-
expect(new Marker().setLngLat([1, 2]).getLngLat()
|
|
126
|
+
expect(new Marker().setLngLat([1, 2]).getLngLat()).toBeInstanceOf(LngLat);
|
|
127
127
|
expect(new Marker().setLngLat([1, 2]).getLngLat()).toEqual(new LngLat(1, 2));
|
|
128
128
|
|
|
129
|
-
expect(new Marker().setLngLat(new LngLat(1, 2)).getLngLat()
|
|
129
|
+
expect(new Marker().setLngLat(new LngLat(1, 2)).getLngLat()).toBeInstanceOf(LngLat);
|
|
130
130
|
expect(new Marker().setLngLat(new LngLat(1, 2)).getLngLat()).toEqual(new LngLat(1, 2));
|
|
131
131
|
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
test('Marker provides offset accessors', () => {
|
|
135
|
-
expect(new Marker().setOffset([1, 2]).getOffset()
|
|
135
|
+
expect(new Marker().setOffset([1, 2]).getOffset()).toBeInstanceOf(Point);
|
|
136
136
|
expect(new Marker().setOffset([1, 2]).getOffset()).toEqual(new Point(1, 2));
|
|
137
137
|
|
|
138
|
-
expect(new Marker().setOffset(new Point(1, 2)).getOffset()
|
|
138
|
+
expect(new Marker().setOffset(new Point(1, 2)).getOffset()).toBeInstanceOf(Point);
|
|
139
139
|
expect(new Marker().setOffset(new Point(1, 2)).getOffset()).toEqual(new Point(1, 2));
|
|
140
140
|
|
|
141
141
|
});
|
|
@@ -343,7 +343,7 @@ describe('marker', () => {
|
|
|
343
343
|
.setLngLat([0, 0])
|
|
344
344
|
.addTo(map);
|
|
345
345
|
|
|
346
|
-
expect(marker.getElement().classList.
|
|
346
|
+
expect(marker.getElement().classList).toContain('maplibregl-marker-anchor-center');
|
|
347
347
|
expect(marker.getElement().style.transform).toMatch(/translate\(-50%,-50%\)/);
|
|
348
348
|
|
|
349
349
|
map.remove();
|
|
@@ -355,7 +355,7 @@ describe('marker', () => {
|
|
|
355
355
|
.setLngLat([0, 0])
|
|
356
356
|
.addTo(map);
|
|
357
357
|
|
|
358
|
-
expect(marker.getElement().classList.
|
|
358
|
+
expect(marker.getElement().classList).toContain('maplibregl-marker-anchor-top');
|
|
359
359
|
expect(marker.getElement().style.transform).toMatch(/translate\(-50%,0\)/);
|
|
360
360
|
|
|
361
361
|
map.remove();
|
|
@@ -407,51 +407,35 @@ describe('marker', () => {
|
|
|
407
407
|
Object.defineProperty(marker.getPopup()._container, 'offsetHeight', {value: 100});
|
|
408
408
|
|
|
409
409
|
// marker should default to above since it has enough space
|
|
410
|
-
expect(
|
|
411
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-bottom')
|
|
412
|
-
).toBeTruthy();
|
|
410
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-bottom');
|
|
413
411
|
|
|
414
412
|
// move marker to the top forcing the popup to below
|
|
415
413
|
marker.setLngLat(map.unproject([mapHeight / 2, markerTop]));
|
|
416
|
-
expect(
|
|
417
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-top')
|
|
418
|
-
).toBeTruthy();
|
|
414
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-top');
|
|
419
415
|
|
|
420
416
|
// move marker to the right forcing the popup to the left
|
|
421
417
|
marker.setLngLat(map.unproject([mapHeight - markerRight, mapHeight / 2]));
|
|
422
|
-
expect(
|
|
423
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-right')
|
|
424
|
-
).toBeTruthy();
|
|
418
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-right');
|
|
425
419
|
|
|
426
420
|
// move marker to the left forcing the popup to the right
|
|
427
421
|
marker.setLngLat(map.unproject([markerRight, mapHeight / 2]));
|
|
428
|
-
expect(
|
|
429
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-left')
|
|
430
|
-
).toBeTruthy();
|
|
422
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-left');
|
|
431
423
|
|
|
432
424
|
// move marker to the top left forcing the popup to the bottom right
|
|
433
425
|
marker.setLngLat(map.unproject([markerRight, markerTop]));
|
|
434
|
-
expect(
|
|
435
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-top-left')
|
|
436
|
-
).toBeTruthy();
|
|
426
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-top-left');
|
|
437
427
|
|
|
438
428
|
// move marker to the top right forcing the popup to the bottom left
|
|
439
429
|
marker.setLngLat(map.unproject([mapHeight - markerRight, markerTop]));
|
|
440
|
-
expect(
|
|
441
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-top-right')
|
|
442
|
-
).toBeTruthy();
|
|
430
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-top-right');
|
|
443
431
|
|
|
444
432
|
// move marker to the bottom left forcing the popup to the top right
|
|
445
433
|
marker.setLngLat(map.unproject([markerRight, mapHeight]));
|
|
446
|
-
expect(
|
|
447
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-bottom-left')
|
|
448
|
-
).toBeTruthy();
|
|
434
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-bottom-left');
|
|
449
435
|
|
|
450
436
|
// move marker to the bottom right forcing the popup to the top left
|
|
451
437
|
marker.setLngLat(map.unproject([mapHeight - markerRight, mapHeight]));
|
|
452
|
-
expect(
|
|
453
|
-
marker.getPopup()._container.classList.contains('maplibregl-popup-anchor-bottom-right')
|
|
454
|
-
).toBeTruthy();
|
|
438
|
+
expect(marker.getPopup()._container.classList).toContain('maplibregl-popup-anchor-bottom-right');
|
|
455
439
|
|
|
456
440
|
map.remove();
|
|
457
441
|
});
|
|
@@ -1352,7 +1336,7 @@ describe('marker', () => {
|
|
|
1352
1336
|
map.fire('terrain');
|
|
1353
1337
|
await sleep(100);
|
|
1354
1338
|
|
|
1355
|
-
expect(marker.getElement().classList.
|
|
1339
|
+
expect(marker.getElement().classList).toContain('maplibregl-marker-covered');
|
|
1356
1340
|
map.remove();
|
|
1357
1341
|
});
|
|
1358
1342
|
|
|
@@ -1368,13 +1352,13 @@ describe('marker', () => {
|
|
|
1368
1352
|
map.fire('terrain');
|
|
1369
1353
|
await sleep(100);
|
|
1370
1354
|
|
|
1371
|
-
expect(marker.getElement().classList.
|
|
1355
|
+
expect(marker.getElement().classList).toContain('maplibregl-marker-covered');
|
|
1372
1356
|
|
|
1373
1357
|
map.terrain.depthAtPoint = () => .95; // Terrain no longer blocking marker
|
|
1374
1358
|
map.fire('moveend');
|
|
1375
1359
|
await sleep(100);
|
|
1376
1360
|
|
|
1377
|
-
expect(marker.getElement().classList.
|
|
1361
|
+
expect(marker.getElement().classList).not.toContain('maplibregl-marker-covered');
|
|
1378
1362
|
map.remove();
|
|
1379
1363
|
});
|
|
1380
1364
|
|
|
@@ -1389,7 +1373,7 @@ describe('marker', () => {
|
|
|
1389
1373
|
map.setProjection({type: 'globe'});
|
|
1390
1374
|
await sleep(100);
|
|
1391
1375
|
|
|
1392
|
-
expect(marker.getElement().classList.
|
|
1376
|
+
expect(marker.getElement().classList).toContain('maplibregl-marker-covered');
|
|
1393
1377
|
map.remove();
|
|
1394
1378
|
});
|
|
1395
1379
|
|
|
@@ -1404,12 +1388,12 @@ describe('marker', () => {
|
|
|
1404
1388
|
map.setProjection({type: 'globe'});
|
|
1405
1389
|
await sleep(100);
|
|
1406
1390
|
|
|
1407
|
-
expect(marker.getElement().classList.
|
|
1391
|
+
expect(marker.getElement().classList).toContain('maplibregl-marker-covered');
|
|
1408
1392
|
|
|
1409
1393
|
marker.setLngLat([0, 0]);
|
|
1410
1394
|
await sleep(100);
|
|
1411
1395
|
|
|
1412
|
-
expect(marker.getElement().classList.
|
|
1396
|
+
expect(marker.getElement().classList).not.toContain('maplibregl-marker-covered');
|
|
1413
1397
|
map.remove();
|
|
1414
1398
|
});
|
|
1415
1399
|
});
|
package/src/ui/popup.test.ts
CHANGED
|
@@ -32,7 +32,7 @@ describe('popup', () => {
|
|
|
32
32
|
.addTo(map);
|
|
33
33
|
|
|
34
34
|
expect(popup.isOpen()).toBeTruthy();
|
|
35
|
-
expect(popup.getElement().classList.
|
|
35
|
+
expect(popup.getElement().classList).toContain('maplibregl-popup');
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test('Popup.addTo adds a .maplibregl-popup element', () => {
|
|
@@ -258,10 +258,10 @@ describe('popup', () => {
|
|
|
258
258
|
test('Popup provides LngLat accessors', () => {
|
|
259
259
|
expect(new Popup().getLngLat()).toBeUndefined();
|
|
260
260
|
|
|
261
|
-
expect(new Popup().setLngLat([1, 2]).getLngLat()
|
|
261
|
+
expect(new Popup().setLngLat([1, 2]).getLngLat()).toBeInstanceOf(LngLat);
|
|
262
262
|
expect(new Popup().setLngLat([1, 2]).getLngLat()).toEqual(new LngLat(1, 2));
|
|
263
263
|
|
|
264
|
-
expect(new Popup().setLngLat(new LngLat(1, 2)).getLngLat()
|
|
264
|
+
expect(new Popup().setLngLat(new LngLat(1, 2)).getLngLat()).toBeInstanceOf(LngLat);
|
|
265
265
|
expect(new Popup().setLngLat(new LngLat(1, 2)).getLngLat()).toEqual(new LngLat(1, 2));
|
|
266
266
|
|
|
267
267
|
});
|
|
@@ -382,7 +382,7 @@ describe('popup', () => {
|
|
|
382
382
|
.setText('Test')
|
|
383
383
|
.addTo(map);
|
|
384
384
|
|
|
385
|
-
expect(popup.getElement().classList.
|
|
385
|
+
expect(popup.getElement().classList).toContain('maplibregl-popup-anchor-top-left');
|
|
386
386
|
});
|
|
387
387
|
|
|
388
388
|
const cases = [
|
|
@@ -413,7 +413,7 @@ describe('popup', () => {
|
|
|
413
413
|
vi.spyOn(map, 'project').mockReturnValue(point);
|
|
414
414
|
popup.setLngLat([0, 0]);
|
|
415
415
|
|
|
416
|
-
expect(popup.getElement().classList.
|
|
416
|
+
expect(popup.getElement().classList).toContain(`maplibregl-popup-anchor-${anchor}`);
|
|
417
417
|
});
|
|
418
418
|
|
|
419
419
|
const transformCases = cases.map(([anchor, _point, transform]) => [anchor, transform] as const);
|
|
@@ -449,7 +449,7 @@ describe('popup', () => {
|
|
|
449
449
|
vi.spyOn(map, 'project').mockReturnValue(point);
|
|
450
450
|
popup.setLngLat([0, 0]);
|
|
451
451
|
|
|
452
|
-
expect(popup.getElement().classList.
|
|
452
|
+
expect(popup.getElement().classList).toContain('maplibregl-popup-anchor-top');
|
|
453
453
|
});
|
|
454
454
|
|
|
455
455
|
test('Popup is offset via a PointLike offset option', () => {
|
|
@@ -563,22 +563,22 @@ describe('popup', () => {
|
|
|
563
563
|
.addTo(map);
|
|
564
564
|
|
|
565
565
|
const popupContainer = popup.getElement();
|
|
566
|
-
expect(popupContainer.classList.
|
|
567
|
-
expect(popupContainer.classList.
|
|
566
|
+
expect(popupContainer.classList).toContain('some');
|
|
567
|
+
expect(popupContainer.classList).toContain('classes');
|
|
568
568
|
|
|
569
569
|
const addClassNameMethodPopupInstance = popup.addClassName('addedClass');
|
|
570
|
-
expect(popupContainer.classList.
|
|
570
|
+
expect(popupContainer.classList).toContain('addedClass');
|
|
571
571
|
expect(addClassNameMethodPopupInstance).toBeInstanceOf(Popup);
|
|
572
572
|
|
|
573
573
|
const removeClassNameMethodPopupInstance = popup.removeClassName('addedClass');
|
|
574
|
-
expect(
|
|
574
|
+
expect(popupContainer.classList).not.toContain('addedClass');
|
|
575
575
|
expect(removeClassNameMethodPopupInstance).toBeInstanceOf(Popup);
|
|
576
576
|
|
|
577
577
|
popup.toggleClassName('toggle');
|
|
578
|
-
expect(popupContainer.classList.
|
|
578
|
+
expect(popupContainer.classList).toContain('toggle');
|
|
579
579
|
|
|
580
580
|
popup.toggleClassName('toggle');
|
|
581
|
-
expect(
|
|
581
|
+
expect(popupContainer.classList).not.toContain('toggle');
|
|
582
582
|
|
|
583
583
|
expect(() => popup.addClassName('should throw exception')).toThrow(window.DOMException);
|
|
584
584
|
expect(() => popup.removeClassName('should throw exception')).toThrow(window.DOMException);
|
|
@@ -1063,8 +1063,8 @@ describe('popup', () => {
|
|
|
1063
1063
|
.trackPointer()
|
|
1064
1064
|
.addTo(map);
|
|
1065
1065
|
|
|
1066
|
-
expect(popup.getElement().classList.
|
|
1067
|
-
expect(map._canvasContainer.classList.
|
|
1066
|
+
expect(popup.getElement().classList).toContain('maplibregl-popup-track-pointer');
|
|
1067
|
+
expect(map._canvasContainer.classList).toContain('maplibregl-track-pointer');
|
|
1068
1068
|
|
|
1069
1069
|
popup.remove();
|
|
1070
1070
|
});
|