Logic: better support for tag optimization and simplifying expressions

This commit is contained in:
Pieter Vander Vennet 2025-01-09 20:39:21 +01:00
parent bd228a6129
commit a3d26db84a
11 changed files with 430 additions and 260 deletions

View file

@ -10,10 +10,7 @@ import {
SetDefault,
} from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import {
MinimalTagRenderingConfigJson,
TagRenderingConfigJson,
} from "../Json/TagRenderingConfigJson"
import { MinimalTagRenderingConfigJson, TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import { Utils } from "../../../Utils"
import RewritableConfigJson from "../Json/RewritableConfigJson"
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
@ -21,8 +18,7 @@ import Translations from "../../../UI/i18n/Translations"
import { Translation } from "../../../UI/i18n/Translation"
import tagrenderingconfigmeta from "../../../../src/assets/schemas/tagrenderingconfigmeta.json"
import { AddContextToTranslations } from "./AddContextToTranslations"
import FilterConfigJson, { FilterConfigOptionJson } from "../Json/FilterConfigJson"
import predifined_filters from "../../../../assets/layers/filters/filters.json"
import FilterConfigJson from "../Json/FilterConfigJson"
import { TagConfigJson } from "../Json/TagConfigJson"
import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson"
import ValidationUtils from "./ValidationUtils"
@ -33,9 +29,7 @@ import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import { ConversionContext } from "./ConversionContext"
import { ExpandRewrite } from "./ExpandRewrite"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import { Tag } from "../../../Logic/Tags/Tag"
import { RegexTag } from "../../../Logic/Tags/RegexTag"
import { Or } from "../../../Logic/Tags/Or"
import { ExpandFilter, PruneFilters } from "./ExpandFilter"
class AddFiltersFromTagRenderings extends DesugaringStep<LayerConfigJson> {
constructor() {
@ -108,163 +102,6 @@ class AddFiltersFromTagRenderings extends DesugaringStep<LayerConfigJson> {
return { ...json, filter: filters }
}
}
class ExpandFilter extends DesugaringStep<LayerConfigJson> {
private static readonly predefinedFilters = ExpandFilter.load_filters()
private _state: DesugaringContext
constructor(state: DesugaringContext) {
super(
[
"Expands filters: replaces a shorthand by the value found in 'filters.json'.",
"If the string is formatted 'layername.filtername, it will be looked up into that layer instead.",
].join(" "),
["filter"],
"ExpandFilter"
)
this._state = state
}
private static load_filters(): Map<string, FilterConfigJson> {
const filters = new Map<string, FilterConfigJson>()
for (const filter of <FilterConfigJson[]>predifined_filters.filter) {
filters.set(filter.id, filter)
}
return filters
}
public static buildFilterFromTagRendering(
tr: TagRenderingConfigJson,
context: ConversionContext
): FilterConfigJson {
if (!(tr.mappings?.length >= 1)) {
context.err(
"Found a matching tagRendering to base a filter on, but this tagRendering does not contain any mappings"
)
}
const qtr = <QuestionableTagRenderingConfigJson>tr
const options = qtr.mappings.map((mapping) => {
let icon: string = mapping.icon?.["path"] ?? mapping.icon
let emoji: string = undefined
if (Utils.isEmoji(icon)) {
emoji = icon
icon = undefined
}
let osmTags = TagUtils.Tag(mapping.if)
if (qtr.multiAnswer && osmTags instanceof Tag) {
osmTags = new RegexTag(
osmTags.key,
new RegExp("^(.+;)?" + osmTags.value + "(;.+)$", "is")
)
}
if (mapping.alsoShowIf) {
osmTags = new Or([osmTags, TagUtils.Tag(mapping.alsoShowIf)])
}
return <FilterConfigOptionJson>{
question: mapping.then,
osmTags: osmTags.asJson(),
searchTerms: mapping.searchTerms,
icon,
emoji,
}
})
// Add default option
options.unshift({
question: tr["question"] ?? Translations.t.general.filterPanel.allTypes,
osmTags: undefined,
searchTerms: undefined,
})
return {
id: tr["id"],
options,
}
}
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if (json?.filter === undefined || json?.filter === null) {
return json // Nothing to change here
}
if (json.filter["sameAs"] !== undefined) {
return json // Nothing to change here
}
const newFilters: FilterConfigJson[] = []
const filters = <(FilterConfigJson | string)[]>json.filter
/**
* Create filters based on builtin filters or create them based on the tagRendering
*/
for (let i = 0; i < filters.length; i++) {
const filter = filters[i]
if (filter === undefined) {
continue
}
if (typeof filter !== "string") {
newFilters.push(filter)
continue
}
const matchingTr = <TagRenderingConfigJson>(
json.tagRenderings.find((tr) => !!tr && tr["id"] === filter)
)
if (matchingTr) {
const filter = ExpandFilter.buildFilterFromTagRendering(
matchingTr,
context.enters("filter", i)
)
newFilters.push(filter)
continue
}
if (filter.indexOf(".") > 0) {
if (!(this._state.sharedLayers?.size > 0)) {
// This is a bootstrapping-run, we can safely ignore this
continue
}
const split = filter.split(".")
if (split.length > 2) {
context.err(
"invalid filter name: " + filter + ", expected `layername.filterid`"
)
}
const layer = this._state.sharedLayers.get(split[0])
if (layer === undefined) {
context.err("Layer '" + split[0] + "' not found")
}
const expectedId = split[1]
const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find(
(f) => typeof f !== "string" && f.id === expectedId
)
if (expandedFilter === undefined) {
context.err("Did not find filter with name " + filter)
} else {
newFilters.push(<FilterConfigJson>expandedFilter)
}
continue
}
// Search for the filter:
const found = ExpandFilter.predefinedFilters.get(filter)
if (found === undefined) {
const suggestions = Utils.sortedByLevenshteinDistance(
filter,
Array.from(ExpandFilter.predefinedFilters.keys()),
(t) => t
)
context
.enter(filter)
.err(
"While searching for predefined filter " +
filter +
": this filter is not found. Perhaps you meant one of: " +
suggestions
)
}
newFilters.push(found)
}
return { ...json, filter: newFilters }
}
}
class ExpandTagRendering extends Conversion<
| string
@ -1481,7 +1318,8 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
new Concat(new ExpandTagRendering(state, layer, { noHardcodedStrings: true }))
),
new AddFiltersFromTagRenderings(),
new ExpandFilter(state)
new ExpandFilter(state),
new PruneFilters()
)
}
}