mapcomplete/Models/ThemeConfig/DependencyCalculator.ts

112 lines
No EOL
4.3 KiB
TypeScript

import {SpecialVisualization} from "../../UI/SpecialVisualizations";
import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
import TagRenderingConfig from "./TagRenderingConfig";
import {ExtraFuncParams, ExtraFunctions} from "../../Logic/ExtraFunctions";
import LayerConfig from "./LayerConfig";
export default class DependencyCalculator {
public static GetTagRenderingDependencies(tr: TagRenderingConfig): string[] {
if (tr === undefined) {
throw "Got undefined tag rendering in getTagRenderingDependencies"
}
const deps: string[] = []
// All translated snippets
const parts: string[] = [].concat(...(tr.EnumerateTranslations().map(tr => tr.AllValues())))
for (const part of parts) {
const specialVizs: { func: SpecialVisualization, args: string[] }[]
= SubstitutedTranslation.ExtractSpecialComponents(part).map(o => o.special)
.filter(o => o?.func?.getLayerDependencies !== undefined)
for (const specialViz of specialVizs) {
deps.push(...specialViz.func.getLayerDependencies(specialViz.args))
}
}
return deps;
}
/**
* Returns a set of all other layer-ids that this layer needs to function.
* E.g. if this layers does snap to another layer in the preset, this other layer id will be mentioned
*/
public static getLayerDependencies(layer: LayerConfig): { neededLayer: string, reason: string, context?: string, neededBy: string }[] {
const deps: { neededLayer: string, reason: string, context?: string, neededBy: string }[] = []
for (let i = 0; layer.presets !== undefined && i < layer.presets.length; i++) {
const preset = layer.presets[i];
preset.preciseInput?.snapToLayers?.forEach(id => {
deps.push({
neededLayer: id,
reason: "a preset snaps to this layer",
context: "presets[" + i + "]",
neededBy: layer.id
});
})
}
for (const tr of layer.AllTagRenderings()) {
for (const dep of DependencyCalculator.GetTagRenderingDependencies(tr)) {
deps.push({
neededLayer: dep,
reason: "a tagrendering needs this layer",
context: tr.id,
neededBy: layer.id
})
}
}
if (layer.calculatedTags?.length > 0) {
const obj = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [0, 0]
},
properties: {
id: "node/1"
}
}
let currentKey = undefined
let currentLine = undefined
const params: ExtraFuncParams = {
getFeatureById: _ => undefined,
getFeaturesWithin: (layerId, _) => {
if (layerId === '*') {
// This is a wildcard
return []
}
// The important line: steal the dependencies!
deps.push({
neededLayer: layerId, reason: "a calculated tag loads features from this layer",
context: "calculatedTag[" + currentLine + "] which calculates the value for " + currentKey,
neededBy: layer.id
})
return []
},
memberships: undefined
}
// Init the extra patched functions...
ExtraFunctions.FullPatchFeature(params, obj)
// ... Run the calculated tag code, which will trigger the getFeaturesWithin above...
for (let i = 0; i < layer.calculatedTags.length; i++) {
const [key, code] = layer.calculatedTags[i];
currentLine = i; // Leak the state...
currentKey = key;
try {
const func = new Function("feat", "return " + code + ";");
const result = func(obj)
obj.properties[key] = JSON.stringify(result);
} catch (e) {
}
}
}
return deps
}
}