UNPKG

maplibre-gl

Version:

BSD licensed community fork of mapbox-gl, a WebGL interactive maps library

131 lines (112 loc) • 5.95 kB
import {beforeEach, describe, expect, test} from 'vitest'; import {PACKED_HIDDEN_OPACITY, PACKED_VISIBLE_OPACITY, Placement, RetainedQueryData} from './placement.ts'; import {MercatorTransform} from '../geo/projection/mercator_transform.ts'; import {SymbolStyleLayer} from '../style/style_layer/symbol_style_layer.ts'; import {CollisionBoxArray, SymbolInstanceArray} from '../data/array_types.g.ts'; import {OverscaledTileID} from '../tile/tile_id.ts'; import {FeatureIndex} from '../data/feature_index.ts'; import {CrossTileSymbolIndex} from './cross_tile_symbol_index.ts'; import {createSymbolTile} from '../../test/unit/lib/create_symbol_layer.ts'; import {loadVectorTile} from '../../test/unit/lib/tile.ts'; import type {IndexedFeature} from '../data/bucket.ts'; import type {SymbolBucket} from '../data/bucket/symbol_bucket.ts'; import type {EvaluationParameters} from '../style/evaluation_parameters.ts'; const collisionBoxArray = new CollisionBoxArray(); /** * Two tiles at the same tile ID, each with a symbol bucket holding the same one label, so * that the cross tile index gives both buckets the same cross tile ID for it. */ function setupTwoTilesSharingOneLabel() { const features = [{feature: loadVectorTile().layers.place_label.feature(10)} as unknown as IndexedFeature]; const tileID = new OverscaledTileID(0, 0, 0, 0, 0); const tiles = Array.from({length: 2}, () => createSymbolTile(tileID, features, collisionBoxArray, {'text-allow-overlap': true, 'text-ignore-placement': true})); const buckets = tiles.map(tile => tile.buckets.test as SymbolBucket); const layer = buckets[0].layers[0]; new CrossTileSymbolIndex().addLayer(layer, tiles, 0); return {tiles, buckets, layer}; } /** Written over a packed opacity, so that finding it again means the buffer was not rewritten. */ const SENTINEL = 12345; describe('placement', () => { let placement: Placement; let transform: MercatorTransform; beforeEach(() => { transform = new MercatorTransform(); transform.resize(512, 512); placement = new Placement(transform, undefined, 0, true); }); test('should not throw on integer overflow', () => { const layer = new SymbolStyleLayer({ id: 'contour-label', type: 'symbol', source: 'contours', 'source-layer': 'contours', layout: { 'text-font': ['Test'], 'text-field': 'test', 'symbol-placement': 'line' }, }, {}); layer.recalculate({zoom: 22, zoomHistory: {}} as EvaluationParameters, undefined); const tileId = new OverscaledTileID(22, 0, 12, 2447, 1666); const bucketInstanceId = 1; placement.retainedQueryData[bucketInstanceId] = new RetainedQueryData( bucketInstanceId, new FeatureIndex(tileId), 0, 0, tileId ); const bucket = { bucketInstanceId, symbolInstances: new SymbolInstanceArray(), collisionArrays: {0: new CollisionBoxArray()}, }; const int16Overflow = Math.pow(2, 15) + 1; bucket.symbolInstances.emplaceBack(0, 0, 0, int16Overflow, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0); bucket.symbolInstances.get(0).crossTileID = 1; expect(() => { placement.placeLayerBucketPart({ symbolInstanceStart: 0, symbolInstanceEnd: 1, parameters: { layout: layer.layout, bucket } as any }, {}, false); }).not.toThrow(); }); test('the first bucket draws the shared label and the second hides it', () => { const {tiles, buckets, layer} = setupTwoTilesSharingOneLabel(); placement.updateLayerOpacities(layer, tiles); expect(buckets[0].text.opacityVertexArray.uint32[0]).toBe(PACKED_VISIBLE_OPACITY); expect(buckets[1].text.opacityVertexArray.uint32[0]).toBe(PACKED_HIDDEN_OPACITY); }); test('leaves the opacity buffers alone when the same tiles come round again', () => { const {tiles, buckets, layer} = setupTwoTilesSharingOneLabel(); placement.updateLayerOpacities(layer, tiles); for (const bucket of buckets) bucket.text.opacityVertexArray.uint32[0] = SENTINEL; placement.updateLayerOpacities(layer, tiles); for (const bucket of buckets) expect(bucket.text.opacityVertexArray.uint32[0]).toBe(SENTINEL); }); test('rewrites a bucket whose cross tile IDs were reassigned', () => { const {tiles, buckets, layer} = setupTwoTilesSharingOneLabel(); placement.updateLayerOpacities(layer, tiles); for (const bucket of buckets) bucket.text.opacityVertexArray.uint32[0] = SENTINEL; // Reindexing hands out fresh IDs. The same bucket still draws the label, so only the // cross tile IDs differ from what was recorded, not the duplicate flags. for (const bucket of buckets) bucket.symbolInstances.get(0).crossTileID = 9999; placement.updateLayerOpacities(layer, tiles); expect(buckets[0].text.opacityVertexArray.uint32[0]).toBe(PACKED_VISIBLE_OPACITY); expect(buckets[1].text.opacityVertexArray.uint32[0]).toBe(PACKED_HIDDEN_OPACITY); }); test('rewrites a bucket once the bucket that was hiding its label is gone', () => { const {tiles, buckets, layer} = setupTwoTilesSharingOneLabel(); placement.updateLayerOpacities(layer, tiles); expect(buckets[1].text.opacityVertexArray.uint32[0]).toBe(PACKED_HIDDEN_OPACITY); // The first tile drops out, so the second bucket now has to draw the label itself. placement.updateLayerOpacities(layer, tiles.slice(1)); expect(buckets[1].text.opacityVertexArray.uint32[0]).toBe(PACKED_VISIBLE_OPACITY); }); });