智慧水务管理系统 - 精河县供水工程综合管理平台

QuadtreeTile.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. // @ts-check
  2. import defined from "../Core/defined.js";
  3. import DeveloperError from "../Core/DeveloperError.js";
  4. import Rectangle from "../Core/Rectangle.js";
  5. import Cartographic from "../Core/Cartographic.js";
  6. import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js";
  7. import TileSelectionResult from "./TileSelectionResult.js";
  8. /** @import TilingScheme from "../Core/TilingScheme.js"; */
  9. /**
  10. * A simple Least Recently Used (LRU) cache implementation.
  11. *
  12. * @private
  13. */
  14. class LRUCache {
  15. /** @param {number} maxSize */
  16. constructor(maxSize) {
  17. this.maxSize = maxSize;
  18. this.cache = new Map();
  19. }
  20. /**
  21. * @param {unknown} key
  22. * @returns {unknown}
  23. */
  24. get(key) {
  25. if (!this.cache.has(key)) {
  26. return undefined;
  27. }
  28. // Move accessed item to the end (most recently used)
  29. const value = this.cache.get(key);
  30. this.cache.delete(key);
  31. this.cache.set(key, value);
  32. return value;
  33. }
  34. /**
  35. * @param {unknown} key
  36. * @param {unknown} value
  37. */
  38. set(key, value) {
  39. if (this.cache.has(key)) {
  40. this.cache.delete(key);
  41. } else if (this.cache.size >= this.maxSize) {
  42. // Remove the least recently used (first entry)
  43. const firstKey = this.cache.keys().next().value;
  44. this.cache.delete(firstKey);
  45. }
  46. this.cache.set(key, value);
  47. }
  48. /**
  49. * @type {number}
  50. * @readonly
  51. */
  52. get size() {
  53. return this.cache.size;
  54. }
  55. clear() {
  56. this.cache.clear();
  57. }
  58. }
  59. // Maximum cache entries per tile
  60. const MAX_CACHE_ENTRIES = 1000;
  61. /**
  62. * A single tile in a {@link QuadtreePrimitive}.
  63. *
  64. * @private
  65. */
  66. class QuadtreeTile {
  67. /**
  68. * @param {object} options
  69. * @param {number} options.level The level of the tile in the quadtree.
  70. * @param {number} options.x The X coordinate of the tile in the quadtree. 0 is the westernmost tile.
  71. * @param {number} options.y The Y coordinate of the tile in the quadtree. 0 is the northernmost tile.
  72. * @param {TilingScheme} options.tilingScheme The tiling scheme in which this tile exists.
  73. * @param {QuadtreeTile} [options.parent] This tile's parent, or undefined if this is a root tile.
  74. */
  75. constructor(options) {
  76. //>>includeStart('debug', pragmas.debug);
  77. if (!defined(options)) {
  78. throw new DeveloperError("options is required.");
  79. }
  80. if (!defined(options.x)) {
  81. throw new DeveloperError("options.x is required.");
  82. } else if (!defined(options.y)) {
  83. throw new DeveloperError("options.y is required.");
  84. } else if (options.x < 0 || options.y < 0) {
  85. throw new DeveloperError(
  86. "options.x and options.y must be greater than or equal to zero.",
  87. );
  88. }
  89. if (!defined(options.level)) {
  90. throw new DeveloperError(
  91. "options.level is required and must be greater than or equal to zero.",
  92. );
  93. }
  94. if (!defined(options.tilingScheme)) {
  95. throw new DeveloperError("options.tilingScheme is required.");
  96. }
  97. //>>includeEnd('debug');
  98. this._tilingScheme = options.tilingScheme;
  99. this._x = options.x;
  100. this._y = options.y;
  101. this._level = options.level;
  102. this._parent = options.parent;
  103. /** @type {Rectangle} */
  104. this._rectangle = this._tilingScheme.tileXYToRectangle(
  105. this._x,
  106. this._y,
  107. this._level,
  108. );
  109. this._southwestChild = undefined;
  110. this._southeastChild = undefined;
  111. this._northwestChild = undefined;
  112. this._northeastChild = undefined;
  113. // TileReplacementQueue gets/sets these private properties.
  114. this.replacementPrevious = undefined;
  115. this.replacementNext = undefined;
  116. // The distance from the camera to this tile, updated when the tile is selected
  117. // for rendering. We can get rid of this if we have a better way to sort by
  118. // distance - for example, by using the natural ordering of a quadtree.
  119. // QuadtreePrimitive gets/sets this private property.
  120. this._distance = 0.0;
  121. this._loadPriority = 0.0;
  122. this._customData = new Set();
  123. this._customDataIterator = undefined;
  124. /** @type {unknown[]} */
  125. this._addedCustomData = [];
  126. /** @type {unknown[]} */
  127. this._removedCustomData = [];
  128. this._lastSelectionResult = TileSelectionResult.NONE;
  129. this._lastSelectionResultFrame = undefined;
  130. this._loadedCallbacks = {};
  131. // Cache for storing computed position values per tile to avoid redundant calculations
  132. this._positionCache = new LRUCache(MAX_CACHE_ENTRIES);
  133. /**
  134. * Gets or sets the current state of the tile in the tile load pipeline.
  135. * @type {QuadtreeTileLoadState}
  136. * @default {@link QuadtreeTileLoadState.START}
  137. */
  138. this.state = QuadtreeTileLoadState.START;
  139. /**
  140. * Gets or sets a value indicating whether or not the tile is currently renderable.
  141. * @type {boolean}
  142. * @default false
  143. */
  144. this.renderable = false;
  145. /**
  146. * Gets or set a value indicating whether or not the tile was entirely upsampled from its
  147. * parent tile. If all four children of a parent tile were upsampled from the parent,
  148. * we will render the parent instead of the children even if the LOD indicates that
  149. * the children would be preferable.
  150. * @type {boolean}
  151. * @default false
  152. */
  153. this.upsampledFromParent = false;
  154. /**
  155. * Gets or sets the additional data associated with this tile. The exact content is specific to the
  156. * {@link QuadtreeTileProvider}.
  157. * @type {object}
  158. * @default undefined
  159. */
  160. this.data = undefined;
  161. }
  162. /**
  163. * Creates a rectangular set of tiles for level of detail zero, the coarsest, least detailed level.
  164. *
  165. *
  166. * @param {TilingScheme} tilingScheme The tiling scheme for which the tiles are to be created.
  167. * @returns {QuadtreeTile[]} An array containing the tiles at level of detail zero, starting with the
  168. * tile in the northwest corner and followed by the tile (if any) to its east.
  169. */
  170. static createLevelZeroTiles(tilingScheme) {
  171. //>>includeStart('debug', pragmas.debug);
  172. if (!defined(tilingScheme)) {
  173. throw new DeveloperError("tilingScheme is required.");
  174. }
  175. //>>includeEnd('debug');
  176. const numberOfLevelZeroTilesX = tilingScheme.getNumberOfXTilesAtLevel(0);
  177. const numberOfLevelZeroTilesY = tilingScheme.getNumberOfYTilesAtLevel(0);
  178. const result = new Array(numberOfLevelZeroTilesX * numberOfLevelZeroTilesY);
  179. let index = 0;
  180. for (let y = 0; y < numberOfLevelZeroTilesY; ++y) {
  181. for (let x = 0; x < numberOfLevelZeroTilesX; ++x) {
  182. result[index++] = new QuadtreeTile({
  183. tilingScheme: tilingScheme,
  184. x: x,
  185. y: y,
  186. level: 0,
  187. });
  188. }
  189. }
  190. return result;
  191. }
  192. /**
  193. * Generates a unique cache key for a given cartographic position.
  194. *
  195. * @param {Cartographic} cartographic The cartographic coordinates.
  196. * @param {number} maximumScreenSpaceError The maximum screen-space error, in pixels, that is allowed.
  197. * A higher maximum error will render fewer tiles and improve performance, while a lower
  198. * value will improve visual quality.
  199. * @returns {string} A string representing the spatial hash key.
  200. */
  201. _getCacheKey(cartographic, maximumScreenSpaceError) {
  202. return createSpatialHashKey(
  203. cartographic.longitude,
  204. cartographic.latitude,
  205. this._rectangle,
  206. maximumScreenSpaceError,
  207. );
  208. }
  209. /**
  210. * Retrieves a cached position for the specified cartographic position.
  211. *
  212. *
  213. * @param {Cartographic} cartographic - The cartographic coordinates.
  214. * @param {number} maximumScreenSpaceError The maximum screen-space error, in pixels, that is allowed.
  215. * A higher maximum error will render fewer tiles and improve performance, while a lower
  216. * value will improve visual quality.
  217. * @returns {object|undefined} The cached position data or undefined if not found.
  218. */
  219. getPositionCacheEntry(cartographic, maximumScreenSpaceError) {
  220. const result = this._positionCache.get(
  221. this._getCacheKey(cartographic, maximumScreenSpaceError),
  222. );
  223. return /** @type {object|undefined} */ (result);
  224. }
  225. /**
  226. * Sets a position on the cache for this tile.
  227. *
  228. *
  229. * @param {Cartographic} cartographic - The cartographic coordinates.
  230. * @param {number} maximumScreenSpaceError The maximum screen-space error, in pixels, that is allowed.
  231. * A higher maximum error will render fewer tiles and improve performance, while a lower
  232. * value will improve visual quality.
  233. * @param {object} value - The object to be cached.
  234. */
  235. setPositionCacheEntry(cartographic, maximumScreenSpaceError, value) {
  236. this._positionCache.set(
  237. this._getCacheKey(cartographic, maximumScreenSpaceError),
  238. value,
  239. );
  240. }
  241. /**
  242. * Clears the position cache for this tile.
  243. * This function removes all cached positions that were previously stored
  244. * to optimize height computations.
  245. *
  246. */
  247. clearPositionCache() {
  248. if (this._positionCache.size > 0) {
  249. this._positionCache.clear();
  250. }
  251. }
  252. updateCustomData() {
  253. const added = this._addedCustomData;
  254. const removed = this._removedCustomData;
  255. if (added.length === 0 && removed.length === 0) {
  256. return;
  257. }
  258. const customData = this.customData;
  259. for (let i = 0; i < added.length; ++i) {
  260. const data = /** @type {*} */ (added[i]);
  261. customData.add(data);
  262. const child = childTileAtPosition(this, data.positionCartographic);
  263. child._addedCustomData.push(data);
  264. }
  265. this._addedCustomData.length = 0;
  266. for (let i = 0; i < removed.length; ++i) {
  267. const data = /** @type {*} */ (removed[i]);
  268. if (customData.has(data)) {
  269. customData.delete(data);
  270. }
  271. const child = childTileAtPosition(this, data.positionCartographic);
  272. child._removedCustomData.push(data);
  273. }
  274. this._removedCustomData.length = 0;
  275. }
  276. /**
  277. * Gets the tiling scheme used to tile the surface.
  278. * @type {TilingScheme}
  279. */
  280. get tilingScheme() {
  281. return this._tilingScheme;
  282. }
  283. /**
  284. * Gets the tile X coordinate.
  285. * @type {number}
  286. */
  287. get x() {
  288. return this._x;
  289. }
  290. /**
  291. * Gets the tile Y coordinate.
  292. * @type {number}
  293. */
  294. get y() {
  295. return this._y;
  296. }
  297. /**
  298. * Gets the level-of-detail, where zero is the coarsest, least-detailed.
  299. * @type {number}
  300. */
  301. get level() {
  302. return this._level;
  303. }
  304. /**
  305. * Gets the parent tile of this tile.
  306. * @type {QuadtreeTile}
  307. */
  308. get parent() {
  309. return this._parent;
  310. }
  311. /**
  312. * Gets the cartographic rectangle of the tile, with north, south, east and
  313. * west properties in radians.
  314. * @type {Rectangle}
  315. */
  316. get rectangle() {
  317. return this._rectangle;
  318. }
  319. /**
  320. * An array of tiles that is at the next level of the tile tree.
  321. * @type {QuadtreeTile[]}
  322. */
  323. get children() {
  324. return [
  325. this.northwestChild,
  326. this.northeastChild,
  327. this.southwestChild,
  328. this.southeastChild,
  329. ];
  330. }
  331. /**
  332. * Gets the southwest child tile.
  333. * @type {QuadtreeTile}
  334. */
  335. get southwestChild() {
  336. if (!defined(this._southwestChild)) {
  337. this._southwestChild = new QuadtreeTile({
  338. tilingScheme: this.tilingScheme,
  339. x: this.x * 2,
  340. y: this.y * 2 + 1,
  341. level: this.level + 1,
  342. parent: this,
  343. });
  344. }
  345. return this._southwestChild;
  346. }
  347. /**
  348. * Gets the southeast child tile.
  349. * @type {QuadtreeTile}
  350. */
  351. get southeastChild() {
  352. if (!defined(this._southeastChild)) {
  353. this._southeastChild = new QuadtreeTile({
  354. tilingScheme: this.tilingScheme,
  355. x: this.x * 2 + 1,
  356. y: this.y * 2 + 1,
  357. level: this.level + 1,
  358. parent: this,
  359. });
  360. }
  361. return this._southeastChild;
  362. }
  363. /**
  364. * Gets the northwest child tile.
  365. * @type {QuadtreeTile}
  366. */
  367. get northwestChild() {
  368. if (!defined(this._northwestChild)) {
  369. this._northwestChild = new QuadtreeTile({
  370. tilingScheme: this.tilingScheme,
  371. x: this.x * 2,
  372. y: this.y * 2,
  373. level: this.level + 1,
  374. parent: this,
  375. });
  376. }
  377. return this._northwestChild;
  378. }
  379. /**
  380. * Gets the northeast child tile.
  381. * @type {QuadtreeTile}
  382. */
  383. get northeastChild() {
  384. if (!defined(this._northeastChild)) {
  385. this._northeastChild = new QuadtreeTile({
  386. tilingScheme: this.tilingScheme,
  387. x: this.x * 2 + 1,
  388. y: this.y * 2,
  389. level: this.level + 1,
  390. parent: this,
  391. });
  392. }
  393. return this._northeastChild;
  394. }
  395. /**
  396. * A set of objects associated with this tile.
  397. * @type {Set<*>}
  398. */
  399. get customData() {
  400. return this._customData;
  401. }
  402. /**
  403. * Gets a value indicating whether or not this tile needs further loading.
  404. * This property will return true if the {@link QuadtreeTile#state} is
  405. * <code>START</code> or <code>LOADING</code>.
  406. * @type {boolean}
  407. */
  408. get needsLoading() {
  409. return this.state < QuadtreeTileLoadState.DONE;
  410. }
  411. /**
  412. * Gets a value indicating whether or not this tile is eligible to be unloaded.
  413. * Typically, a tile is ineligible to be unloaded while an asynchronous operation,
  414. * such as a request for data, is in progress on it. A tile will never be
  415. * unloaded while it is needed for rendering, regardless of the value of this
  416. * property. If {@link QuadtreeTile#data} is defined and has an
  417. * <code>eligibleForUnloading</code> property, the value of that property is returned.
  418. * Otherwise, this property returns true.
  419. * @type {boolean}
  420. */
  421. get eligibleForUnloading() {
  422. let result = true;
  423. if (defined(this.data)) {
  424. result = /** @type {*} */ (this.data).eligibleForUnloading;
  425. if (!defined(result)) {
  426. result = true;
  427. }
  428. }
  429. return result;
  430. }
  431. /**
  432. * @param {QuadtreeTile[]} levelZeroTiles
  433. * @param {number} x
  434. * @param {number} y
  435. * @returns {QuadtreeTile}
  436. */
  437. findLevelZeroTile(levelZeroTiles, x, y) {
  438. const xTiles = this.tilingScheme.getNumberOfXTilesAtLevel(0);
  439. if (x < 0) {
  440. x += xTiles;
  441. } else if (x >= xTiles) {
  442. x -= xTiles;
  443. }
  444. if (y < 0 || y >= this.tilingScheme.getNumberOfYTilesAtLevel(0)) {
  445. return undefined;
  446. }
  447. return levelZeroTiles.filter(function (tile) {
  448. return tile.x === x && tile.y === y;
  449. })[0];
  450. }
  451. /**
  452. * @param {QuadtreeTile[]} levelZeroTiles
  453. * @returns {QuadtreeTile|undefined}
  454. */
  455. findTileToWest(levelZeroTiles) {
  456. const parent = this.parent;
  457. if (parent === undefined) {
  458. return this.findLevelZeroTile(levelZeroTiles, this.x - 1, this.y);
  459. }
  460. if (parent.southeastChild === this) {
  461. return parent.southwestChild;
  462. } else if (parent.northeastChild === this) {
  463. return parent.northwestChild;
  464. }
  465. const westOfParent = parent.findTileToWest(levelZeroTiles);
  466. if (westOfParent === undefined) {
  467. return undefined;
  468. } else if (parent.southwestChild === this) {
  469. return westOfParent.southeastChild;
  470. }
  471. return westOfParent.northeastChild;
  472. }
  473. /**
  474. * @param {QuadtreeTile[]} levelZeroTiles
  475. * @returns {QuadtreeTile|undefined}
  476. */
  477. findTileToEast(levelZeroTiles) {
  478. const parent = this.parent;
  479. if (parent === undefined) {
  480. return this.findLevelZeroTile(levelZeroTiles, this.x + 1, this.y);
  481. }
  482. if (parent.southwestChild === this) {
  483. return parent.southeastChild;
  484. } else if (parent.northwestChild === this) {
  485. return parent.northeastChild;
  486. }
  487. const eastOfParent = parent.findTileToEast(levelZeroTiles);
  488. if (eastOfParent === undefined) {
  489. return undefined;
  490. } else if (parent.southeastChild === this) {
  491. return eastOfParent.southwestChild;
  492. }
  493. return eastOfParent.northwestChild;
  494. }
  495. /**
  496. * @param {QuadtreeTile[]} levelZeroTiles
  497. * @returns {QuadtreeTile|undefined}
  498. */
  499. findTileToSouth(levelZeroTiles) {
  500. const parent = this.parent;
  501. if (parent === undefined) {
  502. return this.findLevelZeroTile(levelZeroTiles, this.x, this.y + 1);
  503. }
  504. if (parent.northwestChild === this) {
  505. return parent.southwestChild;
  506. } else if (parent.northeastChild === this) {
  507. return parent.southeastChild;
  508. }
  509. const southOfParent = parent.findTileToSouth(levelZeroTiles);
  510. if (southOfParent === undefined) {
  511. return undefined;
  512. } else if (parent.southwestChild === this) {
  513. return southOfParent.northwestChild;
  514. }
  515. return southOfParent.northeastChild;
  516. }
  517. /**
  518. * @param {QuadtreeTile[]} levelZeroTiles
  519. * @returns {QuadtreeTile|undefined}
  520. */
  521. findTileToNorth(levelZeroTiles) {
  522. const parent = this.parent;
  523. if (parent === undefined) {
  524. return this.findLevelZeroTile(levelZeroTiles, this.x, this.y - 1);
  525. }
  526. if (parent.southwestChild === this) {
  527. return parent.northwestChild;
  528. } else if (parent.southeastChild === this) {
  529. return parent.northeastChild;
  530. }
  531. const northOfParent = parent.findTileToNorth(levelZeroTiles);
  532. if (northOfParent === undefined) {
  533. return undefined;
  534. } else if (parent.northwestChild === this) {
  535. return northOfParent.southwestChild;
  536. }
  537. return northOfParent.southeastChild;
  538. }
  539. /**
  540. * Frees the resources associated with this tile and returns it to the <code>START</code>
  541. * {@link QuadtreeTileLoadState}. If the {@link QuadtreeTile#data} property is defined and it
  542. * has a <code>freeResources</code> method, the method will be invoked.
  543. *
  544. */
  545. freeResources() {
  546. // Clears cached heights when the tile is freed
  547. this.clearPositionCache();
  548. this.state = QuadtreeTileLoadState.START;
  549. this.renderable = false;
  550. this.upsampledFromParent = false;
  551. const data = /** @type {*} */ (this.data);
  552. if (defined(data) && defined(data.freeResources)) {
  553. data.freeResources();
  554. }
  555. freeTile(this._southwestChild);
  556. this._southwestChild = undefined;
  557. freeTile(this._southeastChild);
  558. this._southeastChild = undefined;
  559. freeTile(this._northwestChild);
  560. this._northwestChild = undefined;
  561. freeTile(this._northeastChild);
  562. this._northeastChild = undefined;
  563. }
  564. }
  565. /**
  566. * Creates a spatial hash key for the given longitude, latitude, and tile level.
  567. * The precision is adjusted based on the tile level and extent to achieve finer precision at higher levels.
  568. *
  569. * This function calculates the spatial hash key by first determining the precision at the given tile for the current maximum screenspace error (MAX_ERROR_PX),
  570. * and then rounding the longitude and latitude to that precision for consistency.
  571. *
  572. * The steps for computing the level precision are as follows:
  573. *
  574. * 1. Compute the resolution (meters per pixel) at the given level:
  575. * level_resolution_m = (2 * PI * RADIUS) / (2^level * TILE_SIZE)
  576. *
  577. * 2. Compute the target precision in meters:
  578. * level_precision_m = level_resolution_m * MAX_ERROR_PX
  579. *
  580. * 3. Compute the target precision to radians:
  581. * level_precision_rad = level_precision_m / BODY_RADIUS
  582. *
  583. * This simplifies to:
  584. * level_precision_rad = (2 * PI * MAX_ERROR_PX) / (2^level * TILE_SIZE)
  585. * which can also be written as:
  586. * level_precision_rad = (PI * MAX_ERROR_PX) / (2^(level-1) * TILE_SIZE)
  587. *
  588. * The computed level_precision_rad is then used to round the input longitude and latitude,
  589. * ensuring that positions that fall within the same spatial bin produce the same hash key.
  590. *
  591. * The constants below are computed once since they are fixed for the given configuration.
  592. *
  593. * @param {number} longitude - The longitude in radians.
  594. * @param {number} latitude - The latitude in radians.
  595. * @param {Rectangle} rectangle - The quadtree tile extents.
  596. * @returns {string} A string representing the spatial hash key.
  597. */
  598. const TILE_SIZE = 256;
  599. /**
  600. * @param {number} longitude
  601. * @param {number} latitude
  602. * @param {Rectangle} rectangle
  603. * @param {number} maximumScreenSpaceError
  604. * @returns {string}
  605. * @ignore
  606. */
  607. function createSpatialHashKey(
  608. longitude,
  609. latitude,
  610. rectangle,
  611. maximumScreenSpaceError,
  612. ) {
  613. // Adjust precision based on quadtree level - higher levels get finer precision
  614. const maxError = (rectangle.width / TILE_SIZE) * maximumScreenSpaceError;
  615. // Round to the grid precision
  616. const lonGrid = Math.floor(longitude / maxError) * maxError;
  617. const latGrid = Math.floor(latitude / maxError) * maxError;
  618. return `${lonGrid.toFixed(10)},${latGrid.toFixed(10)}`;
  619. }
  620. const splitPointScratch = new Cartographic();
  621. /**
  622. * Determines which child tile that contains the specified position. Assumes the position is within
  623. * the bounds of the parent tile.
  624. * @private
  625. * @param {QuadtreeTile} tile - The parent tile.
  626. * @param {Cartographic} positionCartographic - The cartographic position.
  627. * @returns {QuadtreeTile} The child tile that contains the position.
  628. */
  629. function childTileAtPosition(tile, positionCartographic) {
  630. // Can't assume that a given tiling scheme divides a parent into four tiles at its rectangle's center.
  631. // But we can safely take any child tile's rectangle and take its center-facing corner as the parent's split point.
  632. const nwChildRectangle = tile.northwestChild.rectangle;
  633. const tileSplitPoint = Rectangle.southeast(
  634. nwChildRectangle,
  635. splitPointScratch,
  636. );
  637. const x = positionCartographic.longitude >= tileSplitPoint.longitude ? 1 : 0;
  638. const y = positionCartographic.latitude < tileSplitPoint.latitude ? 1 : 0;
  639. switch (y * 2 + x) {
  640. case 0:
  641. return tile.northwestChild;
  642. case 1:
  643. return tile.northeastChild;
  644. case 2:
  645. return tile.southwestChild;
  646. default:
  647. return tile.southeastChild;
  648. }
  649. }
  650. /**
  651. * @param {QuadtreeTile} tile
  652. * @ignore
  653. */
  654. function freeTile(tile) {
  655. if (defined(tile)) {
  656. tile.freeResources();
  657. }
  658. }
  659. export default QuadtreeTile;