From fd1b6d3131cce6f4a7470aafc9908468eaa0b196 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 7 May 2024 17:04:07 +0200 Subject: [PATCH] Performance: add some optimizations and tag fixes --- src/Logic/Tags/And.ts | 53 ++++++++++++++++++++++++++++++++-- src/Logic/Tags/ComparingTag.ts | 29 +++++++++++-------- src/Logic/Tags/RegexTag.ts | 9 +++++- src/Logic/Tags/Tag.ts | 18 ++++++++---- 4 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index f28e65065..c27349205 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -5,6 +5,7 @@ import { Tag } from "./Tag" import { RegexTag } from "./RegexTag" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" import { ExpressionSpecification } from "maplibre-gl" +import ComparingTag from "./ComparingTag" export class And extends TagsFilter { public and: TagsFilter[] @@ -242,6 +243,27 @@ export class And extends TagsFilter { * const raw = {"and": [{"and":["advertising=screen"]}, {"and":["advertising~*"]}]}] * const parsed = TagUtils.Tag(raw) * parsed.optimize().asJson() // => "advertising=screen" + * + * const raw = {"and": ["count=0", "count>0"]} + * const parsed = TagUtils.Tag(raw) + * parsed.optimize() // => false + * + * const raw = {"and": ["count>0", "count>10"]} + * const parsed = TagUtils.Tag(raw) + * parsed.optimize().asJson() // => "count>0" + * + * // regression test + * const orig = { + * "and": [ + * "sport=climbing", + * "climbing!~route", + * "climbing!=route_top", + * "climbing!=route_bottom", + * "leisure!~sports_centre" + * ] + * } + * const parsed = TagUtils.Tag(orig) + * parsed.optimize().asJson() // => orig */ optimize(): TagsFilter | boolean { if (this.and.length === 0) { @@ -256,9 +278,30 @@ export class And extends TagsFilter { } const optimized = optimizedRaw + for (let i = 0; i 0' and 'tj' be 'count>10'. + // As such, it is no use to keep 'tj' around: + // If 'ti' is true, then 'tj' will be true too and 'tj' can be ignored + // If 'ti' is false, then the entire expression will be false and it doesn't matter what 'tj' yields + optimized.splice(j, 1) + }else if (tj.shadows(ti)){ + optimized.splice(i, 1) + i-- + continue + } + } + } + + { // Conflicting keys do return false - const properties: object = {} + const properties: Record = {} for (const opt of optimized) { if (opt instanceof Tag) { properties[opt.key] = opt.value @@ -277,8 +320,7 @@ export class And extends TagsFilter { // detected an internal conflict return false } - } - if (opt instanceof RegexTag) { + } else if (opt instanceof RegexTag) { const k = opt.key if (typeof k !== "string") { continue @@ -316,6 +358,11 @@ export class And extends TagsFilter { i-- } } + }else if(opt instanceof ComparingTag) { + const ct = opt + if(properties[ct.key] !== undefined && !ct.matchesProperties(properties)){ + return false + } } } } diff --git a/src/Logic/Tags/ComparingTag.ts b/src/Logic/Tags/ComparingTag.ts index 42a26c497..88f36e09e 100644 --- a/src/Logic/Tags/ComparingTag.ts +++ b/src/Logic/Tags/ComparingTag.ts @@ -1,10 +1,9 @@ import { TagsFilter } from "./TagsFilter" -import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" import { Tag } from "./Tag" import { ExpressionSpecification } from "maplibre-gl" export default class ComparingTag extends TagsFilter { - private readonly _key: string + public readonly key: string private readonly _predicate: (value: string) => boolean private readonly _representation: "<" | ">" | "<=" | ">=" private readonly _boundary: string @@ -16,7 +15,7 @@ export default class ComparingTag extends TagsFilter { boundary: string ) { super() - this._key = key + this.key = key this._predicate = predicate this._representation = representation this._boundary = boundary @@ -27,7 +26,7 @@ export default class ComparingTag extends TagsFilter { } asHumanString() { - return this._key + this._representation + this._boundary + return this.asJson() } asOverpass(): string[] { @@ -55,7 +54,7 @@ export default class ComparingTag extends TagsFilter { return true } if (other instanceof ComparingTag) { - if (other._key !== this._key) { + if (other.key !== this.key) { return false } const selfDesc = this._representation === "<" || this._representation === "<=" @@ -76,7 +75,7 @@ export default class ComparingTag extends TagsFilter { } if (other instanceof Tag) { - if (other.key !== this._key) { + if (other.key !== this.key) { return false } if (this.matchesProperties({ [other.key]: other.value })) { @@ -101,19 +100,25 @@ export default class ComparingTag extends TagsFilter { * t.matchesProperties({differentKey: 42}) // => false */ matchesProperties(properties: Record): boolean { - return this._predicate(properties[this._key]) + return this._predicate(properties[this.key]) } usedKeys(): string[] { - return [this._key] + return [this.key] } usedTags(): { key: string; value: string }[] { return [] } - asJson(): TagConfigJson { - return this._key + this._representation + /** + * import { TagUtils } from "../../../src/Logic/Tags/TagUtils" + * + * TagUtils.Tag("count>42").asJson() // => "count>42" + * TagUtils.Tag("count<0").asJson() // => "count<0" + */ + asJson(): string { + return this.key + this._representation + this._boundary } optimize(): TagsFilter | boolean { @@ -124,11 +129,11 @@ export default class ComparingTag extends TagsFilter { return true } - visit(f: (TagsFilter) => void) { + visit(f: (tf: TagsFilter) => void) { f(this) } asMapboxExpression(): ExpressionSpecification { - return [this._representation, ["get", this._key], this._boundary] + return [this._representation, ["get", this.key], this._boundary] } } diff --git a/src/Logic/Tags/RegexTag.ts b/src/Logic/Tags/RegexTag.ts index 804913039..549266be6 100644 --- a/src/Logic/Tags/RegexTag.ts +++ b/src/Logic/Tags/RegexTag.ts @@ -259,6 +259,13 @@ export class RegexTag extends TagsFilter { * new RegexTag("key",/^..*$/, true).shadows(new Tag("key","")) // => true * new RegexTag("key","value", true).shadows(new Tag("key","value")) // => false * new RegexTag("key","value", true).shadows(new Tag("key","some_other_value")) // => false + * new RegexTag("key","value", true).shadows(new Tag("key","some_other_value", true)) // => false + * + * const route = TagUtils.Tag("climbing!~route") + * const routeBottom = TagUtils.Tag("climbing!~route_bottom") + * route.shadows(routeBottom) // => false + * routeBottom.shadows(route) // => false + * */ shadows(other: TagsFilter): boolean { if (other instanceof RegexTag) { @@ -267,7 +274,7 @@ export class RegexTag extends TagsFilter { return false } if ( - (other.value["source"] ?? other.key) === (this.value["source"] ?? this.key) && + (other.value["source"] ?? other.value) === (this.value["source"] ?? this.value) && this.invert == other.invert ) { // Values (and inverts) match diff --git a/src/Logic/Tags/Tag.ts b/src/Logic/Tags/Tag.ts index 67a93efdb..edd3b3c82 100644 --- a/src/Logic/Tags/Tag.ts +++ b/src/Logic/Tags/Tag.ts @@ -2,6 +2,7 @@ import { Utils } from "../../Utils" import { TagsFilter } from "./TagsFilter" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" import { ExpressionSpecification } from "maplibre-gl" +import { RegexTag } from "./RegexTag" export class Tag extends TagsFilter { public key: string @@ -122,6 +123,7 @@ export class Tag extends TagsFilter { /** * * import {RegexTag} from "./RegexTag"; + * import {And} from "./And"; * * // should handle advanced regexes * new Tag("key", "aaa").shadows(new RegexTag("key", /a+/)) // => true @@ -131,14 +133,20 @@ export class Tag extends TagsFilter { * new Tag("key","value").shadows(new RegexTag("key", "value", true)) // => false * new Tag("key","value").shadows(new RegexTag("otherkey", "value", true)) // => false * new Tag("key","value").shadows(new RegexTag("otherkey", "value", false)) // => false + * new Tag("key","value").shadows(new And([new Tag("x","y"), new RegexTag("a","b", true)]) // => false */ shadows(other: TagsFilter): boolean { - if (other["key"] !== undefined) { - if (other["key"] !== this.key) { - return false - } + if ((other["key"] !== this.key)) { + return false } - return other.matchesProperties({ [this.key]: this.value }) + if(other instanceof Tag){ + // Other.key === this.key + return other.value === this.value + } + if(other instanceof RegexTag){ + return other.matchesProperties({[this.key]: this.value}) + } + return false } usedKeys(): string[] {