forked from MapComplete/MapComplete
		
	Performance: add some optimizations and tag fixes
This commit is contained in:
		
							parent
							
								
									c46e9da511
								
							
						
					
					
						commit
						fd1b6d3131
					
				
					 4 changed files with 88 additions and 21 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
        if ((other["key"] !== this.key)) {
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
        if(other instanceof Tag){
 | 
			
		||||
            // Other.key === this.key
 | 
			
		||||
            return other.value === this.value
 | 
			
		||||
        }
 | 
			
		||||
        return other.matchesProperties({ [this.key]: this.value })
 | 
			
		||||
        if(other instanceof RegexTag){
 | 
			
		||||
            return other.matchesProperties({[this.key]: this.value})
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    usedKeys(): string[] {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue