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 { RegexTag } from "./RegexTag"
|
||||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||||
import { ExpressionSpecification } from "maplibre-gl"
|
import { ExpressionSpecification } from "maplibre-gl"
|
||||||
|
import ComparingTag from "./ComparingTag"
|
||||||
|
|
||||||
export class And extends TagsFilter {
|
export class And extends TagsFilter {
|
||||||
public and: TagsFilter[]
|
public and: TagsFilter[]
|
||||||
|
@ -242,6 +243,27 @@ export class And extends TagsFilter {
|
||||||
* const raw = {"and": [{"and":["advertising=screen"]}, {"and":["advertising~*"]}]}]
|
* const raw = {"and": [{"and":["advertising=screen"]}, {"and":["advertising~*"]}]}]
|
||||||
* const parsed = TagUtils.Tag(raw)
|
* const parsed = TagUtils.Tag(raw)
|
||||||
* parsed.optimize().asJson() // => "advertising=screen"
|
* 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 {
|
optimize(): TagsFilter | boolean {
|
||||||
if (this.and.length === 0) {
|
if (this.and.length === 0) {
|
||||||
|
@ -256,9 +278,30 @@ export class And extends TagsFilter {
|
||||||
}
|
}
|
||||||
const optimized = <TagsFilter[]>optimizedRaw
|
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
|
// Conflicting keys do return false
|
||||||
const properties: object = {}
|
const properties: Record<string, string> = {}
|
||||||
for (const opt of optimized) {
|
for (const opt of optimized) {
|
||||||
if (opt instanceof Tag) {
|
if (opt instanceof Tag) {
|
||||||
properties[opt.key] = opt.value
|
properties[opt.key] = opt.value
|
||||||
|
@ -277,8 +320,7 @@ export class And extends TagsFilter {
|
||||||
// detected an internal conflict
|
// detected an internal conflict
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
} else if (opt instanceof RegexTag) {
|
||||||
if (opt instanceof RegexTag) {
|
|
||||||
const k = opt.key
|
const k = opt.key
|
||||||
if (typeof k !== "string") {
|
if (typeof k !== "string") {
|
||||||
continue
|
continue
|
||||||
|
@ -316,6 +358,11 @@ export class And extends TagsFilter {
|
||||||
i--
|
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 { TagsFilter } from "./TagsFilter"
|
||||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
|
||||||
import { Tag } from "./Tag"
|
import { Tag } from "./Tag"
|
||||||
import { ExpressionSpecification } from "maplibre-gl"
|
import { ExpressionSpecification } from "maplibre-gl"
|
||||||
|
|
||||||
export default class ComparingTag extends TagsFilter {
|
export default class ComparingTag extends TagsFilter {
|
||||||
private readonly _key: string
|
public readonly key: string
|
||||||
private readonly _predicate: (value: string) => boolean
|
private readonly _predicate: (value: string) => boolean
|
||||||
private readonly _representation: "<" | ">" | "<=" | ">="
|
private readonly _representation: "<" | ">" | "<=" | ">="
|
||||||
private readonly _boundary: string
|
private readonly _boundary: string
|
||||||
|
@ -16,7 +15,7 @@ export default class ComparingTag extends TagsFilter {
|
||||||
boundary: string
|
boundary: string
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this._key = key
|
this.key = key
|
||||||
this._predicate = predicate
|
this._predicate = predicate
|
||||||
this._representation = representation
|
this._representation = representation
|
||||||
this._boundary = boundary
|
this._boundary = boundary
|
||||||
|
@ -27,7 +26,7 @@ export default class ComparingTag extends TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString() {
|
||||||
return this._key + this._representation + this._boundary
|
return this.asJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
asOverpass(): string[] {
|
asOverpass(): string[] {
|
||||||
|
@ -55,7 +54,7 @@ export default class ComparingTag extends TagsFilter {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (other instanceof ComparingTag) {
|
if (other instanceof ComparingTag) {
|
||||||
if (other._key !== this._key) {
|
if (other.key !== this.key) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const selfDesc = this._representation === "<" || this._representation === "<="
|
const selfDesc = this._representation === "<" || this._representation === "<="
|
||||||
|
@ -76,7 +75,7 @@ export default class ComparingTag extends TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (other instanceof Tag) {
|
if (other instanceof Tag) {
|
||||||
if (other.key !== this._key) {
|
if (other.key !== this.key) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.matchesProperties({ [other.key]: other.value })) {
|
if (this.matchesProperties({ [other.key]: other.value })) {
|
||||||
|
@ -101,19 +100,25 @@ export default class ComparingTag extends TagsFilter {
|
||||||
* t.matchesProperties({differentKey: 42}) // => false
|
* t.matchesProperties({differentKey: 42}) // => false
|
||||||
*/
|
*/
|
||||||
matchesProperties(properties: Record<string, string>): boolean {
|
matchesProperties(properties: Record<string, string>): boolean {
|
||||||
return this._predicate(properties[this._key])
|
return this._predicate(properties[this.key])
|
||||||
}
|
}
|
||||||
|
|
||||||
usedKeys(): string[] {
|
usedKeys(): string[] {
|
||||||
return [this._key]
|
return [this.key]
|
||||||
}
|
}
|
||||||
|
|
||||||
usedTags(): { key: string; value: string }[] {
|
usedTags(): { key: string; value: string }[] {
|
||||||
return []
|
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 {
|
optimize(): TagsFilter | boolean {
|
||||||
|
@ -124,11 +129,11 @@ export default class ComparingTag extends TagsFilter {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(f: (TagsFilter) => void) {
|
visit(f: (tf: TagsFilter) => void) {
|
||||||
f(this)
|
f(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
asMapboxExpression(): ExpressionSpecification {
|
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",/^..*$/, 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","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")) // => 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 {
|
shadows(other: TagsFilter): boolean {
|
||||||
if (other instanceof RegexTag) {
|
if (other instanceof RegexTag) {
|
||||||
|
@ -267,7 +274,7 @@ export class RegexTag extends TagsFilter {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (
|
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
|
this.invert == other.invert
|
||||||
) {
|
) {
|
||||||
// Values (and inverts) match
|
// Values (and inverts) match
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Utils } from "../../Utils"
|
||||||
import { TagsFilter } from "./TagsFilter"
|
import { TagsFilter } from "./TagsFilter"
|
||||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||||
import { ExpressionSpecification } from "maplibre-gl"
|
import { ExpressionSpecification } from "maplibre-gl"
|
||||||
|
import { RegexTag } from "./RegexTag"
|
||||||
|
|
||||||
export class Tag extends TagsFilter {
|
export class Tag extends TagsFilter {
|
||||||
public key: string
|
public key: string
|
||||||
|
@ -122,6 +123,7 @@ export class Tag extends TagsFilter {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* import {RegexTag} from "./RegexTag";
|
* import {RegexTag} from "./RegexTag";
|
||||||
|
* import {And} from "./And";
|
||||||
*
|
*
|
||||||
* // should handle advanced regexes
|
* // should handle advanced regexes
|
||||||
* new Tag("key", "aaa").shadows(new RegexTag("key", /a+/)) // => true
|
* 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("key", "value", true)) // => false
|
||||||
* new Tag("key","value").shadows(new RegexTag("otherkey", "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 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 {
|
shadows(other: TagsFilter): boolean {
|
||||||
if (other["key"] !== undefined) {
|
if ((other["key"] !== this.key)) {
|
||||||
if (other["key"] !== this.key) {
|
|
||||||
return false
|
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[] {
|
usedKeys(): string[] {
|
||||||
|
|
Loading…
Reference in a new issue