import { ViewRanges } from "@here/harp-datasource-protocol/lib/ViewRanges" ; import { GeoCoordinates , Projection , ProjectionType , TileKey , TileKeyUtils , TilingScheme } from "@here/harp-geoutils" ; import { LRUCache } from "@here/harp-lrucache" ; import { assert , MathUtils , TaskQueue } from "@here/harp-utils" ; import * as THREE from "three" ; import { BackgroundDataSource } from "./BackgroundDataSource" ; import { ClipPlanesEvaluator } from "./ClipPlanesEvaluator" ; import { DataSource } from "./DataSource" ; import { ElevationRangeSource } from "./ElevationRangeSource" ; import { FrustumIntersection , TileKeyEntry } from "./FrustumIntersection" ; import { TileGeometryManager } from "./geometry/TileGeometryManager" ; import { TileTaskGroups } from "./MapView" ; import { Tile } from "./Tile" ; /**
 * Way the memory consumption of a tile is computed. Either in number of tiles, or in MegaBytes. If
 * it is in MB, an estimation is used.
 */ export enum ResourceComputationType { EstimationInMb = 0 , NumberOfTiles } enum SearchDirection { NONE , UP , DOWN , BOTH } /**
 * Limited set of  MapViewOptions used for  VisibleTileSet .
  export interface VisibleTileSetOptions { /**
     * The projection of the view.
     */ projection : Projection ; /**
     * User-defined camera clipping planes evaluator.
     */ clipPlanesEvaluator : ClipPlanesEvaluator ; /**
     * Limit of tiles that can be visible per datasource.
     */ maxVisibleDataSourceTiles : number ; /**
     * In addition to the simple frustum culling also do additional checks with [[MapTileCuller]].
     */ extendedFrustumCulling : boolean ; /**
     * Missing Typedoc
     */ tileCacheSize : number ; /**
     * Missing Typedoc
     */ resourceComputationType : ResourceComputationType ; /**
     * Number of levels to go up when searching for fallback tiles.
     */ quadTreeSearchDistanceUp : number ; /**
     * Number of levels to go down when searching for fallback tiles.
     */ quadTreeSearchDistanceDown : number ; beta internal defaultValue maxTilesPerFrame : number ; } const MB_FACTOR = 1.0 / ( 1024.0 * 1024.0 ) ; type TileCacheId = string ; /**
 * Wrapper for LRU cache that encapsulates tiles caching for any  DataSource used.
 *
 * Provides LRU based caching mechanism where each tile is identified by its tile key
 * (morton code) and data source name.
 * Tiles are kept in the cache based on last recently used policy, cached tile may be evicted
 * only when cache reaches full saturation and tile is no longer visible.
 *  note Currently cached entries (tiles) are identified by unique tile code (morton code) and
 * data source name, thus it is required that each  DataSource used should have unique
 * name, but implementation could be improved to omit this limitation.
  class DataSourceCache { param mortonCode param offset param dataSource - The  DataSource from which tile was loaded.
      static getKey ( mortonCode , offset , dataSource ) { return `${ dataSource . name }_${ mortonCode }_${ offset }` ; } param tile static getKeyForTile ( tile ) { return DataSourceCache . getKey ( tile . tileKey . mortonCode ( ) , tile . offset , tile . dataSource ) ; } private readonly m_tileCache : LRUCache < TileCacheId , Tile > ; private readonly m_disposedTiles : Tile [ ] = [ ] ; private m_resourceComputationType : ResourceComputationType ; constructor ( cacheSize , rct = ResourceComputationType . EstimationInMb ) { this . m_resourceComputationType = rct ; this . m_tileCache = new LRUCache < string , Tile > ( cacheSize , ( tile ) => { if ( this . m_resourceComputationType === ResourceComputationType . EstimationInMb ) { return tile . memoryUsage * MB_FACTOR ; } else { return 1 ; } } ) ; this . m_tileCache . evictionCallback = ( _ , tile ) => { if ( tile . tileLoader !== undefined ) { tile . tileLoader . cancel ( ) ; } this . m_disposedTiles . push ( tile ) ; } ; this . m_tileCache . canEvict = ( _ , tile ) => { return ! tile . isVisible ; } ; } return get resourceComputationType ( ) : ResourceComputationType { return this . m_resourceComputationType ; } see size . see resourceComputationType . get capacity ( ) : number { return this . m_tileCache . capacity ; } see capacity . see resourceComputationType . get size ( ) : number { return this . m_tileCache . size ; } see capacity . see resourceComputationType . param size param rct setCapacity ( size , rct ) { this . m_resourceComputationType = rct ; this . m_tileCache . setCapacityAndMeasure ( size , ( tile ) => { if ( this . m_resourceComputationType === ResourceComputationType . EstimationInMb ) { return tile . memoryUsage * MB_FACTOR ; } else { return 1 ; } } ) ; } param mortonCode param offset param dataSource - A  DataSource the tile comes from.
      get ( mortonCode , offset , dataSource ) { return this . m_tileCache . get ( DataSourceCache . getKey ( mortonCode , offset , dataSource ) ) ; } param mortonCode param offset param dataSource - A  DataSource the tile comes from.
     *  param tile set ( mortonCode , offset , dataSource , tile ) { this . m_tileCache . set ( DataSourceCache . getKey ( mortonCode , offset , dataSource ) , tile ) ; } note param tile delete ( tile ) { const tileKey = DataSourceCache . getKeyForTile ( tile ) ; this . deleteByKey ( tileKey ) ; } note Tile identifier its constructed using information about tile code (morton code) and its
     *  DataSource .
     *  note see DataSourceCache . getKey . param tileKey deleteByKey ( tileKey ) { this . m_tileCache . delete ( tileKey ) ; } /**
     * Dispose all tiles releasing their internal data.
     */ disposeTiles ( ) { this . m_disposedTiles . forEach ( tile => { tile . dispose ( ) ; } ) ; this . m_disposedTiles . length = 0 ; } /**
     * Shrink cache to its allowed capacity.
     *
     * This method should be called each time after operations are performed on the cache entries,
     * in order to keep cache size consistent. It informs caching mechanism to invalidate memory
     * consumed by its entries and check if cache is overgrown, is such case some tiles will be
     * evicted.
     */ shrinkToCapacity ( ) { this . m_tileCache . shrinkToCapacity ( ) ; } /**
     * Evict all cached tiles implicitly even without checking if still in use.
     */ evictAll ( ) { this . m_tileCache . evictAll ( ) ; } param selector evictSelected ( selector ) { this . m_tileCache . evictSelected ( selector ) ; } /**
     * Call functor (callback) on each tile store in cache.
     *
     * Optionally you may specify from which  DataSource tiles should be processed.
     * This limits the tiles visited to a sub-set originating from single  DataSource .
     *  param callback param inDataSource - The optional  DataSource to which tiles should belong.
      forEach ( callback , inDataSource ? ) { this . m_tileCache . forEach ( ( entry , key ) => { if ( inDataSource === undefined || entry . dataSource === inDataSource ) { callback ( entry , key ) ; } } ) ; } } /**
 * List of visible tiles for a  DataSource .
  export interface DataSourceTileList { /**
     * The datasource that was producing the tiles.
     */ dataSource : DataSource ; /**
     * The current  MapView zoom level.
      zoomLevel : number ; /**
     * The storage level of the visibleTiles.
     * Note: renderedTiles might contain tiles from different levels.
     */ storageLevel : number ; /**
     * True if all [[visibleTiles]] are loaded.
     */ allVisibleTileLoaded : boolean ; /**
     * The number of tiles which are still loading.
     */ numTilesLoading : number ; /**
     * List of tiles we want to render (i.e. the tiles computed from the zoom level and view
     * frustum). However some might not be renderable yet (e.g. loading). See [[renderedTiles]] for
     * the actual list of tiles that the user will see.
     */ visibleTiles : Tile [ ] ; /**
     * Map of tiles that will be rendered, key is the the combination of tile key and offset, see
     * [[getKeyForTileKeyAndOffset]]. This includes tiles that are not in the [[visibleTiles]]
     * list but that are used as fallbacks b/c they are still in the cache.
     */ renderedTiles : Map < number , Tile > ; } const compareDistances = ( a , b ) => { const distanceDiff = a . distance - b . distance ; const minDiff = ( a . distance + b . distance ) * 0.000001 ; return Math . abs ( distanceDiff ) < minDiff ? a . tileKey . mortonCode ( ) - b . tileKey . mortonCode ( ) : distanceDiff ; } ; /**
 * Manages visible  Tile s for  MapView .
 *
 * Responsible for election of rendered tiles:
 *  - quad-tree traversal
 *  - frustum culling
 *  - sorting tiles by relevance (visible area) to prioritize load
 *  - limiting number of visible tiles
 *  - caching tiles
 *  - searching cache to replace visible but yet empty tiles with already loaded siblings in nearby
 *    zoom levels
  export class VisibleTileSet { dataSourceTileList : DataSourceTileList [ ] = [ ] ; allVisibleTilesLoaded : boolean = false ; private readonly m_cameraOverride = new THREE . PerspectiveCamera ( ) ; private readonly m_dataSourceCache : DataSourceCache ; private m_viewRange : ViewRanges = { near : 0.1 , far : Infinity , minimum : 0.1 , maximum : Infinity } ; private readonly m_coveringMap = new Map < number , Tile > ( ) ; private m_resourceComputationType : ResourceComputationType = ResourceComputationType . EstimationInMb ; constructor ( private readonly m_frustumIntersection , private readonly m_tileGeometryManager , public options , private readonly m_taskQueue ) { this . options = options ; this . options . maxTilesPerFrame = Math . floor ( this . options . maxTilesPerFrame ?? 0 ) ; this . m_resourceComputationType = options . resourceComputationType === undefined ? ResourceComputationType . EstimationInMb : options . resourceComputationType ; this . m_dataSourceCache = new DataSourceCache ( this . options . tileCacheSize , this . m_resourceComputationType ) ; } /**
     * Returns cache size.
     */ getDataSourceCacheSize ( ) { return this . options . tileCacheSize ; } param size param computationType - Optional value specifying the way a  Tile s cache usage is
     *      computed, either based on size in MB (mega bytes) or in number of tiles. Defaults to
     *      `ResourceComputationType.EstimationInMb`.
      setDataSourceCacheSize ( size , computationType = ResourceComputationType . EstimationInMb ) { this . options . tileCacheSize = size ; this . resourceComputationType = computationType ; } /**
     * Retrieves maximum number of visible tiles.
     */ getNumberOfVisibleTiles ( ) { return this . options . maxVisibleDataSourceTiles ; } param size setNumberOfVisibleTiles ( size ) { this . options . maxVisibleDataSourceTiles = size ; } beta internal get maxTilesPerFrame ( ) : number { return this . options . maxTilesPerFrame ; } beta internal param value set maxTilesPerFrame ( value ) { if ( value < 0 ) { throw new Error ( "Invalid value, this will result in no tiles ever showing" ) ; } this . options . maxTilesPerFrame = Math . floor ( value ) ; } /**
     * The way the cache usage is computed, either based on size in MB (mega bytes) or in number of
     * tiles.
     */ get resourceComputationType ( ) : ResourceComputationType { return this . m_resourceComputationType ; } param computationType set resourceComputationType ( computationType ) { this . m_resourceComputationType = computationType ; this . m_dataSourceCache . setCapacity ( this . options . tileCacheSize , computationType ) ; } /**
     * Evaluate frustum near/far clip planes and visibility ranges.
     */ updateClipPlanes ( maxElevation ? , minElevation ? ) { if ( maxElevation !== undefined ) { this . options . clipPlanesEvaluator . maxElevation = maxElevation ; } if ( minElevation !== undefined ) { this . options . clipPlanesEvaluator . minElevation = minElevation ; } const { camera , projection , elevationProvider } = this . m_frustumIntersection . mapView ; this . m_viewRange = this . options . clipPlanesEvaluator . evaluateClipPlanes ( camera , projection , elevationProvider ) ; return this . m_viewRange ; } param storageLevel - The camera storage level, see  MapView . storageLevel .
     *  param zoomLevel param dataSources param elevationRangeSource returns updateRenderList ( storageLevel , zoomLevel , dataSources , frameNumber , elevationRangeSource ? ) { let allVisibleTilesLoaded = true ; const newTilesPerFrame = 0 ; const visibleTileKeysResult = this . getVisibleTileKeysForDataSources ( zoomLevel , dataSources , elevationRangeSource ) ; this . dataSourceTileList = [ ] ; this . m_coveringMap . clear ( ) ; for ( const { dataSource , visibleTileKeys } of visibleTileKeysResult . tileKeys ) { visibleTileKeys . sort ( compareDistances ) ; const dataZoomLevel = dataSource . getDataZoomLevel ( zoomLevel ) ; const visibleResult = this . processVisibleTiles ( visibleTileKeys , dataSource , frameNumber , { newTilesPerFrame } , true ) ; const dependentResult = this . processVisibleTiles ( visibleResult . dependentTiles , dataSource , frameNumber , { newTilesPerFrame } , false ) ; this . m_tileGeometryManager . updateTiles ( visibleResult . visibleTiles ) ; this . m_tileGeometryManager . updateTiles ( dependentResult . visibleTiles ) ; this . dataSourceTileList . push ( { dataSource , storageLevel , zoomLevel : dataZoomLevel , allVisibleTileLoaded : visibleResult . allDataSourceTilesLoaded && dependentResult . allDataSourceTilesLoaded , numTilesLoading : visibleResult . numTilesLoading + dependentResult . numTilesLoading , visibleTiles : [ ... visibleResult . visibleTiles , ... dependentResult . visibleTiles ] , renderedTiles : new Map < number , Tile > ( ) } ) ; allVisibleTilesLoaded = allVisibleTilesLoaded && visibleResult . allDataSourceTilesLoaded ; } this . allVisibleTilesLoaded = allVisibleTilesLoaded && visibleTileKeysResult . allBoundingBoxesFinal ; this . populateRenderedTiles ( ) ; this . forEachCachedTile ( tile => { if ( ! tile . isVisible && ! tile . allGeometryLoaded ) { this . disposeTile ( tile ) ; } } ) ; this . m_dataSourceCache . shrinkToCapacity ( ) ; let minElevation ; let maxElevation ; this . dataSourceTileList . forEach ( renderListEntry => { const tiles = renderListEntry . renderedTiles ; tiles . forEach ( tile => { tile . update ( renderListEntry . zoomLevel ) ; minElevation = MathUtils . min2 ( minElevation , tile . geoBox . minAltitude ) ; maxElevation = MathUtils . max2 ( maxElevation , tile . geoBox . maxAltitude ) ; } ) ; } ) ; if ( minElevation === undefined ) { minElevation = 0 ; } if ( maxElevation === undefined ) { maxElevation = 0 ; } let viewRangesChanged = false ; const oldViewRanges = this . m_viewRange ; const newViewRanges = this . updateClipPlanes ( maxElevation , minElevation ) ; viewRangesChanged = viewRangesEqual ( newViewRanges , oldViewRanges ) === false ; return { viewRanges : newViewRanges , viewRangesChanged } ; } param dataSource param tileKey param offset param frameNumber return getTile ( dataSource , tileKey , offset , frameNumber ) { const cacheOnly = false ; return this . getTileImpl ( dataSource , tileKey , offset , cacheOnly , frameNumber ) ; } param dataSource param tileKey param offset param frameNumber return getCachedTile ( dataSource , tileKey , offset , frameNumber ) { assert ( dataSource . cacheable ) ; const cacheOnly = true ; return this . getTileImpl ( dataSource , tileKey , offset , cacheOnly , frameNumber ) ; } param dataSource param tileKey param offset return getRenderedTile ( dataSource , tileKey , offset = 0 ) { const dataSourceVisibleTileList = this . dataSourceTileList . find ( list => { return list . dataSource === dataSource ; } ) ; if ( dataSourceVisibleTileList === undefined ) { return undefined ; } return dataSourceVisibleTileList . renderedTiles . get ( TileKeyUtils . getKeyForTileKeyAndOffset ( tileKey , offset ) ) ; } param dataSource param geoPoint return getRenderedTileAtLocation ( dataSource , geoPoint , offset = 0 ) { const dataSourceVisibleTileList = this . dataSourceTileList . find ( list => { return list . dataSource === dataSource ; } ) ; if ( dataSourceVisibleTileList === undefined ) { return undefined ; } const tilingScheme = dataSource . getTilingScheme ( ) ; const visibleLevel = dataSourceVisibleTileList . zoomLevel ; const visibleTileKey = tilingScheme . getTileKey ( geoPoint , visibleLevel ) ; if ( ! visibleTileKey ) { return undefined ; } let tile = dataSourceVisibleTileList . renderedTiles . get ( TileKeyUtils . getKeyForTileKeyAndOffset ( visibleTileKey , offset ) ) ; if ( tile !== undefined ) { return tile ; } const { searchLevelsUp , searchLevelsDown } = this . getSearchDirection ( dataSource , visibleLevel ) ; let parentTileKey = visibleTileKey ; for ( let levelOffset = 1 ; levelOffset <= searchLevelsUp ; ++ levelOffset ) { parentTileKey = parentTileKey . parent ( ) ; tile = dataSourceVisibleTileList . renderedTiles . get ( TileKeyUtils . getKeyForTileKeyAndOffset ( parentTileKey , offset ) ) ; if ( tile !== undefined ) { return tile ; } } const worldPoint = tilingScheme . projection . projectPoint ( geoPoint ) ; for ( let levelOffset = 1 ; levelOffset <= searchLevelsDown ; ++ levelOffset ) { const childLevel = visibleLevel + levelOffset ; const childTileKey = TileKeyUtils . worldCoordinatesToTileKey ( tilingScheme , worldPoint , childLevel ) ; if ( childTileKey ) { tile = dataSourceVisibleTileList . renderedTiles . get ( TileKeyUtils . getKeyForTileKeyAndOffset ( childTileKey , offset ) ) ; if ( tile !== undefined ) { return tile ; } } } return undefined ; } /**
     * Removes all internal bookkeeping entries and cache related to specified datasource.
     *
     * Called by  MapView when  DataSource has been removed from  MapView .
      removeDataSource ( dataSource ) { this . clearTileCache ( dataSource ) ; this . dataSourceTileList = this . dataSourceTileList . filter ( tileList => tileList . dataSource !== dataSource ) ; } /**
     * Clear the tile cache.
     *
     * Remove the  Tile objects created by cacheable  DataSource .
     * If a  DataSource name is
     * provided, this method restricts the eviction
     * the  DataSource with the given name.
     *
     *  param dataSourceName - The name of the  DataSource .
     *  param filter clearTileCache ( dataSource ? , filter ? ) { if ( dataSource !== undefined ) { this . m_dataSourceCache . evictSelected ( ( tile , _ ) => { return ( tile . dataSource === dataSource && ( filter !== undefined ? filter ( tile ) : true ) ) ; } ) ; } else if ( filter !== undefined ) { this . m_dataSourceCache . evictSelected ( filter ) ; } else { this . m_dataSourceCache . evictAll ( ) ; } } param dataSource - If passed, only the tiles from this  DataSource instance
     *      are processed. If `undefined`, tiles from all  DataSource s are processed.
     *  param filter markTilesDirty ( dataSource ? , filter ? ) { if ( dataSource === undefined ) { this . dataSourceTileList . forEach ( renderListEntry => { this . markDataSourceTilesDirty ( renderListEntry , filter ) ; } ) ; } else { const renderListEntry = this . dataSourceTileList . find ( e => e . dataSource === dataSource ) ; if ( renderListEntry === undefined ) { return ; } this . markDataSourceTilesDirty ( renderListEntry , filter ) ; } } /**
     * Dispose tiles that are marked for removal by  {@link @here/harp-lrucache#LRUCache} algorithm.
      disposePendingTiles ( ) { this . m_dataSourceCache . disposeTiles ( ) ; } param fun forEachVisibleTile ( fun ) { for ( const listEntry of this . dataSourceTileList ) { listEntry . renderedTiles . forEach ( fun ) ; } } param fun param dataSource forEachCachedTile ( fun , dataSource ? ) { this . m_dataSourceCache . forEach ( ( tile , _ ) => fun ( tile ) , dataSource ) ; } /**
     * Dispose a `Tile` from cache, 'dispose()' is also called on the tile to free its resources.
     */ disposeTile ( tile ) { this . m_dataSourceCache . delete ( tile ) ; tile . dispose ( ) ; } private processVisibleTiles ( visibleTileKeys , dataSource , frameNumber , refs , processDependentTiles ) { let allDataSourceTilesLoaded = true ; let numTilesLoading = 0 ; const visibleTiles = [ ] ; const dependentTiles = [ ] ; for ( let i = 0 ; i < visibleTileKeys . length && visibleTiles . length < this . options . maxVisibleDataSourceTiles ; i ++ ) { const tileEntry = visibleTileKeys [ i ] ; const tile = this . getTile ( dataSource , tileEntry . tileKey , tileEntry . offset , frameNumber ) ; if ( tile === undefined ) { continue ; } visibleTiles . push ( tile ) ; allDataSourceTilesLoaded = allDataSourceTilesLoaded && tile . allGeometryLoaded ; if ( ! tile . allGeometryLoaded ) { numTilesLoading ++ ; } else { this . skipOverlappedTiles ( dataSource , tile ) ; if ( this . processDelayTileRendering ( tile , refs . newTilesPerFrame , frameNumber ) ) { refs . newTilesPerFrame ++ ; } } tile . visibleArea = tileEntry . area ; tile . elevationRange = tileEntry . elevationRange ?? { minElevation : 0 , maxElevation : 0 } ; if ( processDependentTiles ) { for ( const tileKey of tile . dependencies ) { if ( visibleTileKeys . find ( tileKeyEntry => tileKeyEntry . tileKey . mortonCode ( ) === tileKey . mortonCode ( ) ) === undefined && dependentTiles . find ( tileKeyEntry => tileKeyEntry . tileKey . mortonCode ( ) === tileKey . mortonCode ( ) ) === undefined ) { dependentTiles . push ( new TileKeyEntry ( tileKey , 0 ) ) ; } } } } return { allDataSourceTilesLoaded , numTilesLoading , visibleTiles , dependentTiles } ; } private processDelayTileRendering ( tile , newTilesPerFrame , frameNumber ) { let isNewTile = false ; if ( this . options . maxTilesPerFrame !== 0 && newTilesPerFrame > this . options . maxTilesPerFrame && ! ( tile . frameNumLastVisible === frameNumber - 1 ) ) { tile . delayRendering = true ; tile . mapView . update ( ) ; } else { if ( tile . frameNumVisible < 0 ) { tile . frameNumVisible = frameNumber ; isNewTile = true ; } tile . numFramesVisible ++ ; tile . delayRendering = false ; } return isNewTile ; } /**
     * Skips rendering of tiles that are overlapped. The overlapping  Tile comes from a
     *  DataSource which is fully covering, i.e. there it is fully opaque.
     * private skipOverlappedTiles ( dataSource , tile ) { if ( this . options . projection . type === ProjectionType . Spherical ) { return ; } if ( dataSource . isFullyCovering ( ) ) { const key = tile . uniqueKey ; const entry = this . m_coveringMap . get ( key ) ; if ( entry === undefined ) { tile . skipRendering = false ; this . m_coveringMap . set ( key , tile ) ; } else { if ( entry . dataSource instanceof BackgroundDataSource ) { entry . skipRendering = true ; } else if ( dataSource instanceof BackgroundDataSource ) { tile . skipRendering = true ; } } } } private getSearchDirection ( dataSource , visibleLevel ) { const searchLevelsUp = Math . min ( this . options . quadTreeSearchDistanceUp , Math . max ( 0 , visibleLevel - dataSource . minDataLevel ) ) ; const searchLevelsDown = Math . min ( this . options . quadTreeSearchDistanceDown , Math . max ( 0 , dataSource . maxDataLevel - visibleLevel ) ) ; const searchDirection = searchLevelsDown > 0 && searchLevelsUp > 0 ? SearchDirection . BOTH : searchLevelsDown > 0 ? SearchDirection . DOWN : searchLevelsUp > 0 ? SearchDirection . UP : SearchDirection . NONE ; return { searchDirection , searchLevelsUp , searchLevelsDown } ; } /**
     * Populates the list of tiles to render, see "renderedTiles". Tiles that are loaded and which
     * are an exact match are added straight to the list, tiles that are still loading are replaced
     * with tiles in the cache that are either a parent or child of the requested tile. This helps
     * to prevent flickering when zooming in / out. The distance to search is based on the options
     * [[quadTreeSearchDistanceDown]] and [[quadTreeSearchDistanceUp]].
     *
     * Each  DataSource can also switch this behaviour on / off using the
     * [[allowOverlappingTiles]] flag.
     *
      private populateRenderedTiles ( ) { this . dataSourceTileList . forEach ( renderListEntry => { const renderedTiles = renderListEntry . renderedTiles ; const incompleteTiles = [ ] ; renderListEntry . visibleTiles . forEach ( tile => { tile . levelOffset = 0 ; if ( tile . hasGeometry && ! tile . delayRendering ) { renderedTiles . set ( tile . uniqueKey , tile ) ; } else { incompleteTiles . push ( tile . uniqueKey ) ; } } ) ; const dataSource = renderListEntry . dataSource ; if ( incompleteTiles . length === 0 || dataSource . allowOverlappingTiles === false ) { return ; } const dataZoomLevel = renderListEntry . zoomLevel ; const { searchDirection } = this . getSearchDirection ( dataSource , dataZoomLevel ) ; const checkedTiles = new Map < number , boolean > ( ) ; for ( const tileKeyCode of incompleteTiles ) { if ( searchDirection === SearchDirection . BOTH || searchDirection === SearchDirection . UP ) { if ( this . findUp ( tileKeyCode , dataZoomLevel , renderedTiles , checkedTiles , dataSource ) ) { continue ; } } if ( searchDirection === SearchDirection . BOTH || searchDirection === SearchDirection . DOWN ) { this . findDown ( tileKeyCode , dataZoomLevel , renderedTiles , dataSource ) ; } } } ) ; } private findDown ( tileKeyCode , dataZoomLevel , renderedTiles , dataSource ) { const { offset , mortonCode } = TileKeyUtils . extractOffsetAndMortonKeyFromKey ( tileKeyCode ) ; const tileKey = TileKey . fromMortonCode ( mortonCode ) ; const tilingScheme = dataSource . getTilingScheme ( ) ; for ( const childTileKey of tilingScheme . getSubTileKeys ( tileKey ) ) { const childTileCode = TileKeyUtils . getKeyForTileKeyAndOffset ( childTileKey , offset ) ; const childTile = this . m_dataSourceCache . get ( childTileKey . mortonCode ( ) , offset , dataSource ) ; const nextLevelDiff = Math . abs ( childTileKey . level - dataZoomLevel ) ; if ( childTile !== undefined && childTile . hasGeometry && ! childTile . delayRendering ) { renderedTiles . set ( childTileCode , childTile ) ; childTile . levelOffset = nextLevelDiff ; continue ; } if ( nextLevelDiff < this . options . quadTreeSearchDistanceDown ) { this . findDown ( childTileCode , dataZoomLevel , renderedTiles , dataSource ) ; } } } param tileKeyCode param dataZoomLevel param renderedTiles param checkedTiles param dataSource returns private findUp ( tileKeyCode , dataZoomLevel , renderedTiles , checkedTiles , dataSource ) { const parentCode = TileKeyUtils . getParentKeyFromKey ( tileKeyCode ) ; if ( renderedTiles . get ( parentCode ) !== undefined ) { return true ; } const exists = checkedTiles . get ( parentCode ) ! ; if ( exists !== undefined ) { return exists ; } const { offset , mortonCode } = TileKeyUtils . extractOffsetAndMortonKeyFromKey ( parentCode ) ; const parentTile = this . m_dataSourceCache . get ( mortonCode , offset , dataSource ) ; const parentTileKey = parentTile ? parentTile . tileKey : TileKey . fromMortonCode ( mortonCode ) ; const nextLevelDiff = Math . abs ( dataZoomLevel - parentTileKey . level ) ; if ( parentTile !== undefined && parentTile . hasGeometry && ! parentTile . delayRendering ) { checkedTiles . set ( parentCode , true ) ; renderedTiles . set ( parentCode , parentTile ) ; parentTile . levelOffset = - nextLevelDiff ; return true ; } else { checkedTiles . set ( parentCode , false ) ; } if ( nextLevelDiff < this . options . quadTreeSearchDistanceUp && parentTileKey . level !== 0 ) { const foundUp = this . findUp ( parentCode , dataZoomLevel , renderedTiles , checkedTiles , dataSource ) ; checkedTiles . set ( parentCode , foundUp ) ; if ( foundUp ) { return true ; } } return false ; } private getTileImpl ( dataSource , tileKey , offset , cacheOnly , frameNumber ) { function touchTile ( tileToUpdate ) { tileToUpdate . frameNumLastRequested = frameNumber ; } if ( ! dataSource . cacheable && ! cacheOnly ) { const resultTile = dataSource . getTile ( tileKey , true ) ; if ( resultTile !== undefined ) { this . addToTaskQueue ( resultTile ) ; touchTile ( resultTile ) ; } return resultTile ; } const tileCache = this . m_dataSourceCache ; let tile = tileCache . get ( tileKey . mortonCode ( ) , offset , dataSource ) ; if ( tile !== undefined && tile . offset === offset ) { touchTile ( tile ) ; return tile ; } if ( cacheOnly ) { return undefined ; } tile = dataSource . getTile ( tileKey , true ) ; if ( tile !== undefined ) { this . addToTaskQueue ( tile ) ; tile . offset = offset ; touchTile ( tile ) ; tileCache . set ( tileKey . mortonCode ( ) , offset , dataSource , tile ) ; } return tile ; } private addToTaskQueue ( tile ) { this . m_taskQueue . add ( { execute : tile . load . bind ( tile ) , group : TileTaskGroups . FETCH_AND_DECODE , getPriority : ( ) => { return tile ?. tileLoader ?. priority ?? 0 ; } , isExpired : ( ) => { return ! tile ?. isVisible ; } , estimatedProcessTime : ( ) => { return 1 ; } } ) ; } private markDataSourceTilesDirty ( renderListEntry , filter ? ) { const dataSourceCache = this . m_dataSourceCache ; const retainedTiles = new Set ( ) ; const markTileDirty = ( tile ) => { const tileKey = DataSourceCache . getKeyForTile ( tile ) ; if ( ! retainedTiles . has ( tileKey ) ) { retainedTiles . add ( tileKey ) ; tile . tileLoader ?. cancel ( ) ; this . addToTaskQueue ( tile ) ; } } ; renderListEntry . visibleTiles . forEach ( tile => { if ( filter === undefined || filter ( tile ) ) { markTileDirty ( tile ) ; } } ) ; renderListEntry . renderedTiles . forEach ( tile => { if ( filter === undefined || filter ( tile ) ) { markTileDirty ( tile ) ; } } ) ; dataSourceCache . forEach ( ( tile , key ) => { if ( ( filter === undefined || filter ( tile ) ) && ! retainedTiles . has ( key ) ) { dataSourceCache . deleteByKey ( key ) ; tile . dispose ( ) ; } } , renderListEntry . dataSource ) ; } private getVisibleTileKeysForDataSources ( zoomLevel , dataSources , elevationRangeSource ) { const tileKeys = Array < { dataSource : DataSource ; visibleTileKeys : TileKeyEntry [ ] ; } > ( ) ; let allBoundingBoxesFinal = true ; if ( dataSources . length === 0 ) { return { tileKeys , allBoundingBoxesFinal } ; } const dataSourceBuckets = new Map < TilingScheme , DataSource [ ] > ( ) ; dataSources . forEach ( dataSource => { const tilingScheme = dataSource . getTilingScheme ( ) ; const bucket = dataSourceBuckets . get ( tilingScheme ) ; if ( bucket === undefined ) { dataSourceBuckets . set ( tilingScheme , [ dataSource ] ) ; } else { bucket . push ( dataSource ) ; } } ) ; if ( elevationRangeSource !== undefined ) { this . m_cameraOverride . copy ( this . m_frustumIntersection . camera ) ; this . m_cameraOverride . near = Math . min ( this . m_cameraOverride . near , this . m_viewRange . minimum ) ; this . m_cameraOverride . far = Math . max ( this . m_cameraOverride . far , this . m_viewRange . maximum ) ; this . m_cameraOverride . updateProjectionMatrix ( ) ; this . m_frustumIntersection . updateFrustum ( this . m_cameraOverride . projectionMatrix ) ; } else { this . m_frustumIntersection . updateFrustum ( ) ; } for ( const [ tilingScheme , bucket ] of dataSourceBuckets ) { const zoomLevels = bucket . map ( dataSource => dataSource . getDataZoomLevel ( zoomLevel ) ) ; const result = this . m_frustumIntersection . compute ( tilingScheme , elevationRangeSource , zoomLevels , bucket ) ; allBoundingBoxesFinal = allBoundingBoxesFinal && result . calculationFinal ; for ( const dataSource of bucket ) { const visibleTileKeys = [ ] ; const dataZoomLevel = dataSource . getDataZoomLevel ( zoomLevel ) ; for ( const tileKeyEntry of result . tileKeyEntries . get ( dataZoomLevel ) ! . values ( ) ) { if ( dataSource . canGetTile ( dataZoomLevel , tileKeyEntry . tileKey ) ) { visibleTileKeys . push ( tileKeyEntry ) ; } } tileKeys . push ( { dataSource , visibleTileKeys } ) ; } } return { tileKeys , allBoundingBoxesFinal } ; } } function viewRangesEqual ( a , b ) { return ( a . far === b . far && a . maximum === b . maximum && a . minimum === b . minimum && a . near === b . near ) ; }