import { LoggerManager } from "@here/harp-utils" ; import { BooleanLiteralExpr , CallExpr , CaseExpr , Env , Expr , ExprScope , ExprVisitor , HasAttributeExpr , InterpolateExpr , isJsonExpr , JsonExpr , LiteralExpr , LookupExpr , MatchExpr , NullLiteralExpr , NumberLiteralExpr , ObjectLiteralExpr , StepExpr , StringLiteralExpr , Value , VarExpr } from "./Expr" ; import { ExprPool } from "./ExprPool" ; import { interpolatedPropertyDefinitionToJsonExpr , isInterpolatedPropertyDefinition } from "./InterpolatedPropertyDefs" ; import { DecoderOptions } from "./ITileDecoder" ; import { AttrScope , getTechniqueAttributeDescriptor } from "./TechniqueDescriptors" ; import { IndexedTechnique , setTechniqueRenderOrderOrPriority , Technique } from "./Techniques" ; import { Definitions , Style , StyleSet } from "./Theme" ; const logger = LoggerManager . instance . create ( "StyleSetEvaluator" ) ; const DEFAULT_TECHNIQUE_ATTR_SCOPE = AttrScope . TechniqueGeometry ; remarks param style param attrName The name of the attribute of the  style .
  function getStyleAttributeScope ( style , attrName ) { if ( style . technique === "extruded-polygon" ) { if ( attrName === "color" && style . vertexColors !== false ) { return DEFAULT_TECHNIQUE_ATTR_SCOPE ; } } return ( getTechniqueAttributeDescriptor ( style . technique , attrName ) ?. scope ?? DEFAULT_TECHNIQUE_ATTR_SCOPE ) ; } interface StyleInternalParams { /**
     * Optimization: Lazy creation and storage of expression in a style object.
     */ _whenExpr ? : Expr ; _minZoomLevelExpr ? : Expr ; _maxZoomLevelExpr ? : Expr ; _staticAttributes ? : Array < [ string , Value ] > ; see _dynamicTechniqueAttributes ? : Array < [ string , Expr ] > ; see _dynamicFeatureAttributes ? : Array < [ string , Expr ] > ; /**
     * These attributes are forwarded as serialized by decoder to main thread, so they are resolved
     * directly in render loop.
     *
     * Will contain attributes from these lists
     *  - interpolants from [[TechiqueDescriptor.techniquePropNames]]
     *  - expressions [[TechniqueDescriptor.dynamicPropNames]] (Future)
     */ _dynamicForwardedAttributes ? : Array < [ string , Expr ] > ; _dynamicTechniques ? : Map < string , IndexedTechnique > ; hidden _staticTechnique ? : IndexedTechnique ; hidden _styleSetIndex ? : number ; hidden _geometryType ? : string ; /**
     * `true` if any of the properties of this technique
     * requires access to the feature's state.
     */ _usesFeatureState ? : boolean ; } type InternalStyle = Style & StyleInternalParams ; hidden class StyleConditionClassifier implements ExprVisitor < Expr | undefined , Expr | undefined > { private _style ! : InternalStyle ; classify ( style ) { if ( style . _whenExpr ) { const savedStyle = this . switchStyle ( style ) ; style . _whenExpr = style . _whenExpr . accept ( this , undefined ) ; this . _style = savedStyle ; } } visitNullLiteralExpr ( expr , enclosingExpr ) { return expr ; } visitBooleanLiteralExpr ( expr , enclosingExpr ) { return expr ; } visitNumberLiteralExpr ( expr , enclosingExpr ) { return expr ; } visitStringLiteralExpr ( expr , enclosingExpr ) { return expr ; } visitObjectLiteralExpr ( expr , enclosingExpr ) { return expr ; } visitVarExpr ( expr , enclosingExpr ) { return expr ; } visitHasAttributeExpr ( expr , enclosingExpr ) { return expr ; } visitMatchExpr ( expr , enclosingExpr ) { return expr ; } visitCaseExpr ( expr , enclosingExpr ) { return expr ; } visitCallExpr ( call , enclosingExpr ) { if ( call . op === "all" ) { const children = call . args . map ( childExpr => childExpr . accept ( this , call ) ) . filter ( childExpr => childExpr !== undefined ) as Expr [ ] ; return new CallExpr ( call . op , children ) ; } else if ( enclosingExpr ) { const matched = this . matchVarStringComparison ( call ) ; if ( matched ) { if ( this . _style . layer === undefined && matched . name === "$layer" ) { this . _style . layer = matched . value ; return undefined ; } else if ( this . _style . _geometryType === undefined && matched . name === "$geometryType" ) { this . _style . _geometryType = matched . value ; return undefined ; } } } return call ; } visitLookupExpr ( lookup , enclosingExpr ) { return this . visitCallExpr ( lookup , enclosingExpr ) ; } visitStepExpr ( expr , enclosingExpr ) { return expr ; } visitInterpolateExpr ( expr , enclosingExpr ) { return expr ; } param call private matchVarStringComparison ( call ) { if ( call . op === "==" ) { const left = call . args [ 0 ] ; const right = call . args [ 1 ] ; if ( left instanceof VarExpr && right instanceof StringLiteralExpr ) { return { name : left . name , value : right . value } ; } if ( right instanceof VarExpr && left instanceof StringLiteralExpr ) { return { name : right . name , value : left . value } ; } } return undefined ; } returns private switchStyle ( style ) { const saved = this . _style ; this . _style = style ; return saved ; } } class OptimizedSubSetKey { key : string ; layer : string | undefined ; geometryType : string | undefined ; cachedStyleSet ? : InternalStyle [ ] ; constructor ( layer ? , geometryType ? ) { this . key = "" ; this . set ( layer , geometryType ) ; } set ( layer , geometryType , env ? ) { let keyUpdateNeeded = false ; if ( layer === undefined ) { const envLayer = env !== undefined ? env . lookup ( "$layer" ) : undefined ; layer = typeof envLayer === "string" ? envLayer : undefined ; } if ( this . layer !== layer ) { this . layer = layer ; keyUpdateNeeded = true ; } if ( geometryType === undefined ) { const envGeometryType = env !== undefined ? env . lookup ( "$geometryType" ) : undefined ; geometryType = typeof envGeometryType === "string" ? envGeometryType : undefined ; } if ( this . geometryType !== geometryType ) { this . geometryType = geometryType ; keyUpdateNeeded = true ; } if ( keyUpdateNeeded ) { this . updateKey ( ) ; } return this ; } private updateKey ( ) { if ( this . layer !== undefined ) { if ( this . geometryType !== undefined ) { this . key = `${ this . layer }:${ this . geometryType }` ; } else { this . key = `${ this . layer }:` ; } } else { if ( this . geometryType !== undefined ) { this . key = `:${ this . geometryType }` ; } else { this . key = "all" ; } } this . cachedStyleSet = undefined ; } } /**
 * Options to be passed to the StyleSetEvaluator
 *
 * Basically identical as the DecoderOptions but requires styleSet to be set.
 */ export type StyleSetOptions = Omit < DecoderOptions , "languages" > & { styleSet : StyleSet ; } ; /**
 * Combine data from datasource and apply the rules from a specified theme to show it on the map.
 */ export class StyleSetEvaluator { readonly styleSet : InternalStyle [ ] ; private readonly m_techniques : IndexedTechnique [ ] = [ ] ; private readonly m_exprPool = new ExprPool ( ) ; private readonly m_cachedResults = new Map < Expr , Value > ( ) ; private readonly m_styleConditionClassifier = new StyleConditionClassifier ( ) ; private readonly m_subStyleSetCache = new Map < string , InternalStyle [ ] > ( ) ; private readonly m_definitions ? : Definitions ; private readonly m_definitionExprCache = new Map < string , Expr > ( ) ; private readonly m_tmpOptimizedSubSetKey : OptimizedSubSetKey = new OptimizedSubSetKey ( ) ; private readonly m_emptyEnv = new Env ( ) ; private m_featureDependencies : string [ ] = [ ] ; private m_layer : string | undefined ; private m_geometryType : string | undefined ; private m_zoomLevel : number | undefined ; private m_previousResult : IndexedTechnique [ ] | undefined ; private m_previousEnv : Env | undefined ; private m_nextArrayBufferId = 0 ; constructor ( private readonly m_options ) { this . m_definitions = this . m_options . definitions ; this . styleSet = resolveReferences ( this . m_options . styleSet , this . m_definitions ) ; computeDefaultRenderOrder ( this . styleSet ) ; this . compileStyleSet ( ) ; } param env param layer param geometryType getMatchingTechniques ( env , layer ? , geometryType ? ) { if ( this . m_previousResult && this . m_previousEnv && this . m_featureDependencies . every ( p => this . m_previousEnv ?. lookup ( p ) === env . lookup ( p ) ) ) { return this . m_previousResult ; } const result = [ ] ; this . m_cachedResults . clear ( ) ; const optimizedSubSetKey = this . m_tmpOptimizedSubSetKey ; optimizedSubSetKey . set ( layer , geometryType , env ) ; this . m_layer = optimizedSubSetKey . layer ; this . m_geometryType = optimizedSubSetKey . geometryType ; this . m_zoomLevel = env . lookup ( "$zoom" ) as number | undefined ; const searchedStyleSet = this . getOptimizedStyleSet ( optimizedSubSetKey ) ; for ( const currStyle of searchedStyleSet ) { if ( this . processStyle ( env , currStyle , result ) ) { break ; } } this . m_previousResult = result ; this . m_previousEnv = env ; return result ; } param layer wantsLayer ( layer ) { return ( this . getOptimizedStyleSet ( this . m_tmpOptimizedSubSetKey . set ( layer , undefined ) ) . length > 0 ) ; } param layer param geometryType wantsFeature ( layer , geometryType ? ) { return ( this . getOptimizedStyleSet ( this . m_tmpOptimizedSubSetKey . set ( layer , geometryType ) ) . length > 0 ) ; } /**
     * Reset array of techniques.
     *
     * Cleans technique array and indices, so it doesn't accumulate accross several decoding runs.
     */ resetTechniques ( ) { for ( const techinque of this . m_techniques ) { techinque . _index = undefined ! ; } this . m_techniques . length = 0 ; this . m_previousResult = undefined ; this . m_previousEnv = undefined ; } /**
     * Get the (current) array of techniques that have been created during decoding.
     */ get techniques ( ) : IndexedTechnique [ ] { return this . m_techniques ; } /**
     * Get the (current) array of techniques that have been created during decoding.
     */ get decodedTechniques ( ) : IndexedTechnique [ ] { return this . m_techniques . map ( makeDecodedTechnique ) ; } private getOptimizedStyleSet ( subSetKey ) { if ( subSetKey . cachedStyleSet !== undefined ) { return subSetKey . cachedStyleSet ; } let optimizedStyleSet = this . m_subStyleSetCache . get ( subSetKey . key ) ; if ( optimizedStyleSet !== undefined ) { subSetKey . cachedStyleSet = optimizedStyleSet ; return optimizedStyleSet ; } optimizedStyleSet = this . createPreFilteredStyleSet ( subSetKey ) ; this . m_subStyleSetCache . set ( subSetKey . key , optimizedStyleSet ) ; subSetKey . cachedStyleSet = optimizedStyleSet ; return optimizedStyleSet ; } private createPreFilteredStyleSet ( subSetKey ) { const { layer , geometryType } = subSetKey ; return this . styleSet . filter ( style => { if ( layer !== undefined && style . layer !== undefined && style . layer !== layer ) { return false ; } if ( geometryType !== undefined && style . _geometryType !== undefined && style . _geometryType !== geometryType ) { return false ; } return true ; } ) ; } /**
     * Compile the `when` conditions found when traversting the styling rules.
     */ private compileStyleSet ( ) { this . m_featureDependencies = [ "$layer" , "$geometryType" , "$zoom" ] ; this . styleSet . forEach ( style => this . compileStyle ( style ) ) ; this . styleSet . forEach ( style => { this . getOptimizedStyleSet ( this . m_tmpOptimizedSubSetKey . set ( style . layer , style . _geometryType ) ) ; } ) ; } param style private compileStyle ( style ) { this . checkStyleDynamicAttributes ( style ) ; if ( style . when !== undefined ) { try { style . _whenExpr = Array . isArray ( style . when ) ? Expr . fromJSON ( style . when , this . m_definitions , this . m_definitionExprCache ) : Expr . parse ( style . when ) ; this . m_styleConditionClassifier . classify ( style ) ; if ( style . _whenExpr !== undefined ) { style . _whenExpr = style . _whenExpr . intern ( this . m_exprPool ) ; } const deps = style . _whenExpr . dependencies ( ) ; deps ?. properties . forEach ( prop => { if ( ! this . m_featureDependencies . includes ( prop ) ) { this . m_featureDependencies . push ( prop ) ; } } ) ; if ( isJsonExpr ( style . minZoomLevel ) ) { style . _minZoomLevelExpr = Expr . fromJSON ( style . minZoomLevel , this . m_definitions ) . intern ( this . m_exprPool ) ; } if ( isJsonExpr ( style . maxZoomLevel ) ) { style . _maxZoomLevelExpr = Expr . fromJSON ( style . maxZoomLevel , this . m_definitions ) . intern ( this . m_exprPool ) ; } } catch ( err ) { logger . log ( "failed to evaluate expression" , JSON . stringify ( style . when ) , "error" , String ( err ) ) ; } } } param env param style param result returns private processStyle ( env , style , result ) { if ( ! this . checkZoomLevel ( env , style ) ) { return false ; } if ( this . m_layer !== undefined && style . layer !== undefined && style . layer !== this . m_layer ) { return false ; } if ( this . m_geometryType !== undefined && style . _geometryType !== undefined && style . _geometryType !== this . m_geometryType ) { return false ; } if ( style . _whenExpr ) { try { if ( ! style . _whenExpr . evaluate ( env , ExprScope . Condition , this . m_cachedResults ) ) { return false ; } } catch ( error ) { logger . error ( `failed to evaluate expression '${ JSON . stringify ( style . when ) }': ${ error }` ) ; return false ; } } if ( style . technique === undefined ) { return false ; } if ( style . technique !== "none" ) { result . push ( this . getTechniqueForStyleMatch ( env , style ) ) ; } return style . final === true ; } private checkZoomLevel ( env , style ) { if ( style . minZoomLevel === undefined && style . maxZoomLevel === undefined ) { return true ; } const zoomLevel = this . m_zoomLevel ; if ( zoomLevel === undefined ) { return true ; } if ( style . minZoomLevel !== undefined ) { let minZoomLevel = style . minZoomLevel ; if ( style . _minZoomLevelExpr ?. isDynamic ( ) === false ) { try { minZoomLevel = style . _minZoomLevelExpr . evaluate ( env , ExprScope . Condition , this . m_cachedResults ) ; } catch ( error ) { logger . error ( `failed to evaluate expression '${ JSON . stringify ( style . _minZoomLevelExpr ) }': ${ error }` ) ; } } if ( typeof minZoomLevel === "number" && zoomLevel < minZoomLevel ) { return false ; } } if ( style . maxZoomLevel !== undefined ) { let maxZoomLevel = style . maxZoomLevel ; if ( style . _maxZoomLevelExpr ?. isDynamic ( ) === false ) { try { maxZoomLevel = style . _maxZoomLevelExpr . evaluate ( env , ExprScope . Condition , this . m_cachedResults ) ; } catch ( error ) { logger . error ( `failed to evaluate expression '${ JSON . stringify ( style . _maxZoomLevelExpr ) }': ${ error }` ) ; } } if ( typeof maxZoomLevel === "number" && zoomLevel >= maxZoomLevel ) { return false ; } } return true ; } private getTechniqueForStyleMatch ( env , style ) { let technique ; if ( style . _dynamicTechniques !== undefined ) { const dynamicAttributes = this . evaluateTechniqueProperties ( style , env ) ; const key = this . getDynamicTechniqueKey ( style , dynamicAttributes ) ; technique = style . _dynamicTechniques ! . get ( key ) ; if ( technique === undefined ) { technique = this . createTechnique ( style , key , dynamicAttributes ) ; style . _dynamicTechniques ! . set ( key , technique ) ; } } else { technique = style . _staticTechnique ; if ( technique === undefined ) { style . _staticTechnique = technique = this . createTechnique ( style , `${ style . _styleSetIndex }` , [ ] ) as IndexedTechnique ; } } if ( technique . _index === undefined ) { technique . _index = this . m_techniques . length ; this . m_techniques . push ( technique ) ; } return technique ; } private getDynamicTechniqueKey ( style , dynamicAttributes ) { const dynamicAttrKey = dynamicAttributes . map ( ( [ _attrName , attrValue ] ) => { if ( attrValue === undefined ) { return "U" ; } else if ( typeof attrValue === "object" ) { return JSON . stringify ( attrValue , ( _ , value ) => { if ( value instanceof ArrayBuffer ) { let arrayBufferId = ( value as any ) . id ; if ( arrayBufferId === undefined ) { arrayBufferId = ( value as any ) . id = this . m_nextArrayBufferId ++ ; } return arrayBufferId ; } return value ; } ) ; } else { return JSON . stringify ( attrValue ) ; } } ) . join ( ":" ) ; return `${ style . _styleSetIndex ! }:${ dynamicAttrKey }` ; } private checkStyleDynamicAttributes ( style ) { if ( style . _dynamicTechniqueAttributes !== undefined || style . technique === "none" ) { return ; } style . _dynamicTechniqueAttributes = [ ] ; style . _dynamicFeatureAttributes = [ ] ; style . _dynamicForwardedAttributes = [ ] ; style . _staticAttributes = [ ] ; const dynamicFeatureAttributes = style . _dynamicFeatureAttributes ; const dynamicTechniqueAttributes = style . _dynamicTechniqueAttributes ; const dynamicForwardedAttributes = style . _dynamicForwardedAttributes ; const targetStaticAttributes = style . _staticAttributes ; const processAttribute = ( attrName , attrValue ) => { if ( attrValue === undefined ) { return ; } if ( isJsonExpr ( attrValue ) ) { attrValue = Expr . fromJSON ( attrValue , this . m_definitions , this . m_definitionExprCache ) . intern ( this . m_exprPool ) ; } else if ( isInterpolatedPropertyDefinition ( attrValue ) ) { attrValue = Expr . fromJSON ( interpolatedPropertyDefinitionToJsonExpr ( attrValue ) ) . intern ( this . m_exprPool ) ; } if ( Expr . isExpr ( attrValue ) ) { const deps = attrValue . dependencies ( ) ; if ( deps . featureState ) { if ( attrName !== "enabled" ) { logger . log ( "feature-state is not supported in this context" ) ; } else { style . _usesFeatureState = true ; } } if ( deps . properties . size === 0 && ! attrValue . isDynamic ( ) ) { attrValue = attrValue . evaluate ( this . m_emptyEnv ) ; } } if ( Expr . isExpr ( attrValue ) ) { const attrScope = getStyleAttributeScope ( style , attrName ) ; const deps = attrValue . dependencies ( ) ; deps . properties . forEach ( prop => { if ( ! this . m_featureDependencies . includes ( prop ) ) { this . m_featureDependencies . push ( prop ) ; } } ) ; switch ( attrScope ) { case AttrScope . FeatureGeometry : dynamicFeatureAttributes . push ( [ attrName , attrValue ] ) ; break ; case AttrScope . TechniqueGeometry : dynamicTechniqueAttributes . push ( [ attrName , attrValue ] ) ; break ; case AttrScope . TechniqueRendering : if ( deps . properties . size === 0 ) { dynamicForwardedAttributes . push ( [ attrName , attrValue ] ) ; } else { dynamicTechniqueAttributes . push ( [ attrName , attrValue ] ) ; } break ; } } else if ( attrValue !== undefined && attrValue !== null ) { targetStaticAttributes . push ( [ attrName , attrValue ] ) ; } } ; const replacement = new Map < string , string > ( [ [ "category" , "_category" ] , [ "secondaryCategory" , "_secondaryCategory" ] ] ) ; for ( const p in style ) { if ( ! style . hasOwnProperty ( p ) ) { continue ; } if ( p . startsWith ( "_" ) ) { continue ; } if ( [ "when" , "technique" , "layer" , "attr" , "description" ] . includes ( p ) ) { continue ; } const pp = replacement . get ( p ) ?? p ; processAttribute ( pp , ( style as any ) [ p ] ) ; } if ( style . attr !== undefined ) { for ( const attrName in style . attr ) { if ( ! style . attr . hasOwnProperty ( attrName ) ) { continue ; } processAttribute ( attrName , ( style . attr as any ) [ attrName ] ) ; } } if ( dynamicTechniqueAttributes . length > 0 ) { style . _dynamicTechniques = new Map ( ) ; } } private evaluateTechniqueProperties ( style , env ) { if ( style . _dynamicTechniqueAttributes === undefined ) { return [ ] ; } return style . _dynamicTechniqueAttributes . map ( ( [ attrName , attrExpr ] ) => { try { const evaluatedValue = attrExpr . evaluate ( env , ExprScope . Value , this . m_cachedResults ) ; return [ attrName , evaluatedValue ] ; } catch ( error ) { logger . error ( `failed to evaluate expression '${ attrExpr . toJSON ( ) }': ${ error }` ) ; return [ attrName , null ] ; } } ) ; } private createTechnique ( style , key , dynamicAttrs ) { const technique = { } ; technique . name = style . technique ; if ( style . _staticAttributes !== undefined ) { for ( const [ attrName , attrValue ] of style . _staticAttributes ) { if ( attrValue !== null ) { technique [ attrName ] = attrValue ; } } } for ( const [ attrName , attrValue ] of dynamicAttrs ) { if ( attrValue !== null ) { technique [ attrName ] = attrValue ; } } if ( style . _dynamicFeatureAttributes !== undefined ) { for ( const [ attrName , attrValue ] of style . _dynamicFeatureAttributes ) { technique [ attrName ] = attrValue ; } } if ( style . _dynamicForwardedAttributes !== undefined ) { for ( const [ attrName , attrValue ] of style . _dynamicForwardedAttributes ) { technique [ attrName ] = attrValue ; } } technique . _index = this . m_techniques . length ; technique . _styleSetIndex = style . _styleSetIndex ! ; if ( style . styleSet !== undefined ) { technique . _styleSet = style . styleSet ; } if ( style . _usesFeatureState !== undefined ) { technique . _usesFeatureState = style . _usesFeatureState ; } setTechniqueRenderOrderOrPriority ( technique , this . m_options . priorities ?? [ ] , this . m_options . labelPriorities ?? [ ] ) ; this . m_techniques . push ( technique as IndexedTechnique ) ; return technique as IndexedTechnique ; } } function computeDefaultRenderOrder ( styleSet ) { let techniqueRenderOrder = 0 ; let styleSetIndex = 0 ; for ( const style of styleSet ) { style . _styleSetIndex = styleSetIndex ++ ; if ( style . technique !== undefined && style . renderOrder === undefined ) { style . renderOrder = techniqueRenderOrder ++ ; } } } function resolveReferences ( styleSet , definitions ) { return styleSet . map ( style => resolveStyleReferences ( style , definitions ) ) ; } function resolveStyleReferences ( style , definitions ) { return { ... style } ; } /**
 * Create transferable representation of dynamic technique.
 *
 * Converts  non-transferable  Expr instances back to JSON form.
  export function makeDecodedTechnique ( technique ) { const result = { } ; for ( const attrName in technique ) { if ( ! technique . hasOwnProperty ( attrName ) ) { continue ; } let attrValue = ( technique as any ) [ attrName ] ; if ( typeof attrValue === "object" && ( attrValue . isVector2 || attrValue . isVector3 || attrValue . isVector4 ) ) { attrValue = LiteralExpr . fromValue ( attrValue ) ; } if ( Expr . isExpr ( attrValue ) ) { attrValue = attrValue . toJSON ( ) ; } ( result as any ) [ attrName ] = attrValue ; } return ( result as any ) as IndexedTechnique ; }