TileAvailability.js 17 KB

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