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
|
@@ -117,44 +117,74 @@ class TileLayerIndex {
|
|
|
117
117
|
const scaledSymbolCoord = this.getScaledCoordinates(symbolInstance, newTileID);
|
|
118
118
|
|
|
119
119
|
if (entry.index) {
|
|
120
|
-
|
|
121
|
-
// grid unit. (with a 4px grid, this covers a 12px by 12px area)
|
|
122
|
-
const indexes = entry.index.range(
|
|
123
|
-
scaledSymbolCoord.x - tolerance,
|
|
124
|
-
scaledSymbolCoord.y - tolerance,
|
|
125
|
-
scaledSymbolCoord.x + tolerance,
|
|
126
|
-
scaledSymbolCoord.y + tolerance).sort();
|
|
127
|
-
|
|
128
|
-
for (const i of indexes) {
|
|
129
|
-
const crossTileID = entry.crossTileIDs[i];
|
|
130
|
-
|
|
131
|
-
if (!zoomCrossTileIDs[crossTileID]) {
|
|
132
|
-
// Once we've marked ourselves duplicate against this parent symbol,
|
|
133
|
-
// don't let any other symbols at the same zoom level duplicate against
|
|
134
|
-
// the same parent (see issue #5993)
|
|
135
|
-
zoomCrossTileIDs[crossTileID] = true;
|
|
136
|
-
symbolInstance.crossTileID = crossTileID;
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
120
|
+
this.findMatchesForIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs);
|
|
140
121
|
} else if (entry.positions) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
122
|
+
this.findMatchesForNonIndexedEntry(entry, symbolInstance, scaledSymbolCoord, tolerance, zoomCrossTileIDs);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Matches a symbol instance against an entry backed by a KDBush spatial index
|
|
129
|
+
* (used for keys with more than {@link KDBUSH_THRESHHOLD} symbols).
|
|
130
|
+
*
|
|
131
|
+
* Matches any symbol with the same key whose coordinates are within one grid
|
|
132
|
+
* unit (with a 4px grid, this covers a 12px by 12px area). The lowest-index
|
|
133
|
+
* unclaimed candidate is claimed in a single pass rather than sorting the
|
|
134
|
+
* whole result set on every query: `range()` can return many candidates where
|
|
135
|
+
* symbols are coincident (e.g. stacked point markers), so a per-query sort
|
|
136
|
+
* would dominate placement cost on dense layers.
|
|
137
|
+
*
|
|
138
|
+
* Once a symbol is marked duplicate against a parent symbol, no other symbol
|
|
139
|
+
* at the same zoom level may duplicate against the same parent (see issue #5993).
|
|
140
|
+
*/
|
|
141
|
+
private findMatchesForIndexedEntry(entry: SymbolsByKeyEntry, symbolInstance: SymbolInstance, scaledSymbolCoord: {x: number; y: number}, tolerance: number, zoomCrossTileIDs: {
|
|
142
|
+
[crossTileID: number]: boolean;
|
|
143
|
+
}): void {
|
|
144
|
+
const indexes = entry.index.range(
|
|
145
|
+
scaledSymbolCoord.x - tolerance,
|
|
146
|
+
scaledSymbolCoord.y - tolerance,
|
|
147
|
+
scaledSymbolCoord.x + tolerance,
|
|
148
|
+
scaledSymbolCoord.y + tolerance);
|
|
149
|
+
|
|
150
|
+
let bestIndex = Infinity;
|
|
151
|
+
for (const candidate of indexes) {
|
|
152
|
+
if (candidate < bestIndex && !zoomCrossTileIDs[entry.crossTileIDs[candidate]]) {
|
|
153
|
+
bestIndex = candidate;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (bestIndex !== Infinity) {
|
|
157
|
+
const crossTileID = entry.crossTileIDs[bestIndex];
|
|
158
|
+
zoomCrossTileIDs[crossTileID] = true;
|
|
159
|
+
symbolInstance.crossTileID = crossTileID;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Matches a symbol instance against an entry that stores its positions as a
|
|
165
|
+
* plain array (used for keys with at most {@link KDBUSH_THRESHHOLD} symbols).
|
|
166
|
+
*
|
|
167
|
+
* Matches any symbol with the same key whose coordinates are within one grid
|
|
168
|
+
* unit (with a 4px grid, this covers a 12px by 12px area). Positions are
|
|
169
|
+
* stored in symbol-instance order, so the first unclaimed match is also the
|
|
170
|
+
* lowest-index one.
|
|
171
|
+
*
|
|
172
|
+
* Once a symbol is marked duplicate against a parent symbol, no other symbol
|
|
173
|
+
* at the same zoom level may duplicate against the same parent (see issue #5993).
|
|
174
|
+
*/
|
|
175
|
+
private findMatchesForNonIndexedEntry(entry: SymbolsByKeyEntry, symbolInstance: SymbolInstance, scaledSymbolCoord: {x: number; y: number}, tolerance: number, zoomCrossTileIDs: {
|
|
176
|
+
[crossTileID: number]: boolean;
|
|
177
|
+
}): void {
|
|
178
|
+
for (let i = 0; i < entry.positions.length; i++) {
|
|
179
|
+
const thisTileSymbol = entry.positions[i];
|
|
180
|
+
const crossTileID = entry.crossTileIDs[i];
|
|
181
|
+
|
|
182
|
+
if (Math.abs(thisTileSymbol.x - scaledSymbolCoord.x) <= tolerance &&
|
|
183
|
+
Math.abs(thisTileSymbol.y - scaledSymbolCoord.y) <= tolerance &&
|
|
184
|
+
!zoomCrossTileIDs[crossTileID]) {
|
|
185
|
+
zoomCrossTileIDs[crossTileID] = true;
|
|
186
|
+
symbolInstance.crossTileID = crossTileID;
|
|
187
|
+
break;
|
|
158
188
|
}
|
|
159
189
|
}
|
|
160
190
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {bench} from 'vitest';
|
|
2
|
+
import {performSymbolLayout} from './symbol_layout.ts';
|
|
3
|
+
import {SymbolBucket} from '../data/bucket/symbol_bucket.ts';
|
|
4
|
+
import {SubdivisionGranularitySetting} from '../render/subdivision_granularity_settings.ts';
|
|
5
|
+
import {fixtureTiles, parseTile} from '../../test/bench/lib/parse_tile.ts';
|
|
6
|
+
|
|
7
|
+
import type {WorkerTileWithData} from '../source/worker_source.ts';
|
|
8
|
+
|
|
9
|
+
const parsedTiles = await Promise.all(fixtureTiles.map(async (tile): Promise<WorkerTileWithData> => {
|
|
10
|
+
return await parseTile(tile, true) as WorkerTileWithData;
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
bench('performSymbolLayout', () => {
|
|
14
|
+
for (const tileResult of parsedTiles) {
|
|
15
|
+
for (const bucket of tileResult.buckets) {
|
|
16
|
+
if (bucket instanceof SymbolBucket) {
|
|
17
|
+
performSymbolLayout({
|
|
18
|
+
bucket,
|
|
19
|
+
glyphMap: tileResult.glyphMap,
|
|
20
|
+
glyphPositions: tileResult.glyphPositions,
|
|
21
|
+
imageMap: tileResult.iconMap,
|
|
22
|
+
imagePositions: tileResult.imageAtlas.iconPositions,
|
|
23
|
+
showCollisionBoxes: false,
|
|
24
|
+
canonical: tileResult.featureIndex.tileID.canonical,
|
|
25
|
+
subdivisionGranularity: SubdivisionGranularitySetting.noSubdivision
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
package/src/tile/tile_id.test.ts
CHANGED
|
@@ -63,7 +63,7 @@ describe('CanonicalTileID', () => {
|
|
|
63
63
|
|
|
64
64
|
describe('OverscaledTileID', () => {
|
|
65
65
|
test('constructor', () => {
|
|
66
|
-
expect(new OverscaledTileID(0, 0, 0, 0, 0)
|
|
66
|
+
expect(new OverscaledTileID(0, 0, 0, 0, 0)).toBeInstanceOf(OverscaledTileID);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
test('constructor - deeper canonicalZ than overscaledZ disallowed', () => {
|
|
@@ -11,7 +11,7 @@ import {extend} from '../util/util.ts';
|
|
|
11
11
|
import {type Dispatcher} from '../util/dispatcher.ts';
|
|
12
12
|
import {TileBounds} from './tile_bounds.ts';
|
|
13
13
|
import {beforeMapTest, createMap as globalCreateMap, sleep, waitForEvent} from '../util/test/util.ts';
|
|
14
|
-
import {now} from '../util/time_control.ts';
|
|
14
|
+
import {now, restoreNow, setNow} from '../util/time_control.ts';
|
|
15
15
|
|
|
16
16
|
import {type Map} from '../ui/map.ts';
|
|
17
17
|
import {type TileCache} from './tile_cache.ts';
|
|
@@ -118,6 +118,7 @@ beforeEach(() => {
|
|
|
118
118
|
|
|
119
119
|
afterEach(() => {
|
|
120
120
|
vi.clearAllMocks();
|
|
121
|
+
restoreNow();
|
|
121
122
|
});
|
|
122
123
|
|
|
123
124
|
describe('TileManager.addTile', () => {
|
|
@@ -627,6 +628,73 @@ describe('TileManager / Source lifecycle', () => {
|
|
|
627
628
|
|
|
628
629
|
});
|
|
629
630
|
|
|
631
|
+
test('keeps the self fade timer when reloading a tile that has data', async () => {
|
|
632
|
+
const transform = new MercatorTransform();
|
|
633
|
+
transform.resize(511, 511);
|
|
634
|
+
transform.setZoom(0);
|
|
635
|
+
const tileID = new OverscaledTileID(0, 0, 0, 0, 0);
|
|
636
|
+
|
|
637
|
+
const tileManager = createTileManager();
|
|
638
|
+
tileManager.setRasterFadeDuration(300);
|
|
639
|
+
tileManager._source.loadTile = async (tile) => {
|
|
640
|
+
tile.state = 'loaded';
|
|
641
|
+
};
|
|
642
|
+
const metadataPromise = waitForEvent(tileManager, 'data', e => e.sourceDataType === 'metadata');
|
|
643
|
+
tileManager.onAdd(undefined);
|
|
644
|
+
await metadataPromise;
|
|
645
|
+
|
|
646
|
+
setNow(1000);
|
|
647
|
+
const loadPromise = waitForEvent(tileManager, 'data', e => e.tile?.tileID.key === tileID.key);
|
|
648
|
+
tileManager.update(transform);
|
|
649
|
+
await loadPromise;
|
|
650
|
+
const tile = tileManager.getTile(tileID);
|
|
651
|
+
tile.setSelfFadeLogic(now() + 300);
|
|
652
|
+
setNow(1010);
|
|
653
|
+
const reloadPromise = waitForEvent(tileManager, 'data', e => e.tile?.tileID.key === tileID.key);
|
|
654
|
+
|
|
655
|
+
tileManager.getSource().fire(new Event('data', {dataType: 'source', sourceDataType: 'content'}));
|
|
656
|
+
|
|
657
|
+
await reloadPromise;
|
|
658
|
+
expect(tile.selfFading).toBe(true);
|
|
659
|
+
expect(tile.timeAdded).toBe(1000);
|
|
660
|
+
expect(tile.fadeEndTime).toBe(1300);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test('bases the self fade timer on when tile data lands, even if a reload was requested prior to arrival', async () => {
|
|
664
|
+
const transform = new MercatorTransform();
|
|
665
|
+
transform.resize(511, 511);
|
|
666
|
+
transform.setZoom(0);
|
|
667
|
+
const tileID = new OverscaledTileID(0, 0, 0, 0, 0);
|
|
668
|
+
|
|
669
|
+
const tileManager = createTileManager();
|
|
670
|
+
tileManager.setRasterFadeDuration(300);
|
|
671
|
+
let respond: () => void;
|
|
672
|
+
const responded = new Promise<void>(resolve => { respond = resolve; });
|
|
673
|
+
tileManager._source.loadTile = async (tile) => {
|
|
674
|
+
await responded;
|
|
675
|
+
tile.state = 'loaded';
|
|
676
|
+
};
|
|
677
|
+
const metadataPromise = waitForEvent(tileManager, 'data', e => e.sourceDataType === 'metadata');
|
|
678
|
+
tileManager.onAdd(undefined);
|
|
679
|
+
await metadataPromise;
|
|
680
|
+
|
|
681
|
+
setNow(1000);
|
|
682
|
+
tileManager.update(transform);
|
|
683
|
+
const tile = tileManager.getTile(tileID);
|
|
684
|
+
tile.setSelfFadeLogic(now() + 300);
|
|
685
|
+
const loadPromise = waitForEvent(tileManager, 'data', e => e.tile?.tileID.key === tileID.key);
|
|
686
|
+
|
|
687
|
+
tileManager.getSource().fire(new Event('data', {dataType: 'source', sourceDataType: 'content'}));
|
|
688
|
+
|
|
689
|
+
expect(tile.state).toBe('loading');
|
|
690
|
+
setNow(1010);
|
|
691
|
+
respond();
|
|
692
|
+
await loadPromise;
|
|
693
|
+
expect(tile.selfFading).toBe(true);
|
|
694
|
+
expect(tile.timeAdded).toBe(1010);
|
|
695
|
+
expect(tile.fadeEndTime).toBe(1310);
|
|
696
|
+
});
|
|
697
|
+
|
|
630
698
|
});
|
|
631
699
|
|
|
632
700
|
describe('TileManager.update', () => {
|
package/src/tile/tile_manager.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {GEOJSON_TILE_LAYER_NAME} from '../data/feature_index.ts';
|
|
|
16
16
|
import {hasRasterTransition, isRasterType, updateFadingTiles} from './tile_manager_raster.ts';
|
|
17
17
|
import {backfillDEM} from './tile_manager_raster_dem.ts';
|
|
18
18
|
import {InViewTiles} from './tile_manager_in_view_tiles.ts';
|
|
19
|
-
import {MapSourceDataEvent} from '../ui/events.ts';
|
|
19
|
+
import {MapSourceDataEvent, type SourceEventType} from '../ui/events.ts';
|
|
20
20
|
|
|
21
21
|
import type Point from '@mapbox/point-geometry';
|
|
22
22
|
import type {Context} from '../webgl/context.ts';
|
|
@@ -55,7 +55,7 @@ type TileResult = {
|
|
|
55
55
|
* - unloading cached tiles not needed to render a given viewport
|
|
56
56
|
* - managing tile state and feature state
|
|
57
57
|
*/
|
|
58
|
-
export class TileManager extends Evented {
|
|
58
|
+
export class TileManager extends Evented<SourceEventType> {
|
|
59
59
|
id: string;
|
|
60
60
|
dispatcher: Dispatcher;
|
|
61
61
|
map: Map;
|
|
@@ -185,10 +185,10 @@ export class TileManager extends Evented {
|
|
|
185
185
|
if (this.transform) this.update(this.transform, this.terrain);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
async _loadTile(tile: Tile, id: string, state: TileState): Promise<void> {
|
|
188
|
+
async _loadTile(tile: Tile, id: string, state: TileState, hadData: boolean): Promise<void> {
|
|
189
189
|
try {
|
|
190
190
|
const result = await this._source.loadTile(tile) as LoadTileResult;
|
|
191
|
-
this._tileLoaded(tile, id, state, result);
|
|
191
|
+
this._tileLoaded(tile, id, state, hadData, result);
|
|
192
192
|
} catch (err) {
|
|
193
193
|
tile.state = 'errored';
|
|
194
194
|
|
|
@@ -287,6 +287,8 @@ export class TileManager extends Evented {
|
|
|
287
287
|
// - hard to tell without repro steps
|
|
288
288
|
if (!tile) return;
|
|
289
289
|
|
|
290
|
+
const hadData = tile.hasData();
|
|
291
|
+
|
|
290
292
|
// The difference between "loading" tiles and "reloading" or "expired"
|
|
291
293
|
// tiles is that "reloading"/"expired" tiles are "renderable".
|
|
292
294
|
// Therefore, a "loading" tile cannot become a "reloading" tile without
|
|
@@ -294,14 +296,17 @@ export class TileManager extends Evented {
|
|
|
294
296
|
if (tile.state !== 'loading') {
|
|
295
297
|
tile.state = state;
|
|
296
298
|
}
|
|
297
|
-
await this._loadTile(tile, id, state);
|
|
299
|
+
await this._loadTile(tile, id, state, hadData);
|
|
298
300
|
}
|
|
299
301
|
|
|
300
|
-
_tileLoaded(tile: Tile, id: string, previousState: TileState, result: LoadTileResult): void {
|
|
301
|
-
tile
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
_tileLoaded(tile: Tile, id: string, previousState: TileState, hadData: boolean, result: LoadTileResult): void {
|
|
303
|
+
// If the tile was already showing do not restart its fade-in animation
|
|
304
|
+
if (!hadData) {
|
|
305
|
+
tile.timeAdded = now();
|
|
306
|
+
// Since self-fading applies to unloaded tiles, fadeEndTime must be updated upon load
|
|
307
|
+
if (tile.selfFading) {
|
|
308
|
+
tile.fadeEndTime = tile.timeAdded + this._rasterFadeDuration;
|
|
309
|
+
}
|
|
305
310
|
}
|
|
306
311
|
|
|
307
312
|
if (previousState === 'expired') tile.refreshedUponExpiration = true;
|
|
@@ -708,7 +713,7 @@ export class TileManager extends Evented {
|
|
|
708
713
|
|
|
709
714
|
if (!tile) {
|
|
710
715
|
tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor());
|
|
711
|
-
this._loadTile(tile, tileID.key, tile.state);
|
|
716
|
+
this._loadTile(tile, tileID.key, tile.state, false);
|
|
712
717
|
}
|
|
713
718
|
|
|
714
719
|
tile.uses++;
|
|
@@ -45,15 +45,15 @@ describe('FullscreenControl', () => {
|
|
|
45
45
|
|
|
46
46
|
const click = new window.Event('click');
|
|
47
47
|
|
|
48
|
-
expect(mapContainer.classList.
|
|
48
|
+
expect(mapContainer.classList).not.toContain('maplibregl-pseudo-fullscreen');
|
|
49
49
|
|
|
50
50
|
fullscreen._fullscreenButton.dispatchEvent(click);
|
|
51
51
|
|
|
52
|
-
expect(mapContainer.classList.
|
|
52
|
+
expect(mapContainer.classList).toContain('maplibregl-pseudo-fullscreen');
|
|
53
53
|
|
|
54
54
|
fullscreen._fullscreenButton.dispatchEvent(click);
|
|
55
55
|
|
|
56
|
-
expect(mapContainer.classList.
|
|
56
|
+
expect(mapContainer.classList).not.toContain('maplibregl-pseudo-fullscreen');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
test('start and end events fire for fullscreen button clicks', () => {
|
|
@@ -129,11 +129,11 @@ describe('FullscreenControl', () => {
|
|
|
129
129
|
|
|
130
130
|
const click = new window.Event('click');
|
|
131
131
|
|
|
132
|
-
expect(mapContainer.classList.
|
|
132
|
+
expect(mapContainer.classList).not.toContain('maplibregl-pseudo-fullscreen');
|
|
133
133
|
fullscreen._fullscreenButton.dispatchEvent(click);
|
|
134
|
-
expect(mapContainer.classList.
|
|
134
|
+
expect(mapContainer.classList).toContain('maplibregl-pseudo-fullscreen');
|
|
135
135
|
fullscreen._fullscreenButton.dispatchEvent(click);
|
|
136
|
-
expect(mapContainer.classList.
|
|
136
|
+
expect(mapContainer.classList).not.toContain('maplibregl-pseudo-fullscreen');
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
test('pseudo option forces pseudo fullscreen even when native fullscreen is available', () => {
|
|
@@ -151,7 +151,7 @@ describe('FullscreenControl', () => {
|
|
|
151
151
|
fullscreen._fullscreenButton.dispatchEvent(click);
|
|
152
152
|
|
|
153
153
|
expect(requestFullscreenSpy).not.toHaveBeenCalled();
|
|
154
|
-
expect(mapContainer.classList.
|
|
154
|
+
expect(mapContainer.classList).toContain('maplibregl-pseudo-fullscreen');
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
test('pseudo fullscreen can be used on custom container', () => {
|
|
@@ -166,11 +166,11 @@ describe('FullscreenControl', () => {
|
|
|
166
166
|
|
|
167
167
|
const click = new window.Event('click');
|
|
168
168
|
|
|
169
|
-
expect(container.classList.
|
|
169
|
+
expect(container.classList).not.toContain('maplibregl-pseudo-fullscreen');
|
|
170
170
|
fullscreen._fullscreenButton.dispatchEvent(click);
|
|
171
|
-
expect(container.classList.
|
|
171
|
+
expect(container.classList).toContain('maplibregl-pseudo-fullscreen');
|
|
172
172
|
expect(fullscreen._container.tagName).toBe('BODY');
|
|
173
173
|
fullscreen._fullscreenButton.dispatchEvent(click);
|
|
174
|
-
expect(container.classList.
|
|
174
|
+
expect(container.classList).not.toContain('maplibregl-pseudo-fullscreen');
|
|
175
175
|
});
|
|
176
176
|
});
|
|
@@ -418,7 +418,7 @@ describe('GeolocateControl with no options', () => {
|
|
|
418
418
|
geolocate.trigger();
|
|
419
419
|
geolocation.sendError({code: 2, message: 'error message'});
|
|
420
420
|
expect(geolocate._watchState).toBe('ACTIVE_ERROR');
|
|
421
|
-
expect(geolocate._geolocateButton.classList.
|
|
421
|
+
expect(geolocate._geolocateButton.classList).toContain('maplibregl-ctrl-geolocate-active-error');
|
|
422
422
|
});
|
|
423
423
|
|
|
424
424
|
test('trigger before added to map', () => {
|
|
@@ -538,9 +538,7 @@ describe('GeolocateControl with no options', () => {
|
|
|
538
538
|
|
|
539
539
|
expect(lngLatAsFixed(map.getCenter(), 4)).toEqual({lat: '10.0000', lng: '20.0000'});
|
|
540
540
|
expect(geolocate._userLocationDotMarker._map).toBeTruthy();
|
|
541
|
-
expect(
|
|
542
|
-
geolocate._userLocationDotMarker._element.classList.contains('maplibregl-user-location-dot-stale')
|
|
543
|
-
).toBeFalsy();
|
|
541
|
+
expect(geolocate._userLocationDotMarker._element.classList).not.toContain('maplibregl-user-location-dot-stale');
|
|
544
542
|
const secontMoveEnd = map.once('moveend');
|
|
545
543
|
geolocation.change({latitude: 40, longitude: 50, accuracy: 60});
|
|
546
544
|
await secontMoveEnd;
|
|
@@ -549,7 +547,7 @@ describe('GeolocateControl with no options', () => {
|
|
|
549
547
|
geolocation.changeError({code: 2, message: 'position unavailable'});
|
|
550
548
|
await errorPromise;
|
|
551
549
|
expect(geolocate._userLocationDotMarker._map).toBeTruthy();
|
|
552
|
-
expect(geolocate._userLocationDotMarker._element.classList.
|
|
550
|
+
expect(geolocate._userLocationDotMarker._element.classList).toContain('maplibregl-user-location-dot-stale');
|
|
553
551
|
});
|
|
554
552
|
|
|
555
553
|
/**
|
|
@@ -73,8 +73,8 @@ describe('GlobeControl', () => {
|
|
|
73
73
|
test('default without call to setProjection', () => {
|
|
74
74
|
const button = map.getContainer().querySelector('.maplibregl-ctrl-globe');
|
|
75
75
|
expect(map.style.projection.name).toBe('mercator');
|
|
76
|
-
expect(button.classList.
|
|
77
|
-
expect(button.classList.
|
|
76
|
+
expect(button.classList).toContain('maplibregl-ctrl-globe');
|
|
77
|
+
expect(button.classList).not.toContain('maplibregl-ctrl-globe-enabled');
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
test('setProjection({type: "mercator" -> "globe")', () => {
|
|
@@ -85,8 +85,8 @@ describe('GlobeControl', () => {
|
|
|
85
85
|
const button = map.getContainer().querySelector('.maplibregl-ctrl-globe-enabled');
|
|
86
86
|
expect(map.style.projection.name).toBe('globe');
|
|
87
87
|
expect(button).not.toBeNull();
|
|
88
|
-
expect(button.classList.
|
|
89
|
-
expect(button.classList.
|
|
88
|
+
expect(button.classList).toContain('maplibregl-ctrl-globe-enabled');
|
|
89
|
+
expect(button.classList).not.toContain('maplibregl-ctrl-globe');
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
test('setProjection({type: "globe" -> "mercator")', () => {
|
|
@@ -96,8 +96,8 @@ describe('GlobeControl', () => {
|
|
|
96
96
|
// mercator = disabled state
|
|
97
97
|
const button = map.getContainer().querySelector('.maplibregl-ctrl-globe');
|
|
98
98
|
expect(map.style.projection.name).toBe('mercator');
|
|
99
|
-
expect(button.classList.
|
|
100
|
-
expect(button.classList.
|
|
99
|
+
expect(button.classList).toContain('maplibregl-ctrl-globe');
|
|
100
|
+
expect(button.classList).not.toContain('maplibregl-ctrl-globe-enabled');
|
|
101
101
|
});
|
|
102
102
|
});
|
|
103
103
|
});
|
package/src/ui/events.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {describe, beforeEach, test, expect, vi, type MockInstance} from 'vitest';
|
|
2
|
+
import Point from '@mapbox/point-geometry';
|
|
2
3
|
import * as timeControl from '../../util/time_control.ts';
|
|
3
4
|
import {Map, type MapOptions} from '../../ui/map.ts';
|
|
4
5
|
import {DOM} from '../../util/dom.ts';
|
|
@@ -656,6 +657,55 @@ describe('ScrollZoomHandler', () => {
|
|
|
656
657
|
map.remove();
|
|
657
658
|
});
|
|
658
659
|
|
|
660
|
+
describe('globe zooms around the pointer', () => {
|
|
661
|
+
async function zoomInAtOffset(offset: Point) {
|
|
662
|
+
const timeControlNow = vi.spyOn(timeControl, 'now');
|
|
663
|
+
timeControlNow.mockReturnValue(1555555555555);
|
|
664
|
+
const map = createMap();
|
|
665
|
+
await map.once('load');
|
|
666
|
+
map.setProjection({type: 'globe'});
|
|
667
|
+
map.setCenter([0, 0]);
|
|
668
|
+
map.setZoom(0);
|
|
669
|
+
map._renderTaskQueue.run();
|
|
670
|
+
|
|
671
|
+
const pointer = map._camera.transform.centerPoint.add(offset);
|
|
672
|
+
const locationBefore = map.unproject(pointer);
|
|
673
|
+
|
|
674
|
+
simulate.wheel(map.getCanvas(), {deltaY: -simulate.magicWheelZoomDelta * 4, clientX: pointer.x, clientY: pointer.y});
|
|
675
|
+
map._renderTaskQueue.run();
|
|
676
|
+
|
|
677
|
+
const locationAfter = map.unproject(pointer);
|
|
678
|
+
const result = {
|
|
679
|
+
driftLng: locationAfter.lng - locationBefore.lng,
|
|
680
|
+
driftLat: locationAfter.lat - locationBefore.lat,
|
|
681
|
+
center: map.getCenter()
|
|
682
|
+
};
|
|
683
|
+
map.remove();
|
|
684
|
+
return result;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
test('keeps the location under the pointer while the globe is small on screen, and still zooms towards it', async () => {
|
|
688
|
+
const wellInsideTheGlobe = new Point(0, -40);
|
|
689
|
+
const {driftLng, driftLat, center} = await zoomInAtOffset(wellInsideTheGlobe);
|
|
690
|
+
expect(driftLng).toBeCloseTo(0, 6);
|
|
691
|
+
expect(driftLat).toBeCloseTo(0, 6);
|
|
692
|
+
expect(center.lat).toBeCloseTo(0.5972, 3);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test('eases the anchoring off near the horizon instead of dropping it', async () => {
|
|
696
|
+
const nearTheHorizon = new Point(0, -67.5);
|
|
697
|
+
const {driftLat} = await zoomInAtOffset(nearTheHorizon);
|
|
698
|
+
expect(Math.abs(driftLat)).toBeGreaterThan(0.5);
|
|
699
|
+
expect(Math.abs(driftLat)).toBeLessThan(1.5);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test('leaves zooming with the pointer off the planet unchanged', async () => {
|
|
703
|
+
const offThePlanet = new Point(0, -85);
|
|
704
|
+
const {center} = await zoomInAtOffset(offThePlanet);
|
|
705
|
+
expect(center.lat).toBeCloseTo(0.3545, 3);
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
|
|
659
709
|
test('Clamps to min/max zoom when using mercator projection', async () => {
|
|
660
710
|
const timeControlNow = vi.spyOn(timeControl, 'now');
|
|
661
711
|
const map = createMap();
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {describe, beforeEach, test, expect, vi} from 'vitest';
|
|
1
|
+
import {describe, beforeEach, test, expect, vi, onTestFinished} from 'vitest';
|
|
2
2
|
import {Map} from '../map.ts';
|
|
3
3
|
import {Marker} from '../marker.ts';
|
|
4
4
|
import {DOM} from '../../util/dom.ts';
|
|
5
5
|
import simulate from '../../../test/unit/lib/simulate_interaction.ts';
|
|
6
6
|
import {beforeMapTest} from '../../util/test/util.ts';
|
|
7
|
+
import {restoreNow, setNow} from '../../util/time_control.ts';
|
|
7
8
|
|
|
8
9
|
function createMap() {
|
|
9
10
|
return new Map({container: DOM.create('div', '', window.document.body)});
|
|
@@ -96,28 +97,38 @@ describe('touch zoom rotate', () => {
|
|
|
96
97
|
map.on('rotate', rotate);
|
|
97
98
|
map.on('rotateend', rotateend);
|
|
98
99
|
|
|
100
|
+
// Frozen time, so that the inertia does not depend on how long the test itself takes.
|
|
101
|
+
setNow(1000);
|
|
102
|
+
onTestFinished(restoreNow);
|
|
99
103
|
simulate.touchstart(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -50}, {target, identifier: 1, clientX: 0, clientY: 50}]});
|
|
100
104
|
map._renderTaskQueue.run();
|
|
101
105
|
expect(rotatestart).toHaveBeenCalledTimes(0);
|
|
102
106
|
expect(rotate).toHaveBeenCalledTimes(0);
|
|
103
107
|
expect(rotateend).toHaveBeenCalledTimes(0);
|
|
104
108
|
|
|
109
|
+
setNow(1016);
|
|
105
110
|
simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: -50, clientY: 0}, {target, identifier: 1, clientX: 50, clientY: 0}]});
|
|
106
111
|
map._renderTaskQueue.run();
|
|
107
112
|
expect(rotatestart).toHaveBeenCalledTimes(1);
|
|
108
113
|
expect(rotate).toHaveBeenCalledTimes(1);
|
|
109
114
|
expect(rotateend).toHaveBeenCalledTimes(0);
|
|
110
115
|
|
|
116
|
+
setNow(1032);
|
|
111
117
|
simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -50}, {target, identifier: 1, clientX: 0, clientY: 50}]});
|
|
112
118
|
map._renderTaskQueue.run();
|
|
113
119
|
expect(rotatestart).toHaveBeenCalledTimes(1);
|
|
114
120
|
expect(rotate).toHaveBeenCalledTimes(2);
|
|
115
121
|
expect(rotateend).toHaveBeenCalledTimes(0);
|
|
116
122
|
|
|
123
|
+
setNow(1048);
|
|
117
124
|
simulate.touchend(map.getCanvas(), {touches: []});
|
|
118
125
|
map._renderTaskQueue.run();
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
|
|
127
|
+
// incremented because inertia starts a second rotation: the gesture was still
|
|
128
|
+
// rotating when it ended, even though it ended at the bearing it started from
|
|
129
|
+
expect(rotatestart).toHaveBeenCalledTimes(2);
|
|
130
|
+
map._renderTaskQueue.run();
|
|
131
|
+
expect(rotate).toHaveBeenCalledTimes(3);
|
|
121
132
|
expect(rotateend).toHaveBeenCalledTimes(1);
|
|
122
133
|
|
|
123
134
|
map.remove();
|
|
@@ -312,11 +323,11 @@ describe('touch zoom rotate', () => {
|
|
|
312
323
|
const map = createMap();
|
|
313
324
|
|
|
314
325
|
const className = 'maplibregl-touch-zoom-rotate';
|
|
315
|
-
expect(map.getCanvasContainer().classList.
|
|
326
|
+
expect(map.getCanvasContainer().classList).toContain(className);
|
|
316
327
|
map.touchZoomRotate.disable();
|
|
317
|
-
expect(map.getCanvasContainer().classList.
|
|
328
|
+
expect(map.getCanvasContainer().classList).not.toContain(className);
|
|
318
329
|
map.touchZoomRotate.enable();
|
|
319
|
-
expect(map.getCanvasContainer().classList.
|
|
330
|
+
expect(map.getCanvasContainer().classList).toContain(className);
|
|
320
331
|
});
|
|
321
332
|
|
|
322
333
|
test('TwoFingersTouchZoomRotateHandler zooms when touching two markers on the same map', () => {
|