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) {
|
||||
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[] {
|
||||
|
|
Loading…
Reference in a new issue