@itwin/core-frontend
Version:
iTwin.js frontend components
225 lines • 9.39 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Tiles
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TileAvailability = void 0;
const core_common_1 = require("@itwin/core-common");
const internal_1 = require("../../../tile/internal");
const core_bentley_1 = require("@itwin/core-bentley");
// portions adapted from Cesium.js Copyright 2011 - 2017 Cesium Contributors
class RectangleWithLevel extends internal_1.MapCartoRectangle {
level;
constructor(level, west, south, east, north) {
super(west, south, east, north);
this.level = level;
}
}
class QuadTreeNode {
tilingScheme;
parent;
level;
x;
y;
swNode;
seNode;
nwNode;
neNode;
extent;
rectangles = new core_bentley_1.SortedArray((lhs, rhs) => lhs.level - rhs.level, true);
constructor(tilingScheme, parent, level, x, y) {
this.tilingScheme = tilingScheme;
this.parent = parent;
this.level = level;
this.x = x;
this.y = y;
this.extent = tilingScheme.tileXYToRectangle(x, y, level + 1);
}
get nw() {
if (!this.nwNode)
this.nwNode = new QuadTreeNode(this.tilingScheme, this, this.level + 1, this.x * 2, this.y * 2);
return this.nwNode;
}
get ne() {
if (!this.neNode)
this.neNode = new QuadTreeNode(this.tilingScheme, this, this.level + 1, this.x * 2 + 1, this.y * 2);
return this.neNode;
}
get sw() {
if (!this.swNode)
this.swNode = new QuadTreeNode(this.tilingScheme, this, this.level + 1, this.x * 2, this.y * 2 + 1);
return this.swNode;
}
get se() {
if (!this.seNode)
this.seNode = new QuadTreeNode(this.tilingScheme, this, this.level + 1, this.x * 2 + 1, this.y * 2 + 1);
return this.seNode;
}
}
function putRectangleInQuadtree(maxDepth, node, rectangle) {
while (node.level < maxDepth) {
if (node.nw.extent.containsRange(rectangle)) {
node = node.nw;
}
else if (node.ne.extent.containsRange(rectangle)) {
node = node.ne;
}
else if (node.sw.extent.containsRange(rectangle)) {
node = node.sw;
}
else if (node.se.extent.containsRange(rectangle)) {
node = node.se;
}
else {
break;
}
}
node.rectangles.insert(rectangle);
}
class TileAvailability {
_tilingScheme;
_maximumLevel;
_rootNodes = new Array();
constructor(_tilingScheme, _maximumLevel) {
this._tilingScheme = _tilingScheme;
this._maximumLevel = _maximumLevel;
}
static rectangleScratch = internal_1.MapCartoRectangle.createMaximum();
findNode(level, x, y, nodes) {
for (const node of nodes) {
if (node.x === x && node.y === y && node.level === level) {
return true;
}
}
return false;
}
/**
* Marks a rectangular range of tiles in a particular level as being available. For best performance,
* add your ranges in order of increasing level.
*
* @param {Number} level The level.
* @param {Number} startX The X coordinate of the first available tiles at the level.
* @param {Number} startY The Y coordinate of the first available tiles at the level.
* @param {Number} endX The X coordinate of the last available tiles at the level.
* @param {Number} endY The Y coordinate of the last available tiles at the level.
*/
addAvailableTileRange(level, startX, startY, endX, endY) {
const tilingScheme = this._tilingScheme;
const rootNodes = this._rootNodes;
if (level === 0) {
for (let y = startY; y <= endY; ++y) {
for (let x = startX; x <= endX; ++x) {
if (!this.findNode(level, x, y, rootNodes)) {
rootNodes.push(new QuadTreeNode(tilingScheme, undefined, 0, x, y));
}
}
}
}
tilingScheme.tileXYToRectangle(startX, startY, level + 1, TileAvailability.rectangleScratch);
const west = TileAvailability.rectangleScratch.west;
const south = TileAvailability.rectangleScratch.south;
tilingScheme.tileXYToRectangle(endX, endY, level + 1, TileAvailability.rectangleScratch);
const east = TileAvailability.rectangleScratch.east;
const north = TileAvailability.rectangleScratch.north;
const rectangleWithLevel = new RectangleWithLevel(level, west, south, east, north);
for (const rootNode of rootNodes) {
if (rootNode.extent.intersectsRange(rectangleWithLevel)) {
putRectangleInQuadtree(this._maximumLevel, rootNode, rectangleWithLevel);
}
}
}
computeMaximumLevelAtPosition(position) {
// Find the root node that contains this position.
let node;
for (const rootNode of this._rootNodes) {
if (rootNode.extent.containsCartographic(position)) {
node = rootNode;
break;
}
}
if (undefined === node) {
return -1;
}
return this.findMaxLevelFromNode(undefined, node, position);
}
_cartographicScratch = core_common_1.Cartographic.createZero();
/**
* Determines if a particular tile is available.
* @param {Number} level The tile level to check.
* @param {Number} x The X coordinate of the tile to check.
* @param {Number} y The Y coordinate of the tile to check.
* @return {Boolean} True if the tile is available; otherwise, false.
*/
isTileAvailable(level, x, y) {
// Get the center of the tile and find the maximum level at that position.
// Because availability is by tile, if the level is available at that point, it
// is sure to be available for the whole tile. We assume that if a tile at level n exists,
// then all its parent tiles back to level 0 exist too. This isn't really enforced
// anywhere, but Cesium would never load a tile for which this is not true.
const rectangle = this._tilingScheme.tileXYToRectangle(x, y, level + 1, TileAvailability.rectangleScratch);
rectangle.getCenter(this._cartographicScratch);
return this.computeMaximumLevelAtPosition(this._cartographicScratch) >= level;
}
findMaxLevelFromNode(stopNode, node, position) {
let maxLevel = 0;
// Find the deepest quadtree node containing this point.
let found = false;
while (!found && node !== undefined) {
const nw = node.nwNode && node.nwNode.extent.containsCartographic(position);
const ne = node.neNode && node.neNode.extent.containsCartographic(position);
const sw = node.swNode && node.swNode.extent.containsCartographic(position);
const se = node.seNode && node.seNode.extent.containsCartographic(position);
// The common scenario is that the point is in only one quadrant and we can simply
// iterate down the tree. But if the point is on a boundary between tiles, it is
// in multiple tiles and we need to check all of them, so use recursion.
if ((nw ? 1 : 0) + (ne ? 1 : 0) + (sw ? 1 : 0) + (se ? 1 : 0) > 1) {
if (nw) {
maxLevel = Math.max(maxLevel, this.findMaxLevelFromNode(node, node.nwNode, position));
}
if (ne) {
maxLevel = Math.max(maxLevel, this.findMaxLevelFromNode(node, node.neNode, position));
}
if (sw) {
maxLevel = Math.max(maxLevel, this.findMaxLevelFromNode(node, node.swNode, position));
}
if (se) {
maxLevel = Math.max(maxLevel, this.findMaxLevelFromNode(node, node.seNode, position));
}
break;
}
else if (nw) {
node = node.nwNode;
}
else if (ne) {
node = node.neNode;
}
else if (sw) {
node = node.swNode;
}
else if (se) {
node = node.seNode;
}
else {
found = true;
}
}
// Work up the tree until we find a rectangle that contains this point.
while (node !== stopNode) {
const rectangles = node.rectangles;
// Rectangles are sorted by level, lowest first.
for (let i = rectangles.length - 1; i >= 0 && rectangles.get(i).level > maxLevel; --i) {
const rectangle = rectangles.get(i);
if (rectangle.containsCartographic(position))
maxLevel = rectangle.level;
}
node = node.parent;
}
return maxLevel;
}
}
exports.TileAvailability = TileAvailability;
//# sourceMappingURL=MapTileAvailability.js.map