@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
174 lines (154 loc) • 4.88 kB
text/typescript
const TOP = 0;
const TOP_RIGHT = 1;
const RIGHT = 2;
const BOTTOM_RIGHT = 3;
const BOTTOM = 4;
const BOTTOM_LEFT = 5;
const LEFT = 6;
const TOP_LEFT = 7;
export interface Tile {
/**
* The unique ID of the tile.
*/
id: number;
/**
* The tile's X coordinate in the grid.
*/
x: number;
/**
* The tile's Y coordinate in the grid.
*/
y: number;
/**
* The tile's Z coordinate (LOD) in the grid.
*/
z: number;
}
export type NeighbourList<T extends Tile> = [
T | null,
T | null,
T | null,
T | null,
T | null,
T | null,
T | null,
T | null,
];
export type Predicate<T extends Tile> = (tile: T) => boolean;
class TileIndex<T extends Tile> {
tiles: Map<string, WeakRef<T>>;
tilesById: Map<number, WeakRef<T>>;
constructor() {
this.tiles = new Map();
this.tilesById = new Map();
}
/**
* Adds a tile to the index.
*
* @param tile - the tile to add.
*/
addTile(tile: T) {
const key = TileIndex.getKey(tile.x, tile.y, tile.z);
const wr = new WeakRef(tile);
this.tiles.set(key, wr);
this.tilesById.set(tile.id, wr);
}
/**
* Gets a tile by its ID.
*
* @param id - The ID.
* @returns The found tile, otherwise undefined.
*/
getTile(id: number): T | undefined {
const entry = this.tilesById.get(id);
if (entry) {
const value = entry.deref();
if (value) {
return value;
}
}
return undefined;
}
static getKey(x: number, y: number, z: number) {
return `${x},${y},${z}`;
}
/**
* Returns an array containing the 8 possible neighbours of a tile.
* A neighbor is a tile at the same level or higher level located according to the clock order
* from north:
*
* ```
* 7 : north west -- 0 : north -- 1 : north east
* 6 : west -- THE TILE -- 2 : east
* 5 : south west -- 4 : south -- 3 : south east
* ```
*
* If there is no neighbor, if it isn't visible or if it is a smaller level one, return null.
*
* @param tile - the tile to query
* @returns neighbors : Array of found neighbors
*/
getNeighbours(tile: T, result: NeighbourList<T>, predicate?: Predicate<T>): NeighbourList<T> {
const { x, y, z } = tile;
result[TOP] = this.searchTileOrAncestor(x, y + 1, z, predicate);
result[TOP_RIGHT] = this.searchTileOrAncestor(x + 1, y + 1, z, predicate);
result[RIGHT] = this.searchTileOrAncestor(x + 1, y, z, predicate);
result[BOTTOM_RIGHT] = this.searchTileOrAncestor(x + 1, y - 1, z, predicate);
result[BOTTOM] = this.searchTileOrAncestor(x, y - 1, z, predicate);
result[BOTTOM_LEFT] = this.searchTileOrAncestor(x - 1, y - 1, z, predicate);
result[LEFT] = this.searchTileOrAncestor(x - 1, y, z, predicate);
result[TOP_LEFT] = this.searchTileOrAncestor(x - 1, y + 1, z, predicate);
return result;
}
static getParent(x: number, y: number, z: number) {
if (z === 0) {
return null;
}
const newX = Math.floor(x / 2);
const newY = Math.floor(y / 2);
const newZ = z - 1;
return { x: newX, y: newY, z: newZ };
}
update() {
// Remove obsolete entries
const keys = [...this.tiles.keys()];
for (const key of keys) {
const entry = this.tiles.get(key);
if (entry && !entry.deref()) {
this.tiles.delete(key);
}
}
const ids = [...this.tilesById.keys()];
for (const key of ids) {
const entry = this.tilesById.get(key);
if (entry && !entry.deref()) {
this.tilesById.delete(key);
}
}
}
/**
* Search for the specific tile by coordinates if any, or any valid ancestor.
*
* @param x - The tile X coordinate.
* @param y - The tile Y coordinate.
* @param z - The tile Z coordinate (zoom level).
* @returns The matching tile if found, null otherwise.
*/
searchTileOrAncestor(x: number, y: number, z: number, predicate?: Predicate<T>): T | null {
const key = TileIndex.getKey(x, y, z);
const entry = this.tiles.get(key);
if (entry) {
const n = entry.deref();
if (n && (typeof predicate !== 'function' || predicate(n))) {
return n;
}
}
const parent = TileIndex.getParent(x, y, z);
if (!parent) {
return null;
}
return this.searchTileOrAncestor(parent.x, parent.y, parent.z, predicate);
}
}
export default TileIndex;
export { BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT, LEFT, RIGHT, TOP, TOP_LEFT, TOP_RIGHT };