| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 | import binarySearch from "./binarySearch.js";import Cartographic from "./Cartographic.js";import defined from "./defined.js";import Rectangle from "./Rectangle.js";/** * Reports the availability of tiles in a {@link TilingScheme}. * * @alias TileAvailability * @constructor * * @param {TilingScheme} tilingScheme The tiling scheme in which to report availability. * @param {Number} maximumLevel The maximum tile level that is potentially available. */function TileAvailability(tilingScheme, maximumLevel) {  this._tilingScheme = tilingScheme;  this._maximumLevel = maximumLevel;  this._rootNodes = [];}const rectangleScratch = new Rectangle();function findNode(level, x, y, nodes) {  const count = nodes.length;  for (let i = 0; i < count; ++i) {    const node = nodes[i];    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. */TileAvailability.prototype.addAvailableTileRange = function (  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 (!findNode(level, x, y, rootNodes)) {          rootNodes.push(new QuadtreeNode(tilingScheme, undefined, 0, x, y));        }      }    }  }  tilingScheme.tileXYToRectangle(startX, startY, level, rectangleScratch);  const west = rectangleScratch.west;  const north = rectangleScratch.north;  tilingScheme.tileXYToRectangle(endX, endY, level, rectangleScratch);  const east = rectangleScratch.east;  const south = rectangleScratch.south;  const rectangleWithLevel = new RectangleWithLevel(    level,    west,    south,    east,    north  );  for (let i = 0; i < rootNodes.length; ++i) {    const rootNode = rootNodes[i];    if (rectanglesOverlap(rootNode.extent, rectangleWithLevel)) {      putRectangleInQuadtree(this._maximumLevel, rootNode, rectangleWithLevel);    }  }};/** * Determines the level of the most detailed tile covering the position.  This function * usually completes in time logarithmic to the number of rectangles added with * {@link TileAvailability#addAvailableTileRange}. * * @param {Cartographic} position The position for which to determine the maximum available level.  The height component is ignored. * @return {Number} The level of the most detailed tile covering the position. * @throws {DeveloperError} If position is outside any tile according to the tiling scheme. */TileAvailability.prototype.computeMaximumLevelAtPosition = function (position) {  // Find the root node that contains this position.  let node;  for (let nodeIndex = 0; nodeIndex < this._rootNodes.length; ++nodeIndex) {    const rootNode = this._rootNodes[nodeIndex];    if (rectangleContainsPosition(rootNode.extent, position)) {      node = rootNode;      break;    }  }  if (!defined(node)) {    return -1;  }  return findMaxLevelFromNode(undefined, node, position);};const rectanglesScratch = [];const remainingToCoverByLevelScratch = [];const westScratch = new Rectangle();const eastScratch = new Rectangle();/** * Finds the most detailed level that is available _everywhere_ within a given rectangle.  More detailed * tiles may be available in parts of the rectangle, but not the whole thing.  The return value of this * function may be safely passed to {@link sampleTerrain} for any position within the rectangle.  This function * usually completes in time logarithmic to the number of rectangles added with * {@link TileAvailability#addAvailableTileRange}. * * @param {Rectangle} rectangle The rectangle. * @return {Number} The best available level for the entire rectangle. */TileAvailability.prototype.computeBestAvailableLevelOverRectangle = function (  rectangle) {  const rectangles = rectanglesScratch;  rectangles.length = 0;  if (rectangle.east < rectangle.west) {    // Rectangle crosses the IDL, make it two rectangles.    rectangles.push(      Rectangle.fromRadians(        -Math.PI,        rectangle.south,        rectangle.east,        rectangle.north,        westScratch      )    );    rectangles.push(      Rectangle.fromRadians(        rectangle.west,        rectangle.south,        Math.PI,        rectangle.north,        eastScratch      )    );  } else {    rectangles.push(rectangle);  }  const remainingToCoverByLevel = remainingToCoverByLevelScratch;  remainingToCoverByLevel.length = 0;  let i;  for (i = 0; i < this._rootNodes.length; ++i) {    updateCoverageWithNode(      remainingToCoverByLevel,      this._rootNodes[i],      rectangles    );  }  for (i = remainingToCoverByLevel.length - 1; i >= 0; --i) {    if (      defined(remainingToCoverByLevel[i]) &&      remainingToCoverByLevel[i].length === 0    ) {      return i;    }  }  return 0;};const cartographicScratch = new Cartographic();/** * 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. */TileAvailability.prototype.isTileAvailable = function (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,    rectangleScratch  );  Rectangle.center(rectangle, cartographicScratch);  return this.computeMaximumLevelAtPosition(cartographicScratch) >= level;};/** * Computes a bit mask indicating which of a tile's four children exist. * If a child's bit is set, a tile is available for that child.  If it is cleared, * the tile is not available.  The bit values are as follows: * <table> *     <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr> *     <tr><td>0</td><td>1</td><td>Southwest</td></tr> *     <tr><td>1</td><td>2</td><td>Southeast</td></tr> *     <tr><td>2</td><td>4</td><td>Northwest</td></tr> *     <tr><td>3</td><td>8</td><td>Northeast</td></tr> * </table> * * @param {Number} level The level of the parent tile. * @param {Number} x The X coordinate of the parent tile. * @param {Number} y The Y coordinate of the parent tile. * @return {Number} The bit mask indicating child availability. */TileAvailability.prototype.computeChildMaskForTile = function (level, x, y) {  const childLevel = level + 1;  if (childLevel >= this._maximumLevel) {    return 0;  }  let mask = 0;  mask |= this.isTileAvailable(childLevel, 2 * x, 2 * y + 1) ? 1 : 0;  mask |= this.isTileAvailable(childLevel, 2 * x + 1, 2 * y + 1) ? 2 : 0;  mask |= this.isTileAvailable(childLevel, 2 * x, 2 * y) ? 4 : 0;  mask |= this.isTileAvailable(childLevel, 2 * x + 1, 2 * y) ? 8 : 0;  return mask;};function QuadtreeNode(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);  this.rectangles = [];  this._sw = undefined;  this._se = undefined;  this._nw = undefined;  this._ne = undefined;}Object.defineProperties(QuadtreeNode.prototype, {  nw: {    get: function () {      if (!this._nw) {        this._nw = new QuadtreeNode(          this.tilingScheme,          this,          this.level + 1,          this.x * 2,          this.y * 2        );      }      return this._nw;    },  },  ne: {    get: function () {      if (!this._ne) {        this._ne = new QuadtreeNode(          this.tilingScheme,          this,          this.level + 1,          this.x * 2 + 1,          this.y * 2        );      }      return this._ne;    },  },  sw: {    get: function () {      if (!this._sw) {        this._sw = new QuadtreeNode(          this.tilingScheme,          this,          this.level + 1,          this.x * 2,          this.y * 2 + 1        );      }      return this._sw;    },  },  se: {    get: function () {      if (!this._se) {        this._se = new QuadtreeNode(          this.tilingScheme,          this,          this.level + 1,          this.x * 2 + 1,          this.y * 2 + 1        );      }      return this._se;    },  },});function RectangleWithLevel(level, west, south, east, north) {  this.level = level;  this.west = west;  this.south = south;  this.east = east;  this.north = north;}function rectanglesOverlap(rectangle1, rectangle2) {  const west = Math.max(rectangle1.west, rectangle2.west);  const south = Math.max(rectangle1.south, rectangle2.south);  const east = Math.min(rectangle1.east, rectangle2.east);  const north = Math.min(rectangle1.north, rectangle2.north);  return south < north && west < east;}function putRectangleInQuadtree(maxDepth, node, rectangle) {  while (node.level < maxDepth) {    if (rectangleFullyContainsRectangle(node.nw.extent, rectangle)) {      node = node.nw;    } else if (rectangleFullyContainsRectangle(node.ne.extent, rectangle)) {      node = node.ne;    } else if (rectangleFullyContainsRectangle(node.sw.extent, rectangle)) {      node = node.sw;    } else if (rectangleFullyContainsRectangle(node.se.extent, rectangle)) {      node = node.se;    } else {      break;    }  }  if (    node.rectangles.length === 0 ||    node.rectangles[node.rectangles.length - 1].level <= rectangle.level  ) {    node.rectangles.push(rectangle);  } else {    // Maintain ordering by level when inserting.    let index = binarySearch(      node.rectangles,      rectangle.level,      rectangleLevelComparator    );    if (index < 0) {      index = ~index;    }    node.rectangles.splice(index, 0, rectangle);  }}function rectangleLevelComparator(a, b) {  return a.level - b;}function rectangleFullyContainsRectangle(potentialContainer, rectangleToTest) {  return (    rectangleToTest.west >= potentialContainer.west &&    rectangleToTest.east <= potentialContainer.east &&    rectangleToTest.south >= potentialContainer.south &&    rectangleToTest.north <= potentialContainer.north  );}function rectangleContainsPosition(potentialContainer, positionToTest) {  return (    positionToTest.longitude >= potentialContainer.west &&    positionToTest.longitude <= potentialContainer.east &&    positionToTest.latitude >= potentialContainer.south &&    positionToTest.latitude <= potentialContainer.north  );}function findMaxLevelFromNode(stopNode, node, position) {  let maxLevel = 0;  // Find the deepest quadtree node containing this point.  let found = false;  while (!found) {    const nw = node._nw && rectangleContainsPosition(node._nw.extent, position);    const ne = node._ne && rectangleContainsPosition(node._ne.extent, position);    const sw = node._sw && rectangleContainsPosition(node._sw.extent, position);    const se = node._se && rectangleContainsPosition(node._se.extent, 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 + ne + sw + se > 1) {      if (nw) {        maxLevel = Math.max(          maxLevel,          findMaxLevelFromNode(node, node._nw, position)        );      }      if (ne) {        maxLevel = Math.max(          maxLevel,          findMaxLevelFromNode(node, node._ne, position)        );      }      if (sw) {        maxLevel = Math.max(          maxLevel,          findMaxLevelFromNode(node, node._sw, position)        );      }      if (se) {        maxLevel = Math.max(          maxLevel,          findMaxLevelFromNode(node, node._se, position)        );      }      break;    } else if (nw) {      node = node._nw;    } else if (ne) {      node = node._ne;    } else if (sw) {      node = node._sw;    } else if (se) {      node = node._se;    } 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[i].level > maxLevel;      --i    ) {      const rectangle = rectangles[i];      if (rectangleContainsPosition(rectangle, position)) {        maxLevel = rectangle.level;      }    }    node = node.parent;  }  return maxLevel;}function updateCoverageWithNode(  remainingToCoverByLevel,  node,  rectanglesToCover) {  if (!node) {    return;  }  let i;  let anyOverlap = false;  for (i = 0; i < rectanglesToCover.length; ++i) {    anyOverlap =      anyOverlap || rectanglesOverlap(node.extent, rectanglesToCover[i]);  }  if (!anyOverlap) {    // This node is not applicable to the rectangle(s).    return;  }  const rectangles = node.rectangles;  for (i = 0; i < rectangles.length; ++i) {    const rectangle = rectangles[i];    if (!remainingToCoverByLevel[rectangle.level]) {      remainingToCoverByLevel[rectangle.level] = rectanglesToCover;    }    remainingToCoverByLevel[rectangle.level] = subtractRectangle(      remainingToCoverByLevel[rectangle.level],      rectangle    );  }  // Update with child nodes.  updateCoverageWithNode(remainingToCoverByLevel, node._nw, rectanglesToCover);  updateCoverageWithNode(remainingToCoverByLevel, node._ne, rectanglesToCover);  updateCoverageWithNode(remainingToCoverByLevel, node._sw, rectanglesToCover);  updateCoverageWithNode(remainingToCoverByLevel, node._se, rectanglesToCover);}function subtractRectangle(rectangleList, rectangleToSubtract) {  const result = [];  for (let i = 0; i < rectangleList.length; ++i) {    const rectangle = rectangleList[i];    if (!rectanglesOverlap(rectangle, rectangleToSubtract)) {      // Disjoint rectangles.  Original rectangle is unmodified.      result.push(rectangle);    } else {      // rectangleToSubtract partially or completely overlaps rectangle.      if (rectangle.west < rectangleToSubtract.west) {        result.push(          new Rectangle(            rectangle.west,            rectangle.south,            rectangleToSubtract.west,            rectangle.north          )        );      }      if (rectangle.east > rectangleToSubtract.east) {        result.push(          new Rectangle(            rectangleToSubtract.east,            rectangle.south,            rectangle.east,            rectangle.north          )        );      }      if (rectangle.south < rectangleToSubtract.south) {        result.push(          new Rectangle(            Math.max(rectangleToSubtract.west, rectangle.west),            rectangle.south,            Math.min(rectangleToSubtract.east, rectangle.east),            rectangleToSubtract.south          )        );      }      if (rectangle.north > rectangleToSubtract.north) {        result.push(          new Rectangle(            Math.max(rectangleToSubtract.west, rectangle.west),            rectangleToSubtract.north,            Math.min(rectangleToSubtract.east, rectangle.east),            rectangle.north          )        );      }    }  }  return result;}export default TileAvailability;
 |