TileAvailability.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. import binarySearch from "./binarySearch.js";
  2. import Cartographic from "./Cartographic.js";
  3. import defined from "./defined.js";
  4. import Rectangle from "./Rectangle.js";
  5. /**
  6. * Reports the availability of tiles in a {@link TilingScheme}.
  7. *
  8. * @alias TileAvailability
  9. * @constructor
  10. *
  11. * @param {TilingScheme} tilingScheme The tiling scheme in which to report availability.
  12. * @param {Number} maximumLevel The maximum tile level that is potentially available.
  13. */
  14. function TileAvailability(tilingScheme, maximumLevel) {
  15. this._tilingScheme = tilingScheme;
  16. this._maximumLevel = maximumLevel;
  17. this._rootNodes = [];
  18. }
  19. const rectangleScratch = new Rectangle();
  20. function findNode(level, x, y, nodes) {
  21. const count = nodes.length;
  22. for (let i = 0; i < count; ++i) {
  23. const node = nodes[i];
  24. if (node.x === x && node.y === y && node.level === level) {
  25. return true;
  26. }
  27. }
  28. return false;
  29. }
  30. /**
  31. * Marks a rectangular range of tiles in a particular level as being available. For best performance,
  32. * add your ranges in order of increasing level.
  33. *
  34. * @param {Number} level The level.
  35. * @param {Number} startX The X coordinate of the first available tiles at the level.
  36. * @param {Number} startY The Y coordinate of the first available tiles at the level.
  37. * @param {Number} endX The X coordinate of the last available tiles at the level.
  38. * @param {Number} endY The Y coordinate of the last available tiles at the level.
  39. */
  40. TileAvailability.prototype.addAvailableTileRange = function (
  41. level,
  42. startX,
  43. startY,
  44. endX,
  45. endY
  46. ) {
  47. const tilingScheme = this._tilingScheme;
  48. const rootNodes = this._rootNodes;
  49. if (level === 0) {
  50. for (let y = startY; y <= endY; ++y) {
  51. for (let x = startX; x <= endX; ++x) {
  52. if (!findNode(level, x, y, rootNodes)) {
  53. rootNodes.push(new QuadtreeNode(tilingScheme, undefined, 0, x, y));
  54. }
  55. }
  56. }
  57. }
  58. tilingScheme.tileXYToRectangle(startX, startY, level, rectangleScratch);
  59. const west = rectangleScratch.west;
  60. const north = rectangleScratch.north;
  61. tilingScheme.tileXYToRectangle(endX, endY, level, rectangleScratch);
  62. const east = rectangleScratch.east;
  63. const south = rectangleScratch.south;
  64. const rectangleWithLevel = new RectangleWithLevel(
  65. level,
  66. west,
  67. south,
  68. east,
  69. north
  70. );
  71. for (let i = 0; i < rootNodes.length; ++i) {
  72. const rootNode = rootNodes[i];
  73. if (rectanglesOverlap(rootNode.extent, rectangleWithLevel)) {
  74. putRectangleInQuadtree(this._maximumLevel, rootNode, rectangleWithLevel);
  75. }
  76. }
  77. };
  78. /**
  79. * Determines the level of the most detailed tile covering the position. This function
  80. * usually completes in time logarithmic to the number of rectangles added with
  81. * {@link TileAvailability#addAvailableTileRange}.
  82. *
  83. * @param {Cartographic} position The position for which to determine the maximum available level. The height component is ignored.
  84. * @return {Number} The level of the most detailed tile covering the position.
  85. * @throws {DeveloperError} If position is outside any tile according to the tiling scheme.
  86. */
  87. TileAvailability.prototype.computeMaximumLevelAtPosition = function (position) {
  88. // Find the root node that contains this position.
  89. let node;
  90. for (let nodeIndex = 0; nodeIndex < this._rootNodes.length; ++nodeIndex) {
  91. const rootNode = this._rootNodes[nodeIndex];
  92. if (rectangleContainsPosition(rootNode.extent, position)) {
  93. node = rootNode;
  94. break;
  95. }
  96. }
  97. if (!defined(node)) {
  98. return -1;
  99. }
  100. return findMaxLevelFromNode(undefined, node, position);
  101. };
  102. const rectanglesScratch = [];
  103. const remainingToCoverByLevelScratch = [];
  104. const westScratch = new Rectangle();
  105. const eastScratch = new Rectangle();
  106. /**
  107. * Finds the most detailed level that is available _everywhere_ within a given rectangle. More detailed
  108. * tiles may be available in parts of the rectangle, but not the whole thing. The return value of this
  109. * function may be safely passed to {@link sampleTerrain} for any position within the rectangle. This function
  110. * usually completes in time logarithmic to the number of rectangles added with
  111. * {@link TileAvailability#addAvailableTileRange}.
  112. *
  113. * @param {Rectangle} rectangle The rectangle.
  114. * @return {Number} The best available level for the entire rectangle.
  115. */
  116. TileAvailability.prototype.computeBestAvailableLevelOverRectangle = function (
  117. rectangle
  118. ) {
  119. const rectangles = rectanglesScratch;
  120. rectangles.length = 0;
  121. if (rectangle.east < rectangle.west) {
  122. // Rectangle crosses the IDL, make it two rectangles.
  123. rectangles.push(
  124. Rectangle.fromRadians(
  125. -Math.PI,
  126. rectangle.south,
  127. rectangle.east,
  128. rectangle.north,
  129. westScratch
  130. )
  131. );
  132. rectangles.push(
  133. Rectangle.fromRadians(
  134. rectangle.west,
  135. rectangle.south,
  136. Math.PI,
  137. rectangle.north,
  138. eastScratch
  139. )
  140. );
  141. } else {
  142. rectangles.push(rectangle);
  143. }
  144. const remainingToCoverByLevel = remainingToCoverByLevelScratch;
  145. remainingToCoverByLevel.length = 0;
  146. let i;
  147. for (i = 0; i < this._rootNodes.length; ++i) {
  148. updateCoverageWithNode(
  149. remainingToCoverByLevel,
  150. this._rootNodes[i],
  151. rectangles
  152. );
  153. }
  154. for (i = remainingToCoverByLevel.length - 1; i >= 0; --i) {
  155. if (
  156. defined(remainingToCoverByLevel[i]) &&
  157. remainingToCoverByLevel[i].length === 0
  158. ) {
  159. return i;
  160. }
  161. }
  162. return 0;
  163. };
  164. const cartographicScratch = new Cartographic();
  165. /**
  166. * Determines if a particular tile is available.
  167. * @param {Number} level The tile level to check.
  168. * @param {Number} x The X coordinate of the tile to check.
  169. * @param {Number} y The Y coordinate of the tile to check.
  170. * @return {Boolean} True if the tile is available; otherwise, false.
  171. */
  172. TileAvailability.prototype.isTileAvailable = function (level, x, y) {
  173. // Get the center of the tile and find the maximum level at that position.
  174. // Because availability is by tile, if the level is available at that point, it
  175. // is sure to be available for the whole tile. We assume that if a tile at level n exists,
  176. // then all its parent tiles back to level 0 exist too. This isn't really enforced
  177. // anywhere, but Cesium would never load a tile for which this is not true.
  178. const rectangle = this._tilingScheme.tileXYToRectangle(
  179. x,
  180. y,
  181. level,
  182. rectangleScratch
  183. );
  184. Rectangle.center(rectangle, cartographicScratch);
  185. return this.computeMaximumLevelAtPosition(cartographicScratch) >= level;
  186. };
  187. /**
  188. * Computes a bit mask indicating which of a tile's four children exist.
  189. * If a child's bit is set, a tile is available for that child. If it is cleared,
  190. * the tile is not available. The bit values are as follows:
  191. * <table>
  192. * <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
  193. * <tr><td>0</td><td>1</td><td>Southwest</td></tr>
  194. * <tr><td>1</td><td>2</td><td>Southeast</td></tr>
  195. * <tr><td>2</td><td>4</td><td>Northwest</td></tr>
  196. * <tr><td>3</td><td>8</td><td>Northeast</td></tr>
  197. * </table>
  198. *
  199. * @param {Number} level The level of the parent tile.
  200. * @param {Number} x The X coordinate of the parent tile.
  201. * @param {Number} y The Y coordinate of the parent tile.
  202. * @return {Number} The bit mask indicating child availability.
  203. */
  204. TileAvailability.prototype.computeChildMaskForTile = function (level, x, y) {
  205. const childLevel = level + 1;
  206. if (childLevel >= this._maximumLevel) {
  207. return 0;
  208. }
  209. let mask = 0;
  210. mask |= this.isTileAvailable(childLevel, 2 * x, 2 * y + 1) ? 1 : 0;
  211. mask |= this.isTileAvailable(childLevel, 2 * x + 1, 2 * y + 1) ? 2 : 0;
  212. mask |= this.isTileAvailable(childLevel, 2 * x, 2 * y) ? 4 : 0;
  213. mask |= this.isTileAvailable(childLevel, 2 * x + 1, 2 * y) ? 8 : 0;
  214. return mask;
  215. };
  216. function QuadtreeNode(tilingScheme, parent, level, x, y) {
  217. this.tilingScheme = tilingScheme;
  218. this.parent = parent;
  219. this.level = level;
  220. this.x = x;
  221. this.y = y;
  222. this.extent = tilingScheme.tileXYToRectangle(x, y, level);
  223. this.rectangles = [];
  224. this._sw = undefined;
  225. this._se = undefined;
  226. this._nw = undefined;
  227. this._ne = undefined;
  228. }
  229. Object.defineProperties(QuadtreeNode.prototype, {
  230. nw: {
  231. get: function () {
  232. if (!this._nw) {
  233. this._nw = new QuadtreeNode(
  234. this.tilingScheme,
  235. this,
  236. this.level + 1,
  237. this.x * 2,
  238. this.y * 2
  239. );
  240. }
  241. return this._nw;
  242. },
  243. },
  244. ne: {
  245. get: function () {
  246. if (!this._ne) {
  247. this._ne = new QuadtreeNode(
  248. this.tilingScheme,
  249. this,
  250. this.level + 1,
  251. this.x * 2 + 1,
  252. this.y * 2
  253. );
  254. }
  255. return this._ne;
  256. },
  257. },
  258. sw: {
  259. get: function () {
  260. if (!this._sw) {
  261. this._sw = new QuadtreeNode(
  262. this.tilingScheme,
  263. this,
  264. this.level + 1,
  265. this.x * 2,
  266. this.y * 2 + 1
  267. );
  268. }
  269. return this._sw;
  270. },
  271. },
  272. se: {
  273. get: function () {
  274. if (!this._se) {
  275. this._se = new QuadtreeNode(
  276. this.tilingScheme,
  277. this,
  278. this.level + 1,
  279. this.x * 2 + 1,
  280. this.y * 2 + 1
  281. );
  282. }
  283. return this._se;
  284. },
  285. },
  286. });
  287. function RectangleWithLevel(level, west, south, east, north) {
  288. this.level = level;
  289. this.west = west;
  290. this.south = south;
  291. this.east = east;
  292. this.north = north;
  293. }
  294. function rectanglesOverlap(rectangle1, rectangle2) {
  295. const west = Math.max(rectangle1.west, rectangle2.west);
  296. const south = Math.max(rectangle1.south, rectangle2.south);
  297. const east = Math.min(rectangle1.east, rectangle2.east);
  298. const north = Math.min(rectangle1.north, rectangle2.north);
  299. return south < north && west < east;
  300. }
  301. function putRectangleInQuadtree(maxDepth, node, rectangle) {
  302. while (node.level < maxDepth) {
  303. if (rectangleFullyContainsRectangle(node.nw.extent, rectangle)) {
  304. node = node.nw;
  305. } else if (rectangleFullyContainsRectangle(node.ne.extent, rectangle)) {
  306. node = node.ne;
  307. } else if (rectangleFullyContainsRectangle(node.sw.extent, rectangle)) {
  308. node = node.sw;
  309. } else if (rectangleFullyContainsRectangle(node.se.extent, rectangle)) {
  310. node = node.se;
  311. } else {
  312. break;
  313. }
  314. }
  315. if (
  316. node.rectangles.length === 0 ||
  317. node.rectangles[node.rectangles.length - 1].level <= rectangle.level
  318. ) {
  319. node.rectangles.push(rectangle);
  320. } else {
  321. // Maintain ordering by level when inserting.
  322. let index = binarySearch(
  323. node.rectangles,
  324. rectangle.level,
  325. rectangleLevelComparator
  326. );
  327. if (index < 0) {
  328. index = ~index;
  329. }
  330. node.rectangles.splice(index, 0, rectangle);
  331. }
  332. }
  333. function rectangleLevelComparator(a, b) {
  334. return a.level - b;
  335. }
  336. function rectangleFullyContainsRectangle(potentialContainer, rectangleToTest) {
  337. return (
  338. rectangleToTest.west >= potentialContainer.west &&
  339. rectangleToTest.east <= potentialContainer.east &&
  340. rectangleToTest.south >= potentialContainer.south &&
  341. rectangleToTest.north <= potentialContainer.north
  342. );
  343. }
  344. function rectangleContainsPosition(potentialContainer, positionToTest) {
  345. return (
  346. positionToTest.longitude >= potentialContainer.west &&
  347. positionToTest.longitude <= potentialContainer.east &&
  348. positionToTest.latitude >= potentialContainer.south &&
  349. positionToTest.latitude <= potentialContainer.north
  350. );
  351. }
  352. function findMaxLevelFromNode(stopNode, node, position) {
  353. let maxLevel = 0;
  354. // Find the deepest quadtree node containing this point.
  355. let found = false;
  356. while (!found) {
  357. const nw = node._nw && rectangleContainsPosition(node._nw.extent, position);
  358. const ne = node._ne && rectangleContainsPosition(node._ne.extent, position);
  359. const sw = node._sw && rectangleContainsPosition(node._sw.extent, position);
  360. const se = node._se && rectangleContainsPosition(node._se.extent, position);
  361. // The common scenario is that the point is in only one quadrant and we can simply
  362. // iterate down the tree. But if the point is on a boundary between tiles, it is
  363. // in multiple tiles and we need to check all of them, so use recursion.
  364. if (nw + ne + sw + se > 1) {
  365. if (nw) {
  366. maxLevel = Math.max(
  367. maxLevel,
  368. findMaxLevelFromNode(node, node._nw, position)
  369. );
  370. }
  371. if (ne) {
  372. maxLevel = Math.max(
  373. maxLevel,
  374. findMaxLevelFromNode(node, node._ne, position)
  375. );
  376. }
  377. if (sw) {
  378. maxLevel = Math.max(
  379. maxLevel,
  380. findMaxLevelFromNode(node, node._sw, position)
  381. );
  382. }
  383. if (se) {
  384. maxLevel = Math.max(
  385. maxLevel,
  386. findMaxLevelFromNode(node, node._se, position)
  387. );
  388. }
  389. break;
  390. } else if (nw) {
  391. node = node._nw;
  392. } else if (ne) {
  393. node = node._ne;
  394. } else if (sw) {
  395. node = node._sw;
  396. } else if (se) {
  397. node = node._se;
  398. } else {
  399. found = true;
  400. }
  401. }
  402. // Work up the tree until we find a rectangle that contains this point.
  403. while (node !== stopNode) {
  404. const rectangles = node.rectangles;
  405. // Rectangles are sorted by level, lowest first.
  406. for (
  407. let i = rectangles.length - 1;
  408. i >= 0 && rectangles[i].level > maxLevel;
  409. --i
  410. ) {
  411. const rectangle = rectangles[i];
  412. if (rectangleContainsPosition(rectangle, position)) {
  413. maxLevel = rectangle.level;
  414. }
  415. }
  416. node = node.parent;
  417. }
  418. return maxLevel;
  419. }
  420. function updateCoverageWithNode(
  421. remainingToCoverByLevel,
  422. node,
  423. rectanglesToCover
  424. ) {
  425. if (!node) {
  426. return;
  427. }
  428. let i;
  429. let anyOverlap = false;
  430. for (i = 0; i < rectanglesToCover.length; ++i) {
  431. anyOverlap =
  432. anyOverlap || rectanglesOverlap(node.extent, rectanglesToCover[i]);
  433. }
  434. if (!anyOverlap) {
  435. // This node is not applicable to the rectangle(s).
  436. return;
  437. }
  438. const rectangles = node.rectangles;
  439. for (i = 0; i < rectangles.length; ++i) {
  440. const rectangle = rectangles[i];
  441. if (!remainingToCoverByLevel[rectangle.level]) {
  442. remainingToCoverByLevel[rectangle.level] = rectanglesToCover;
  443. }
  444. remainingToCoverByLevel[rectangle.level] = subtractRectangle(
  445. remainingToCoverByLevel[rectangle.level],
  446. rectangle
  447. );
  448. }
  449. // Update with child nodes.
  450. updateCoverageWithNode(remainingToCoverByLevel, node._nw, rectanglesToCover);
  451. updateCoverageWithNode(remainingToCoverByLevel, node._ne, rectanglesToCover);
  452. updateCoverageWithNode(remainingToCoverByLevel, node._sw, rectanglesToCover);
  453. updateCoverageWithNode(remainingToCoverByLevel, node._se, rectanglesToCover);
  454. }
  455. function subtractRectangle(rectangleList, rectangleToSubtract) {
  456. const result = [];
  457. for (let i = 0; i < rectangleList.length; ++i) {
  458. const rectangle = rectangleList[i];
  459. if (!rectanglesOverlap(rectangle, rectangleToSubtract)) {
  460. // Disjoint rectangles. Original rectangle is unmodified.
  461. result.push(rectangle);
  462. } else {
  463. // rectangleToSubtract partially or completely overlaps rectangle.
  464. if (rectangle.west < rectangleToSubtract.west) {
  465. result.push(
  466. new Rectangle(
  467. rectangle.west,
  468. rectangle.south,
  469. rectangleToSubtract.west,
  470. rectangle.north
  471. )
  472. );
  473. }
  474. if (rectangle.east > rectangleToSubtract.east) {
  475. result.push(
  476. new Rectangle(
  477. rectangleToSubtract.east,
  478. rectangle.south,
  479. rectangle.east,
  480. rectangle.north
  481. )
  482. );
  483. }
  484. if (rectangle.south < rectangleToSubtract.south) {
  485. result.push(
  486. new Rectangle(
  487. Math.max(rectangleToSubtract.west, rectangle.west),
  488. rectangle.south,
  489. Math.min(rectangleToSubtract.east, rectangle.east),
  490. rectangleToSubtract.south
  491. )
  492. );
  493. }
  494. if (rectangle.north > rectangleToSubtract.north) {
  495. result.push(
  496. new Rectangle(
  497. Math.max(rectangleToSubtract.west, rectangle.west),
  498. rectangleToSubtract.north,
  499. Math.min(rectangleToSubtract.east, rectangle.east),
  500. rectangle.north
  501. )
  502. );
  503. }
  504. }
  505. }
  506. return result;
  507. }
  508. export default TileAvailability;