import { TileKey } from "@here/harp-geoutils" ; import { assert , LoggerManager , LogLevel } from "@here/harp-utils" ; import { TextElement } from "./TextElement" ; import { TextElementGroup } from "./TextElementGroup" ; import { TextElementFilter , TextElementGroupState } from "./TextElementGroupState" ; import { isLineMarkerElementState , TextElementState } from "./TextElementState" ; import { TextElementType } from "./TextElementType" ; const logger = LoggerManager . instance . create ( "TextElementsStateCache" , { level : LogLevel . Log } ) ; /**
 * Label distance tolerance squared in meters. Point labels with the same name that are closer in
 * world space than this value are treated as the same label. Used to identify duplicate labels in
 * overlapping tiles and label replacements at different storage levels.
 */ function getDedupSqDistTolerance ( zoomLevel ) { const minSqTol = 100 ; const minSqTolLevel = 13 ; const maxLevelDelta = 4 ; const levelDelta = Math . min ( maxLevelDelta , minSqTolLevel - Math . min ( minSqTolLevel , Math . floor ( zoomLevel ) ) ) ; return minSqTol << ( levelDelta << 2 ) ; } const tmpCachedDuplicate = { entries : [ ] , index : - 1 } ; function getCacheKey ( element ) { return element . hasFeatureId ( ) ? element . featureId ! : element . text ; } param elementState param candidates returns function findDuplicateById ( elementState , candidates ) { const element = elementState . element ; const duplicateIndex = candidates . findIndex ( entry => entry . element . tileOffset === element . tileOffset ) ; if ( duplicateIndex === - 1 ) { return - 1 ; } const candidateElement = candidates [ duplicateIndex ] ; const candidate = candidateElement . element ; assert ( element . featureId === candidate . featureId ) ; if ( candidate . text !== element . text ) { logger . debug ( `Text feature id ${ element . featureId } collision between "${ element . text } and \
             ${ candidate . text }` ) ; return undefined ; } return duplicateIndex ; } type DuplicateCmp = ( newCandidate : TextElement , newDistance : number , oldCandidate : TextElement , oldDistance : number ) => boolean ; function isBetterPathDuplicate ( newCandidate , _newDistance , oldCandidate , _oldDistance ) { if ( newCandidate . pathLengthSqr === undefined ) { return false ; } if ( oldCandidate . pathLengthSqr === undefined ) { return false ; } return newCandidate . pathLengthSqr > oldCandidate . pathLengthSqr ; } function isBetterPointDuplicate ( _newCandidate , newDistance , _oldCandidate , oldDistance ) { return newDistance < oldDistance ; } param elementState param candidates param zoomLevel returns function findDuplicateByText ( elementState , candidates , zoomLevel ) { const element = elementState . element ; const maxSqDistError = getDedupSqDistTolerance ( zoomLevel ) ; const entryCount = candidates . length ; const elementPosition = elementState . position ; const elementVisible = elementState . visible ; const isLineMarker = isLineMarkerElementState ( elementState ) ; let dupIndex = - 1 ; let duplicate ; let dupDistSquared = Infinity ; const isBetterDuplicate = element . type === TextElementType . PathLabel ? isBetterPathDuplicate : isBetterPointDuplicate ; for ( let i = 0 ; i < entryCount ; ++ i ) { const candidateEntry = candidates [ i ] ; const cachedElement = candidateEntry . element ; const areDiffType = element . type !== cachedElement . type || isLineMarker !== isLineMarkerElementState ( candidateEntry ) ; const areBothVisible = elementVisible && candidateEntry . visible ; if ( areDiffType || areBothVisible ) { continue ; } const distSquared = elementPosition . distanceToSquared ( cachedElement . position ) ; if ( distSquared > maxSqDistError ) { continue ; } if ( duplicate === undefined || isBetterDuplicate ( cachedElement , distSquared , duplicate , dupDistSquared ) ) { dupIndex = i ; duplicate = cachedElement ; dupDistSquared = distSquared ; } } return dupIndex ; } /**
 * Caches the state of text element groups currently rendered as well as the text element states
 * belonging to them, including their fading state and text deduplication information.
 */ export class TextElementStateCache { private readonly m_referenceMap = new Map < TextElementGroup , TextElementGroupState > ( ) ; private m_sortedGroupStates : TextElementGroupState [ ] | undefined ; private readonly m_textMap = new Map < string | number , TextElementState [ ] > ( ) ; param textElementGroup param tileKey param textElementFilter see returns getOrSet ( textElementGroup , tileKey , textElementFilter ) { let groupState = this . get ( textElementGroup ) ; if ( groupState !== undefined ) { groupState . updateElements ( textElementFilter ) ; return [ groupState , true ] ; } groupState = new TextElementGroupState ( textElementGroup , tileKey , textElementFilter ) ; this . set ( textElementGroup , groupState ) ; return [ groupState , false ] ; } get size ( ) : number { return this . m_referenceMap . size ; } hidden returns get cacheSize ( ) : number { return this . m_textMap . size ; } returns get sortedGroupStates ( ) : TextElementGroupState [ ] { if ( this . m_sortedGroupStates === undefined ) { this . m_sortedGroupStates = Array . from ( this . m_referenceMap . values ( ) ) ; this . m_sortedGroupStates . sort ( ( a , b ) => { return b . group . priority - a . group . priority ; } ) ; } assert ( this . m_referenceMap . size === this . m_sortedGroupStates . length ) ; return this . m_sortedGroupStates ; } param time param disableFading param findReplacements param zoomLevel returns update ( time , disableFading , findReplacements , zoomLevel ) { const replaceCallback = findReplacements ? this . replaceElement . bind ( this , zoomLevel ) : undefined ; let anyEviction = false ; for ( const [ key , groupState ] of this . m_referenceMap . entries ( ) ) { if ( groupState . visited ) { groupState . updateFading ( time , disableFading ) ; } else { if ( findReplacements ) { groupState . traverseVisibleElements ( replaceCallback ! ) ; } this . m_referenceMap . delete ( key ) ; this . m_sortedGroupStates = undefined ; anyEviction = true ; } } return anyEviction ; } /**
     * Clears visited state for all text element groups in cache.
     */ clearVisited ( ) { for ( const groupState of this . m_referenceMap . values ( ) ) { groupState . visited = false ; } } clearTextCache ( ) { this . m_textMap . clear ( ) ; } /**
     * Clears the whole cache contents.
     */ clear ( ) { this . m_referenceMap . clear ( ) ; this . m_sortedGroupStates = undefined ; this . m_textMap . clear ( ) ; } param zoomLevel param elementState returns deduplicateElement ( zoomLevel , elementState ) { const cacheResult = this . findDuplicate ( elementState , zoomLevel ) ; if ( cacheResult === undefined ) { this . m_textMap . set ( getCacheKey ( elementState . element ) , [ elementState ] ) ; return true ; } if ( cacheResult . index === - 1 ) { cacheResult . entries . push ( elementState ) ; return true ; } const cachedDuplicate = cacheResult . entries [ cacheResult . index ] ; if ( ! cachedDuplicate . visible && elementState . visible ) { cacheResult . entries [ cacheResult . index ] = elementState ; cachedDuplicate . reset ( ) ; return true ; } return false ; } param zoomLevel param elementState returns replaceElement ( zoomLevel , elementState ) { assert ( elementState . visible ) ; const cacheResult = this . findDuplicate ( elementState , zoomLevel ) ; if ( cacheResult === undefined || cacheResult . index === - 1 ) { return false ; } const replacement = cacheResult . entries [ cacheResult . index ] ; assert ( ! replacement . visible ) ; replacement . replace ( elementState ) ; return true ; } param textElementGroup returns private get ( textElementGroup ) { const groupState = this . m_referenceMap . get ( textElementGroup ) ; if ( groupState !== undefined ) { groupState . visited = true ; } return groupState ; } param textElementGroup param textElementGroupState private set ( textElementGroup , textElementGroupState ) { assert ( textElementGroup . elements . length > 0 ) ; this . m_referenceMap . set ( textElementGroup , textElementGroupState ) ; this . m_sortedGroupStates = undefined ; } private findDuplicate ( elementState , zoomLevel ) { const element = elementState . element ; const cachedEntries = this . m_textMap . get ( getCacheKey ( element ) ) ; if ( cachedEntries === undefined ) { return undefined ; } tmpCachedDuplicate . entries = cachedEntries ; const index = element . hasFeatureId ( ) ? findDuplicateById ( elementState , cachedEntries ) : findDuplicateByText ( elementState , cachedEntries , zoomLevel ) ; if ( index === undefined ) { element . featureId = undefined ; return this . findDuplicate ( elementState , zoomLevel ) ; } tmpCachedDuplicate . index = index ; return tmpCachedDuplicate ; } }