maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
239 lines (188 loc) • 8.63 kB
text/typescript
import CrossTileSymbolIndex from './cross_tile_symbol_index';
import {OverscaledTileID} from '../source/tile_id';
import StyleLayer from '../style/style_layer';
const styleLayer = {
id: 'test'
} as StyleLayer;
const makeSymbolInstance = (x, y, key): any => {
return {
anchorX: x,
anchorY: y,
key
};
};
const makeTile = (tileID, symbolInstances): any => {
const bucket = {
symbolInstances: {
get(i) { return symbolInstances[i]; },
length: symbolInstances.length
},
layerIds: ['test']
};
return {
tileID,
getBucket: () => bucket,
latestFeatureIndex: {}
};
};
describe('CrossTileSymbolIndex.addLayer', () => {
test('matches ids', () => {
const index = new CrossTileSymbolIndex();
const mainID = new OverscaledTileID(6, 0, 6, 8, 8);
const mainInstances = [
makeSymbolInstance(1000, 1000, 'Detroit'),
makeSymbolInstance(2000, 2000, 'Toronto')
];
const mainTile = makeTile(mainID, mainInstances);
index.addLayer(styleLayer, [mainTile], 0);
// Assigned new IDs
expect(mainInstances[0].crossTileID).toBe(1);
expect(mainInstances[1].crossTileID).toBe(2);
const childID = new OverscaledTileID(7, 0, 7, 16, 16);
const childInstances = [
makeSymbolInstance(2000, 2000, 'Detroit'),
makeSymbolInstance(2000, 2000, 'Windsor'),
makeSymbolInstance(3000, 3000, 'Toronto'),
makeSymbolInstance(4001, 4001, 'Toronto')
];
const childTile = makeTile(childID, childInstances);
index.addLayer(styleLayer, [mainTile, childTile], 0);
// matched parent tile
expect(childInstances[0].crossTileID).toBe(1);
// does not match because of different key
expect(childInstances[1].crossTileID).toBe(3);
// does not match because of different location
expect(childInstances[2].crossTileID).toBe(4);
// matches with a slightly different location
expect(childInstances[3].crossTileID).toBe(2);
const parentID = new OverscaledTileID(5, 0, 5, 4, 4);
const parentInstances = [
makeSymbolInstance(500, 500, 'Detroit')
];
const parentTile = makeTile(parentID, parentInstances);
index.addLayer(styleLayer, [mainTile, childTile, parentTile], 0);
// matched child tile
expect(parentInstances[0].crossTileID).toBe(1);
const grandchildID = new OverscaledTileID(8, 0, 8, 32, 32);
const grandchildInstances = [
makeSymbolInstance(4000, 4000, 'Detroit'),
makeSymbolInstance(4000, 4000, 'Windsor')
];
const grandchildTile = makeTile(grandchildID, grandchildInstances);
index.addLayer(styleLayer, [mainTile], 0);
index.addLayer(styleLayer, [mainTile, grandchildTile], 0);
// Matches the symbol in `mainBucket`
expect(grandchildInstances[0].crossTileID).toBe(1);
// Does not match the previous value for Windsor because that tile was removed
expect(grandchildInstances[1].crossTileID).toBe(5);
});
test('overwrites ids when re-adding', () => {
const index = new CrossTileSymbolIndex();
const mainID = new OverscaledTileID(6, 0, 6, 8, 8);
const mainInstances = [makeSymbolInstance(1000, 1000, 'Detroit')];
const mainTile = makeTile(mainID, mainInstances);
const childID = new OverscaledTileID(7, 0, 7, 16, 16);
const childInstances = [makeSymbolInstance(2000, 2000, 'Detroit')];
const childTile = makeTile(childID, childInstances);
// assigns a new id
index.addLayer(styleLayer, [mainTile], 0);
expect(mainInstances[0].crossTileID).toBe(1);
// removes the tile
index.addLayer(styleLayer, [], 0);
// assigns a new id
index.addLayer(styleLayer, [childTile], 0);
expect(childInstances[0].crossTileID).toBe(2);
// overwrites the old id to match the already-added tile
index.addLayer(styleLayer, [mainTile, childTile], 0);
expect(mainInstances[0].crossTileID).toBe(2);
expect(childInstances[0].crossTileID).toBe(2);
});
test('does not duplicate ids within one zoom level', () => {
const index = new CrossTileSymbolIndex();
const mainID = new OverscaledTileID(6, 0, 6, 8, 8);
const mainInstances = [
makeSymbolInstance(1000, 1000, ''), // A
makeSymbolInstance(1000, 1000, '') // B
];
const mainTile = makeTile(mainID, mainInstances);
const childID = new OverscaledTileID(7, 0, 7, 16, 16);
const childInstances = [
makeSymbolInstance(2000, 2000, ''), // A'
makeSymbolInstance(2000, 2000, ''), // B'
makeSymbolInstance(2000, 2000, '') // C'
];
const childTile = makeTile(childID, childInstances);
// assigns new ids
index.addLayer(styleLayer, [mainTile], 0);
expect(mainInstances[0].crossTileID).toBe(1);
expect(mainInstances[1].crossTileID).toBe(2);
const layerIndex = index.layerIndexes[styleLayer.id];
expect(Object.keys(layerIndex.usedCrossTileIDs[6])).toEqual(['1', '2']);
// copies parent ids without duplicate ids in this tile
index.addLayer(styleLayer, [childTile], 0);
expect(childInstances[0].crossTileID).toBe(1); // A' copies from A
expect(childInstances[1].crossTileID).toBe(2); // B' copies from B
expect(childInstances[2].crossTileID).toBe(3); // C' gets new ID
// Updates per-zoom usedCrossTileIDs
expect(Object.keys(layerIndex.usedCrossTileIDs[6])).toEqual([]);
expect(Object.keys(layerIndex.usedCrossTileIDs[7])).toEqual(['1', '2', '3']);
});
test('does not regenerate ids for same zoom', () => {
const index = new CrossTileSymbolIndex();
const tileID = new OverscaledTileID(6, 0, 6, 8, 8);
const firstInstances = [
makeSymbolInstance(1000, 1000, ''), // A
makeSymbolInstance(1000, 1000, '') // B
];
const firstTile = makeTile(tileID, firstInstances);
const secondInstances = [
makeSymbolInstance(1000, 1000, ''), // A'
makeSymbolInstance(1000, 1000, ''), // B'
makeSymbolInstance(1000, 1000, ''), // C'
];
const secondTile = makeTile(tileID, secondInstances);
// assigns new ids
index.addLayer(styleLayer, [firstTile], 0);
expect(firstInstances[0].crossTileID).toBe(1);
expect(firstInstances[1].crossTileID).toBe(2);
const layerIndex = index.layerIndexes[styleLayer.id];
expect(Object.keys(layerIndex.usedCrossTileIDs[6])).toEqual(['1', '2']);
// uses same ids when tile gets updated
index.addLayer(styleLayer, [secondTile], 0);
expect(secondInstances[0].crossTileID).toBe(1); // A' copies from A
expect(secondInstances[1].crossTileID).toBe(2); // B' copies from B
expect(secondInstances[2].crossTileID).toBe(3); // C' gets new ID
expect(Object.keys(layerIndex.usedCrossTileIDs[6])).toEqual(['1', '2', '3']);
});
test('reuses indexes when longitude is wrapped', () => {
const index = new CrossTileSymbolIndex();
const longitude = 370;
const tileID = new OverscaledTileID(6, 1, 6, 8, 8);
const firstInstances = [
makeSymbolInstance(1000, 1000, ''), // A
];
const tile = makeTile(tileID, firstInstances);
index.addLayer(styleLayer, [tile], longitude);
expect(firstInstances[0].crossTileID).toBe(1); // A
tile.tileID = tileID.wrapped();
index.addLayer(styleLayer, [tile], longitude % 360);
expect(firstInstances[0].crossTileID).toBe(1);
});
});
describe('CrossTileSymbolIndex.pruneUnusedLayers', () => {
const index = new CrossTileSymbolIndex();
const tileID = new OverscaledTileID(6, 0, 6, 8, 8);
const instances = [
makeSymbolInstance(1000, 1000, ''), // A
makeSymbolInstance(1000, 1000, '') // B
];
const tile = makeTile(tileID, instances);
// assigns new ids
index.addLayer(styleLayer, [tile], 0);
expect(instances[0].crossTileID).toBe(1);
expect(instances[1].crossTileID).toBe(2);
expect(index.layerIndexes[styleLayer.id]).toBeTruthy();
// remove styleLayer
index.pruneUnusedLayers([]);
expect(index.layerIndexes[styleLayer.id]).toBeFalsy();
});