2021-12-12 17:35:08 +01:00
import SimpleMetaTaggers , { SimpleMetaTagger } from "./SimpleMetaTagger" ;
2021-12-05 02:06:14 +01:00
import { ExtraFuncParams , ExtraFunctions } from "./ExtraFunctions" ;
2021-08-07 23:11:34 +02:00
import LayerConfig from "../Models/ThemeConfig/LayerConfig" ;
2022-01-26 20:47:08 +01:00
import { ElementStorage } from "./ElementStorage" ;
2021-04-22 13:30:00 +02:00
2020-10-19 12:08:42 +02:00
/ * *
* Metatagging adds various tags to the elements , e . g . lat , lon , surface area , . . .
*
* All metatags start with an underscore
* /
export default class MetaTagging {
2021-09-09 00:05:51 +02:00
private static errorPrintCount = 0 ;
private static readonly stopErrorOutputAt = 10 ;
2022-01-26 21:40:38 +01:00
private static retaggingFuncCache = new Map < string , ( ( feature : any ) = > void ) [ ] > ( )
2021-09-09 00:05:51 +02:00
2021-03-24 01:25:57 +01:00
/ * *
2021-09-21 02:10:42 +02:00
* This method ( re ) calculates all metatags and calculated tags on every given object .
* The given features should be part of the given layer
2021-11-07 16:34:51 +01:00
*
2021-10-27 20:19:45 +02:00
* Returns true if at least one feature has changed properties
2021-03-24 01:25:57 +01:00
* /
2021-09-22 05:02:09 +02:00
public static addMetatags ( features : { feature : any ; freshness : Date } [ ] ,
2021-09-26 17:36:39 +02:00
params : ExtraFuncParams ,
layer : LayerConfig ,
2022-01-26 21:40:38 +01:00
state ? : { allElements? : ElementStorage } ,
2021-09-26 17:36:39 +02:00
options ? : {
includeDates? : true | boolean ,
2022-02-02 02:36:49 +01:00
includeNonDates? : true | boolean ,
evaluateStrict? : false | boolean
2021-10-27 20:19:45 +02:00
} ) : boolean {
2021-07-26 20:21:05 +02:00
if ( features === undefined || features . length === 0 ) {
2021-06-21 03:13:49 +02:00
return ;
}
2020-10-30 00:56:46 +01:00
2021-12-12 17:35:08 +01:00
const metatagsToApply : SimpleMetaTagger [ ] = [ ]
2021-12-07 02:22:56 +01:00
for ( const metatag of SimpleMetaTaggers . metatags ) {
2021-10-10 23:38:09 +02:00
if ( metatag . includesDates ) {
2022-01-26 20:47:08 +01:00
if ( options ? . includeDates ? ? true ) {
2021-10-10 23:38:09 +02:00
metatagsToApply . push ( metatag )
}
} else {
2022-01-26 20:47:08 +01:00
if ( options ? . includeNonDates ? ? true ) {
2021-10-10 23:38:09 +02:00
metatagsToApply . push ( metatag )
2021-09-22 05:02:09 +02:00
}
2020-10-30 00:56:46 +01:00
}
2021-10-10 23:38:09 +02:00
}
2020-10-30 00:56:46 +01:00
2021-09-26 17:36:39 +02:00
// The calculated functions - per layer - which add the new keys
2021-12-12 17:35:08 +01:00
const layerFuncs = this . createRetaggingFunc ( layer , state )
2021-05-13 12:40:19 +02:00
2021-10-27 20:19:45 +02:00
let atLeastOneFeatureChanged = false ;
2020-10-30 00:56:46 +01:00
2021-09-26 17:36:39 +02:00
for ( let i = 0 ; i < features . length ; i ++ ) {
2021-10-10 23:38:09 +02:00
const ff = features [ i ] ;
const feature = ff . feature
const freshness = ff . freshness
let somethingChanged = false
2022-01-26 21:40:38 +01:00
let definedTags = new Set ( Object . getOwnPropertyNames ( feature . properties ) )
2021-10-10 23:38:09 +02:00
for ( const metatag of metatagsToApply ) {
try {
if ( ! metatag . keys . some ( key = > feature . properties [ key ] === undefined ) ) {
// All keys are already defined, we probably already ran this one
continue
}
2021-11-07 16:34:51 +01:00
if ( metatag . isLazy ) {
2022-01-26 21:40:38 +01:00
if ( ! metatag . keys . some ( key = > ! definedTags . has ( key ) ) ) {
2022-01-06 18:51:52 +01:00
// All keys are defined - lets skip!
continue
}
2021-10-10 23:38:09 +02:00
somethingChanged = true ;
2021-12-12 17:35:08 +01:00
metatag . applyMetaTagsOnFeature ( feature , freshness , layer , state )
2022-02-02 02:36:49 +01:00
if ( options ? . evaluateStrict ) {
for ( const key of metatag . keys ) {
feature . properties [ key ]
}
}
2021-11-07 16:34:51 +01:00
} else {
2021-12-12 17:35:08 +01:00
const newValueAdded = metatag . applyMetaTagsOnFeature ( feature , freshness , layer , state )
2021-10-04 00:18:08 +02:00
/ * N o t e t h a t t h e e x p r e s s i o n :
* ` somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness) `
* Is WRONG
*
* IF something changed is ` true ` due to an earlier run , it will short - circuit and _not_ evaluate the right hand of the OR ,
* thus not running an update !
* /
somethingChanged = newValueAdded || somethingChanged
2021-09-26 17:36:39 +02:00
}
2021-10-10 23:38:09 +02:00
} catch ( e ) {
console . error ( "Could not calculate metatag for " , metatag . keys . join ( "," ) , ":" , e , e . stack )
2021-06-20 03:09:26 +02:00
}
}
2020-10-30 00:56:46 +01:00
2021-10-10 23:38:09 +02:00
if ( layerFuncs !== undefined ) {
2022-01-06 18:51:52 +01:00
let retaggingChanged = false ;
2021-10-10 23:38:09 +02:00
try {
2022-01-06 18:51:52 +01:00
retaggingChanged = layerFuncs ( params , feature )
2021-10-10 23:38:09 +02:00
} catch ( e ) {
console . error ( e )
}
2022-01-06 18:51:52 +01:00
somethingChanged = somethingChanged || retaggingChanged
2021-10-10 23:38:09 +02:00
}
2021-09-26 17:36:39 +02:00
2021-10-10 23:38:09 +02:00
if ( somethingChanged ) {
2021-12-12 17:35:08 +01:00
state ? . allElements ? . getEventSourceById ( feature . properties . id ) ? . ping ( )
2021-10-27 20:19:45 +02:00
atLeastOneFeatureChanged = true
2021-10-10 23:38:09 +02:00
}
2020-10-30 00:56:46 +01:00
}
2021-10-27 20:19:45 +02:00
return atLeastOneFeatureChanged
2021-10-10 23:38:09 +02:00
}
2022-01-26 21:40:38 +01:00
2022-01-26 20:47:08 +01:00
private static createFunctionsForFeature ( layerId : string , calculatedTags : [ string , string , boolean ] [ ] ) : ( ( feature : any ) = > void ) [ ] {
2022-01-07 04:14:53 +01:00
const functions : ( ( feature : any ) = > any ) [ ] = [ ] ;
2021-03-24 01:25:57 +01:00
for ( const entry of calculatedTags ) {
const key = entry [ 0 ]
const code = entry [ 1 ] ;
2021-12-12 02:59:24 +01:00
const isStrict = entry [ 2 ]
2021-03-24 01:25:57 +01:00
if ( code === undefined ) {
continue ;
2021-03-22 01:04:25 +01:00
}
2021-09-21 02:10:42 +02:00
2022-01-07 04:14:53 +01:00
const calculateAndAssign : ( ( feat : any ) = > any ) = ( feat ) = > {
2021-12-12 02:59:24 +01:00
try {
let result = new Function ( "feat" , "return " + code + ";" ) ( feat ) ;
if ( result === "" ) {
result === undefined
}
if ( result !== undefined && typeof result !== "string" ) {
// Make sure it is a string!
result = JSON . stringify ( result ) ;
}
delete feat . properties [ key ]
feat . properties [ key ] = result ;
2022-01-07 04:14:53 +01:00
return result
2022-01-26 21:40:38 +01:00
} catch ( e ) {
2021-12-12 02:59:24 +01:00
if ( MetaTagging . errorPrintCount < MetaTagging . stopErrorOutputAt ) {
console . warn ( "Could not calculate a " + ( isStrict ? "strict " : "" ) + " calculated tag for key " + key + " defined by " + code + " (in layer" + layerId + ") due to \n" + e + "\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features" , e , e . stack )
MetaTagging . errorPrintCount ++ ;
if ( MetaTagging . errorPrintCount == MetaTagging . stopErrorOutputAt ) {
console . error ( "Got " , MetaTagging . stopErrorOutputAt , " errors calculating this metatagging - stopping output now" )
}
}
2022-01-07 04:14:53 +01:00
return undefined ;
2021-12-12 02:59:24 +01:00
}
2022-01-26 21:40:38 +01:00
}
if ( isStrict ) {
2021-12-12 02:59:24 +01:00
functions . push ( calculateAndAssign )
continue
}
2021-12-11 02:19:28 +01:00
2021-12-12 02:59:24 +01:00
// Lazy function
const f = ( feature : any ) = > {
2022-01-07 17:31:39 +01:00
const oldValue = feature . properties [ key ]
2021-10-10 23:38:09 +02:00
delete feature . properties [ key ]
Object . defineProperty ( feature . properties , key , {
configurable : true ,
enumerable : false , // By setting this as not enumerable, the localTileSaver will _not_ calculate this
get : function ( ) {
2022-01-07 04:14:53 +01:00
return calculateAndAssign ( feature )
2021-11-07 16:34:51 +01:00
}
} )
2022-01-07 04:14:53 +01:00
return undefined
2021-05-10 23:51:03 +02:00
}
2021-11-07 16:34:51 +01:00
2021-10-10 23:38:09 +02:00
functions . push ( f )
2021-02-20 03:29:55 +01:00
}
2021-10-10 23:38:09 +02:00
return functions ;
}
2022-01-06 18:51:52 +01:00
/ * *
* Creates the function which adds all the calculated tags to a feature . Called once per layer
* @param layer
* @param state
* @private
* /
2021-12-12 17:35:08 +01:00
private static createRetaggingFunc ( layer : LayerConfig , state ) :
2022-01-06 18:51:52 +01:00
( ( params : ExtraFuncParams , feature : any ) = > boolean ) {
2021-10-10 23:38:09 +02:00
2021-12-12 02:59:24 +01:00
const calculatedTags : [ string , string , boolean ] [ ] = layer . calculatedTags ;
2021-10-10 23:38:09 +02:00
if ( calculatedTags === undefined || calculatedTags . length === 0 ) {
return undefined ;
}
2022-01-26 21:40:38 +01:00
let functions : ( ( feature : any ) = > void ) [ ] = MetaTagging . retaggingFuncCache . get ( layer . id ) ;
2021-12-11 02:19:28 +01:00
if ( functions === undefined ) {
functions = MetaTagging . createFunctionsForFeature ( layer . id , calculatedTags )
MetaTagging . retaggingFuncCache . set ( layer . id , functions )
}
2021-09-21 02:10:42 +02:00
return ( params : ExtraFuncParams , feature ) = > {
2021-03-24 01:25:57 +01:00
const tags = feature . properties
if ( tags === undefined ) {
return ;
2020-11-11 16:23:49 +01:00
}
2021-03-22 02:45:22 +01:00
2021-03-26 03:24:58 +01:00
try {
2021-12-05 02:06:14 +01:00
ExtraFunctions . FullPatchFeature ( params , feature ) ;
2021-03-26 03:24:58 +01:00
for ( const f of functions ) {
2021-10-10 23:38:09 +02:00
f ( feature ) ;
2021-03-24 01:25:57 +01:00
}
2021-12-12 17:35:08 +01:00
state ? . allElements ? . getEventSourceById ( feature . properties . id ) ? . ping ( ) ;
2021-03-26 03:24:58 +01:00
} catch ( e ) {
2021-10-10 23:38:09 +02:00
console . error ( "Invalid syntax in calculated tags or some other error: " , e )
2021-03-24 01:25:57 +01:00
}
2022-01-06 18:51:52 +01:00
return true ; // Something changed
2021-03-22 01:04:25 +01:00
}
}
2021-03-25 15:19:44 +01:00
2020-10-19 12:08:42 +02:00
}