Performance: add some optimizations and tag fixes

This commit is contained in:
Pieter Vander Vennet 2024-05-07 17:04:07 +02:00
parent c46e9da511
commit fd1b6d3131
4 changed files with 88 additions and 21 deletions

View file

@ -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 = <TagsFilter[]>optimizedRaw
for (let i = 0; i <optimized.length; i++) {
for (let j = i + 1; j < optimized.length; j++) {
const ti = optimized[i]
const tj = optimized[j]
if(ti.shadows(tj)){
// if 'ti' is true, this implies 'tj' is always true as well.
// if 'ti' is false, then 'tj' might be true or false
// (e.g. let 'ti' be 'count>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<string, string> = {}
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
}
}
}
}

View file

@ -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<string, string>): 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]
}
}

View file

@ -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

View file

@ -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[] {