Fix naughty bug in tag optimization by adding better typing
This commit is contained in:
parent
b98245fafb
commit
fd16e165c4
11 changed files with 223 additions and 93 deletions
|
@ -6,6 +6,7 @@ import { RegexTag } from "./RegexTag"
|
|||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { ExpressionSpecification } from "maplibre-gl"
|
||||
import ComparingTag from "./ComparingTag"
|
||||
import { FlatTag, OptimizedTag, TagsFilterClosed, TagTypes } from "./TagTypes"
|
||||
|
||||
export class And extends TagsFilter {
|
||||
public and: TagsFilter[]
|
||||
|
@ -15,6 +16,8 @@ export class And extends TagsFilter {
|
|||
this.and = and
|
||||
}
|
||||
|
||||
public static construct(and: TagsFilter[]): TagsFilter
|
||||
public static construct(and: (FlatTag | (Or & OptimizedTag))[]): TagsFilterClosed & OptimizedTag
|
||||
public static construct(and: TagsFilter[]): TagsFilter {
|
||||
if (and.length === 1) {
|
||||
return and[0]
|
||||
|
@ -175,6 +178,10 @@ export class And extends TagsFilter {
|
|||
* When the evaluation hits (A=B & X=Y), we know _for sure_ that X=Y does _not_ match, as it would have matched the first clause otherwise.
|
||||
* This means that the entire 'AND' is considered FALSE
|
||||
*
|
||||
* @return only phrases that should be kept.
|
||||
* @param knownExpression The expression which is known in the subexpression and for which calculations can be done
|
||||
* @param value the given knownExpression is considered to have this value, namely 'true' or 'false'
|
||||
*
|
||||
* new And([ new Tag("key","value") ,new Tag("other_key","value")]).removePhraseConsideredKnown(new Tag("key","value"), true) // => new Tag("other_key","value")
|
||||
* new And([ new Tag("key","value") ,new Tag("other_key","value")]).removePhraseConsideredKnown(new Tag("key","value"), false) // => false
|
||||
* new And([ new RegexTag("key",/^..*$/) ,new Tag("other_key","value")]).removePhraseConsideredKnown(new Tag("key","value"), true) // => new Tag("other_key","value")
|
||||
|
@ -187,13 +194,14 @@ export class And extends TagsFilter {
|
|||
* const expr = <And> TagUtils.Tag({and: ["sport=climbing", {or:["club~*", "office~*"]}]} )
|
||||
* expr.removePhraseConsideredKnown(new Tag("club","climbing"), false) // => expr
|
||||
*/
|
||||
removePhraseConsideredKnown(knownExpression: TagsFilter, value: boolean): TagsFilter | boolean {
|
||||
removePhraseConsideredKnown(knownExpression: TagsFilter, value: boolean): (TagsFilterClosed & OptimizedTag) | boolean {
|
||||
const newAnds: TagsFilter[] = []
|
||||
for (const tag of this.and) {
|
||||
if (tag instanceof And) {
|
||||
throw "Optimize expressions before using removePhraseConsideredKnown. Found an AND in an AND: "+this.asHumanString()
|
||||
throw "Optimize expressions before using removePhraseConsideredKnown. Found an AND in an AND: " + this.asHumanString()
|
||||
}
|
||||
if (tag instanceof Or) {
|
||||
// Second try
|
||||
const r = tag.removePhraseConsideredKnown(knownExpression, value)
|
||||
if (r === true) {
|
||||
continue
|
||||
|
@ -232,7 +240,7 @@ export class And extends TagsFilter {
|
|||
if (newAnds.length === 0) {
|
||||
return true
|
||||
}
|
||||
return And.construct(newAnds)
|
||||
return And.construct(newAnds).optimize()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -265,7 +273,7 @@ export class And extends TagsFilter {
|
|||
* const parsed = TagUtils.Tag(orig)
|
||||
* parsed.optimize().asJson() // => orig
|
||||
*/
|
||||
optimize(): TagsFilter | boolean {
|
||||
optimize(): (TagsFilterClosed & OptimizedTag) | boolean {
|
||||
if (this.and.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
@ -276,7 +284,7 @@ export class And extends TagsFilter {
|
|||
// We have an AND with a contained false: this is always 'false'
|
||||
return false
|
||||
}
|
||||
const optimized = <TagsFilter[]>optimizedRaw
|
||||
const optimized = <(TagsFilterClosed & OptimizedTag)[]>optimizedRaw
|
||||
|
||||
for (let i = 0; i < optimized.length; i++) {
|
||||
for (let j = i + 1; j < optimized.length; j++) {
|
||||
|
@ -299,7 +307,7 @@ export class And extends TagsFilter {
|
|||
}
|
||||
|
||||
{
|
||||
// Conflicting keys do return false
|
||||
// Conflicting keys do return false. We build a 'known' set and check for conflicts
|
||||
const properties: Record<string, string> = {}
|
||||
for (const opt of optimized) {
|
||||
if (opt instanceof Tag) {
|
||||
|
@ -366,12 +374,12 @@ export class And extends TagsFilter {
|
|||
}
|
||||
}
|
||||
|
||||
const newAnds: TagsFilter[] = []
|
||||
const newAnds: (FlatTag | (Or & OptimizedTag))[] = []
|
||||
let containedOrs: (Or & OptimizedTag)[] = []
|
||||
|
||||
let containedOrs: Or[] = []
|
||||
for (const tf of optimized) {
|
||||
if (tf instanceof And) {
|
||||
newAnds.push(...tf.and)
|
||||
newAnds.push(...TagTypes.safeAnd(tf))
|
||||
} else if (tf instanceof Or) {
|
||||
containedOrs.push(tf)
|
||||
} else {
|
||||
|
@ -382,10 +390,10 @@ export class And extends TagsFilter {
|
|||
{
|
||||
let dirty = false
|
||||
do {
|
||||
const cleanedContainedOrs: Or[] = []
|
||||
const cleanedContainedOrs: (Or & OptimizedTag)[] = []
|
||||
outer: for (let containedOr of containedOrs) {
|
||||
for (const known of newAnds) {
|
||||
// input for optimazation: (K=V & (X=Y | K=V))
|
||||
// input for optimization: (K=V & (X=Y | K=V))
|
||||
// containedOr: (X=Y | K=V)
|
||||
// newAnds (and thus known): (K=V) --> true
|
||||
const cleaned = containedOr.removePhraseConsideredKnown(known, true)
|
||||
|
@ -401,11 +409,17 @@ export class And extends TagsFilter {
|
|||
containedOr = cleaned
|
||||
continue
|
||||
}
|
||||
if (cleaned instanceof And) {
|
||||
// An optimized 'And' should not contain 'Ands', we can safely cast
|
||||
newAnds.push(...TagTypes.safeAnd(cleaned))
|
||||
continue
|
||||
}
|
||||
// the 'or' dissolved into a normal tag -> it has to be added to the newAnds
|
||||
newAnds.push(cleaned)
|
||||
dirty = true // rerun this algo later on
|
||||
continue outer
|
||||
}
|
||||
|
||||
cleanedContainedOrs.push(containedOr)
|
||||
}
|
||||
containedOrs = cleanedContainedOrs
|
||||
|
@ -433,9 +447,9 @@ export class And extends TagsFilter {
|
|||
if (commonValues.length === 0) {
|
||||
newAnds.push(...containedOrs)
|
||||
} else {
|
||||
const newOrs: TagsFilter[] = []
|
||||
const newOrs: TagsFilterClosed[] = []
|
||||
for (const containedOr of containedOrs) {
|
||||
const elements = containedOr.or.filter(
|
||||
const elements: (FlatTag | (And & OptimizedTag))[] = TagTypes.safeOr( containedOr).filter(
|
||||
(candidate) => !commonValues.some((cv) => cv.shadows(candidate))
|
||||
)
|
||||
if (elements.length > 0) {
|
||||
|
@ -450,6 +464,8 @@ export class And extends TagsFilter {
|
|||
return false
|
||||
} else if (result === true) {
|
||||
// neutral element: skip
|
||||
}else if(result instanceof And) {
|
||||
newAnds.push(...TagTypes.safeAnd(result))
|
||||
} else {
|
||||
newAnds.push(result)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TagsFilter } from "./TagsFilter"
|
||||
import { Tag } from "./Tag"
|
||||
import { ExpressionSpecification } from "maplibre-gl"
|
||||
import { OptimizedTag } from "./TagTypes"
|
||||
|
||||
export default class ComparingTag extends TagsFilter {
|
||||
public readonly key: string
|
||||
|
@ -121,8 +122,8 @@ export default class ComparingTag extends TagsFilter {
|
|||
return this.key + this._representation + this._boundary
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this
|
||||
optimize(): (ComparingTag & OptimizedTag) | boolean {
|
||||
return <any> this
|
||||
}
|
||||
|
||||
isNegative(): boolean {
|
||||
|
|
|
@ -3,6 +3,11 @@ import { TagUtils } from "./TagUtils"
|
|||
import { And } from "./And"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { ExpressionSpecification } from "maplibre-gl"
|
||||
import { Tag } from "./Tag"
|
||||
import { RegexTag } from "./RegexTag"
|
||||
import SubstitutingTag from "./SubstitutingTag"
|
||||
import ComparingTag from "./ComparingTag"
|
||||
import { FlatTag, OptimizedTag, TagsFilterClosed, TagTypes } from "./TagTypes"
|
||||
|
||||
export class Or extends TagsFilter {
|
||||
public or: TagsFilter[]
|
||||
|
@ -12,6 +17,9 @@ export class Or extends TagsFilter {
|
|||
this.or = or
|
||||
}
|
||||
|
||||
public static construct(or: TagsFilter[]): TagsFilter
|
||||
public static construct<T extends TagsFilter>(or: [T]): T
|
||||
public static construct(or: ((And & OptimizedTag) | FlatTag)[]): (TagsFilterClosed & OptimizedTag)
|
||||
public static construct(or: TagsFilter[]): TagsFilter {
|
||||
if (or.length === 1) {
|
||||
return or[0]
|
||||
|
@ -59,7 +67,7 @@ export class Or extends TagsFilter {
|
|||
return this.or
|
||||
.map((t) => {
|
||||
let e = t.asHumanString(linkToWiki, shorten, properties)
|
||||
if (t["and"]) {
|
||||
if (t["and"] || t["or"]) {
|
||||
e = "(" + e + ")"
|
||||
}
|
||||
return e
|
||||
|
@ -115,13 +123,13 @@ export class Or extends TagsFilter {
|
|||
* new Or([ new Tag("key","value") ,new Tag("other_key","value")]).removePhraseConsideredKnown(new Tag("key","value"), false) // => new Tag("other_key","value")
|
||||
* new Or([ new Tag("key","value") ]).removePhraseConsideredKnown(new Tag("key","value"), true) // => true
|
||||
* new Or([ new Tag("key","value") ]).removePhraseConsideredKnown(new Tag("key","value"), false) // => false
|
||||
* new Or([new RegexTag("x", "y", true),new RegexTag("c", "d")]).removePhraseConsideredKnown(new Tag("foo","bar"), false) // => new Or([new RegexTag("x", "y", true),new RegexTag("c", "d")])
|
||||
* new Or([new RegexTag("x", "y", true),new RegexTag("c", "d")]).removePhraseConsideredKnown(new Tag("foo","bar"), false) // => new Or([new RegexTag("c", "d"), new RegexTag("x", "y", true)])
|
||||
*/
|
||||
removePhraseConsideredKnown(knownExpression: TagsFilter, value: boolean): TagsFilter | boolean {
|
||||
removePhraseConsideredKnown(knownExpression: TagsFilter, value: boolean): (TagsFilterClosed & OptimizedTag) | boolean {
|
||||
const newOrs: TagsFilter[] = []
|
||||
for (const tag of this.or) {
|
||||
if (tag instanceof Or) {
|
||||
throw "Optimize expressions before using removePhraseConsideredKnown. Found an OR in an OR: "+this.asHumanString(false, false, {})
|
||||
throw "Optimize expressions before using removePhraseConsideredKnown. Found an OR in an OR: " + this.asHumanString()
|
||||
}
|
||||
if (tag instanceof And) {
|
||||
const r = tag.removePhraseConsideredKnown(knownExpression, value)
|
||||
|
@ -160,7 +168,7 @@ export class Or extends TagsFilter {
|
|||
if (newOrs.length === 0) {
|
||||
return false
|
||||
}
|
||||
return Or.construct(newOrs)
|
||||
return Or.construct(newOrs).optimize()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,7 +177,7 @@ export class Or extends TagsFilter {
|
|||
* parsed.optimize().asJson() // => {"and":["leisure=playground","playground!=forest"]}
|
||||
*
|
||||
*/
|
||||
optimize(): TagsFilter | boolean {
|
||||
optimize(): (TagsFilterClosed & OptimizedTag) | boolean {
|
||||
if (this.or.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
@ -181,26 +189,28 @@ export class Or extends TagsFilter {
|
|||
// We have an OR with a contained true: this is always 'true'
|
||||
return true
|
||||
}
|
||||
const optimized = <TagsFilter[]>optimizedRaw
|
||||
const optimized = optimizedRaw
|
||||
|
||||
const newOrs: TagsFilter[] = []
|
||||
let containedAnds: And[] = []
|
||||
const newOrs: ((And & OptimizedTag) | FlatTag)[] = []
|
||||
let containedAnds: (And & OptimizedTag)[] = []
|
||||
for (const tf of optimized) {
|
||||
if (tf["or"]) {
|
||||
if (tf instanceof Or) {
|
||||
// expand all the nested ors...
|
||||
newOrs.push(...tf["or"])
|
||||
for (const clauseOr of TagTypes.safeOr(tf)) {
|
||||
newOrs.push(clauseOr)
|
||||
}
|
||||
} else if (tf instanceof And) {
|
||||
// partition of all the ands
|
||||
containedAnds.push(tf)
|
||||
} else {
|
||||
newOrs.push(tf)
|
||||
newOrs.push(<(Tag | (And & OptimizedTag) | RegexTag | SubstitutingTag | ComparingTag)>tf)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let dirty = false
|
||||
do {
|
||||
const cleanedContainedANds: And[] = []
|
||||
const cleanedContainedANds: (And & OptimizedTag)[] = []
|
||||
outer: for (let containedAnd of containedAnds) {
|
||||
for (const known of newOrs) {
|
||||
// input for optimization: (K=V | (X=Y & K=V))
|
||||
|
@ -219,8 +229,16 @@ export class Or extends TagsFilter {
|
|||
containedAnd = cleaned
|
||||
continue // clean up with the other known values
|
||||
}
|
||||
|
||||
if(cleaned instanceof Or){
|
||||
// An optimized 'or' should not contain 'ors', we can safely cast
|
||||
newOrs.push(...TagTypes.safeOr(cleaned))
|
||||
continue
|
||||
}
|
||||
|
||||
const noAnd: OptimizedTag & (Tag | RegexTag | SubstitutingTag | ComparingTag) = cleaned
|
||||
// the 'and' dissolved into a normal tag -> it has to be added to the newOrs
|
||||
newOrs.push(cleaned)
|
||||
newOrs.push(noAnd)
|
||||
dirty = true // rerun this algo later on
|
||||
continue outer
|
||||
}
|
||||
|
@ -243,16 +261,16 @@ export class Or extends TagsFilter {
|
|||
if (commonValues.length === 0) {
|
||||
newOrs.push(...containedAnds)
|
||||
} else {
|
||||
const newAnds: TagsFilter[] = []
|
||||
const newAnds: TagsFilterClosed[] = []
|
||||
for (const containedAnd of containedAnds) {
|
||||
const elements = containedAnd.and.filter(
|
||||
const elements: (FlatTag | (Or & OptimizedTag))[] = TagTypes.safeAnd(containedAnd).filter(
|
||||
(candidate) => !commonValues.some((cv) => cv.shadows(candidate))
|
||||
)
|
||||
if (elements.length == 0) {
|
||||
continue
|
||||
}
|
||||
if (elements.length > 0) {
|
||||
newAnds.push(And.construct(elements))
|
||||
}
|
||||
}
|
||||
|
||||
if (newAnds.length > 0) {
|
||||
commonValues.push(Or.construct(newAnds))
|
||||
}
|
||||
|
@ -262,9 +280,10 @@ export class Or extends TagsFilter {
|
|||
return true
|
||||
} else if (result === false) {
|
||||
// neutral element: skip
|
||||
} else if (commonValues.length > 0) {
|
||||
newOrs.push(And.construct(commonValues))
|
||||
}
|
||||
} else if(result instanceof Or){
|
||||
newOrs.push(...TagTypes.safeOr(result))
|
||||
}else
|
||||
newOrs.push(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Tag } from "./Tag"
|
|||
import { TagsFilter } from "./TagsFilter"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { ExpressionSpecification } from "maplibre-gl"
|
||||
import { OptimizedTag } from "./TagTypes"
|
||||
|
||||
export class RegexTag extends TagsFilter {
|
||||
public readonly key: RegExp | string
|
||||
|
@ -354,8 +355,8 @@ export class RegexTag extends TagsFilter {
|
|||
return []
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this
|
||||
optimize(): (RegexTag & OptimizedTag) | boolean {
|
||||
return <any> this
|
||||
}
|
||||
|
||||
isNegative(): boolean {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Tag } from "./Tag"
|
|||
import { Utils } from "../../Utils"
|
||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { ExpressionSpecification } from "maplibre-gl"
|
||||
import { OptimizedTag } from "./TagTypes"
|
||||
|
||||
/**
|
||||
* The substituting-tag uses the tags of a feature a variables and replaces them.
|
||||
|
@ -111,8 +112,8 @@ export default class SubstitutingTag extends TagsFilter {
|
|||
return [{ k: this._key, v: v }]
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this
|
||||
optimize(): (SubstitutingTag & OptimizedTag) | boolean {
|
||||
return <any> this
|
||||
}
|
||||
|
||||
isNegative(): boolean {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { TagsFilter } from "./TagsFilter"
|
|||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { ExpressionSpecification } from "maplibre-gl"
|
||||
import { RegexTag } from "./RegexTag"
|
||||
import { OptimizedTag } from "./TagTypes"
|
||||
|
||||
export class Tag extends TagsFilter {
|
||||
public key: string
|
||||
|
@ -66,7 +67,7 @@ export class Tag extends TagsFilter {
|
|||
asOverpass(): string[] {
|
||||
if (this.value === "") {
|
||||
// NOT having this key
|
||||
return ['[!"' + this.key + '"]']
|
||||
return ["[!\"" + this.key + "\"]"]
|
||||
}
|
||||
return [`["${this.key}"="${this.value}"]`]
|
||||
}
|
||||
|
@ -164,8 +165,8 @@ export class Tag extends TagsFilter {
|
|||
return [{ k: this.key, v: this.value }]
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this
|
||||
optimize(): (Tag & OptimizedTag) | boolean {
|
||||
return <any>this
|
||||
}
|
||||
|
||||
isNegative(): boolean {
|
||||
|
|
35
src/Logic/Tags/TagTypes.ts
Normal file
35
src/Logic/Tags/TagTypes.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { TagsFilter } from "./TagsFilter"
|
||||
import { Tag } from "./Tag"
|
||||
import SubstitutingTag from "./SubstitutingTag"
|
||||
import { And } from "./And"
|
||||
import { RegexTag } from "./RegexTag"
|
||||
import ComparingTag from "./ComparingTag"
|
||||
import { Or } from "./Or"
|
||||
|
||||
declare const __is_optimized: unique symbol
|
||||
type Brand<B> = { [__is_optimized]: B }
|
||||
/**
|
||||
* A marker class, no actual content
|
||||
*/
|
||||
export type OptimizedTag = Brand<TagsFilter>
|
||||
|
||||
|
||||
export type UploadableTag = Tag | SubstitutingTag | And
|
||||
/**
|
||||
* Not nested
|
||||
*/
|
||||
export type FlatTag = Tag | RegexTag | SubstitutingTag | ComparingTag
|
||||
export type TagsFilterClosed = FlatTag | And | Or
|
||||
|
||||
|
||||
export class TagTypes {
|
||||
|
||||
static safeAnd(and: And & OptimizedTag): ((FlatTag | (Or & OptimizedTag)) & OptimizedTag)[]{
|
||||
return <any> and.and
|
||||
}
|
||||
|
||||
static safeOr(or: Or & OptimizedTag): ((FlatTag | (And & OptimizedTag)) & OptimizedTag)[]{
|
||||
return <any> or.or
|
||||
}
|
||||
|
||||
}
|
|
@ -10,9 +10,9 @@ import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
|||
import key_counts from "../../assets/key_totals.json"
|
||||
|
||||
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"
|
||||
import { TagsFilterClosed, UploadableTag } from "./TagTypes"
|
||||
|
||||
type Tags = Record<string, string>
|
||||
export type UploadableTag = Tag | SubstitutingTag | And
|
||||
|
||||
export class TagUtils {
|
||||
public static readonly comparators: ReadonlyArray<
|
||||
|
@ -476,7 +476,7 @@ export class TagUtils {
|
|||
* regex.matchesProperties({maxspeed: "50 mph"}) // => true
|
||||
*/
|
||||
|
||||
public static Tag(json: TagConfigJson, context: string | ConversionContext = ""): TagsFilter {
|
||||
public static Tag(json: TagConfigJson, context: string | ConversionContext = ""): TagsFilterClosed {
|
||||
try {
|
||||
const ctx = typeof context === "string" ? context : context.path.join(".")
|
||||
return this.ParseTagUnsafe(json, ctx)
|
||||
|
@ -712,7 +712,7 @@ export class TagUtils {
|
|||
return Utils.NoNull(spec)
|
||||
}
|
||||
|
||||
private static ParseTagUnsafe(json: TagConfigJson, context: string = ""): TagsFilter {
|
||||
private static ParseTagUnsafe(json: TagConfigJson, context: string = ""): TagsFilterClosed {
|
||||
if (json === undefined) {
|
||||
throw new Error(
|
||||
`Error while parsing a tag: 'json' is undefined in ${context}. Make sure all the tags are defined and at least one tag is present in a complex expression`
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { ExpressionSpecification } from "maplibre-gl"
|
||||
import { OptimizedTag, TagsFilterClosed } from "./TagTypes"
|
||||
|
||||
export abstract class TagsFilter {
|
||||
abstract asOverpass(): string[]
|
||||
|
@ -50,7 +51,7 @@ export abstract class TagsFilter {
|
|||
/**
|
||||
* Returns an optimized version (or self) of this tagsFilter
|
||||
*/
|
||||
abstract optimize(): TagsFilter | boolean
|
||||
abstract optimize(): (OptimizedTag & TagsFilterClosed) | boolean
|
||||
|
||||
/**
|
||||
* Returns 'true' if the tagsfilter might select all features (i.e. the filter will return everything from OSM, except a few entries).
|
||||
|
|
|
@ -31,7 +31,7 @@ describe("Tag optimalization", () => {
|
|||
const t = new And([
|
||||
new Tag("foo", "bar"),
|
||||
new Or([new Tag("x", "y"), new Tag("a", "b")]),
|
||||
new Or([new Tag("x", "y"), new Tag("c", "d")]),
|
||||
new Or([new Tag("x", "y"), new Tag("c", "d")])
|
||||
])
|
||||
const opt = <TagsFilter>t.optimize()
|
||||
expect(TagUtils.toString(opt)).toBe("foo=bar& (x=y| (a=b&c=d) )")
|
||||
|
@ -42,7 +42,7 @@ describe("Tag optimalization", () => {
|
|||
const t = new And([
|
||||
new Tag("foo", "bar"),
|
||||
new Or([new RegexTag("x", "y"), new RegexTag("a", "b")]),
|
||||
new Or([new RegexTag("x", "y"), new RegexTag("c", "d")]),
|
||||
new Or([new RegexTag("x", "y"), new RegexTag("c", "d")])
|
||||
])
|
||||
const opt = <TagsFilter>t.optimize()
|
||||
expect(TagUtils.toString(opt)).toBe("foo=bar& ( (a=b&c=d) |x=y)")
|
||||
|
@ -53,7 +53,7 @@ describe("Tag optimalization", () => {
|
|||
const t = new And([
|
||||
new Tag("foo", "bar"),
|
||||
new Or([new RegexTag("x", "y"), new RegexTag("a", "b")]),
|
||||
new Or([new RegexTag("x", "y", true), new RegexTag("c", "d")]),
|
||||
new Or([new RegexTag("x", "y", true), new RegexTag("c", "d")])
|
||||
])
|
||||
const opt = <TagsFilter>t.optimize()
|
||||
expect(TagUtils.toString(opt)).toBe("foo=bar& (a=b|x=y) & (c=d|x!=y)")
|
||||
|
@ -86,12 +86,12 @@ describe("Tag optimalization", () => {
|
|||
{
|
||||
and: [
|
||||
{
|
||||
or: ["X=Y", "FOO=BAR"],
|
||||
or: ["X=Y", "FOO=BAR"]
|
||||
},
|
||||
"bicycle=yes",
|
||||
],
|
||||
},
|
||||
],
|
||||
"bicycle=yes"
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
// (X=Y | FOO=BAR | (bicycle=yes & (X=Y | FOO=BAR)) )
|
||||
// This is equivalent to (X=Y | FOO=BAR)
|
||||
|
@ -109,11 +109,11 @@ describe("Tag optimalization", () => {
|
|||
"amenity=charging_station",
|
||||
"disused:amenity=charging_station",
|
||||
"planned:amenity=charging_station",
|
||||
"construction:amenity=charging_station",
|
||||
],
|
||||
"construction:amenity=charging_station"
|
||||
]
|
||||
},
|
||||
"bicycle=yes",
|
||||
],
|
||||
"bicycle=yes"
|
||||
]
|
||||
},
|
||||
{
|
||||
and: [
|
||||
|
@ -122,19 +122,19 @@ describe("Tag optimalization", () => {
|
|||
"amenity=charging_station",
|
||||
"disused:amenity=charging_station",
|
||||
"planned:amenity=charging_station",
|
||||
"construction:amenity=charging_station",
|
||||
],
|
||||
},
|
||||
],
|
||||
"construction:amenity=charging_station"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"amenity=toilets",
|
||||
"amenity=bench",
|
||||
"leisure=picnic_table",
|
||||
{
|
||||
and: ["tower:type=observation"],
|
||||
and: ["tower:type=observation"]
|
||||
},
|
||||
{
|
||||
and: ["amenity=bicycle_repair_station"],
|
||||
and: ["amenity=bicycle_repair_station"]
|
||||
},
|
||||
{
|
||||
and: [
|
||||
|
@ -143,16 +143,16 @@ describe("Tag optimalization", () => {
|
|||
"amenity=bicycle_rental",
|
||||
"bicycle_rental~*",
|
||||
"service:bicycle:rental=yes",
|
||||
"rental~.*bicycle.*",
|
||||
],
|
||||
"rental~.*bicycle.*"
|
||||
]
|
||||
},
|
||||
"bicycle_rental!=docking_station",
|
||||
],
|
||||
"bicycle_rental!=docking_station"
|
||||
]
|
||||
},
|
||||
{
|
||||
and: ["leisure=playground", "playground!=forest"],
|
||||
},
|
||||
],
|
||||
and: ["leisure=playground", "playground!=forest"]
|
||||
}
|
||||
]
|
||||
})
|
||||
const opt = <TagsFilter>filter.optimize()
|
||||
const expected = [
|
||||
|
@ -166,7 +166,7 @@ describe("Tag optimalization", () => {
|
|||
"planned:amenity=charging_station",
|
||||
"tower:type=observation",
|
||||
"(amenity=bicycle_rental|service:bicycle:rental=yes|bicycle_rental~.+|rental~^(.*bicycle.*)$) &bicycle_rental!=docking_station",
|
||||
"leisure=playground&playground!=forest",
|
||||
"leisure=playground&playground!=forest"
|
||||
]
|
||||
|
||||
expect((<Or>opt).or.map((f) => TagUtils.toString(f))).toEqual(expected)
|
||||
|
@ -187,7 +187,7 @@ describe("Tag optimalization", () => {
|
|||
it("with nested And which has a common property should be dropped", () => {
|
||||
const t = new Or([
|
||||
new Tag("foo", "bar"),
|
||||
new And([new Tag("foo", "bar"), new Tag("x", "y")]),
|
||||
new And([new Tag("foo", "bar"), new Tag("x", "y")])
|
||||
])
|
||||
const opt = <TagsFilter>t.optimize()
|
||||
expect(TagUtils.toString(opt)).toBe("foo=bar")
|
||||
|
@ -212,14 +212,14 @@ describe("Tag optimalization", () => {
|
|||
and: [
|
||||
"sport=climbing",
|
||||
{
|
||||
or: ["office~*", "club~*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
or: ["office~*", "club~*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
const gym_tags = TagUtils.Tag({
|
||||
and: ["sport=climbing", "leisure=sports_centre"],
|
||||
and: ["sport=climbing", "leisure=sports_centre"]
|
||||
})
|
||||
const other_climbing = TagUtils.Tag({
|
||||
and: [
|
||||
|
@ -227,8 +227,8 @@ describe("Tag optimalization", () => {
|
|||
"climbing!~route",
|
||||
"leisure!~sports_centre",
|
||||
"climbing!=route_top",
|
||||
"climbing!=route_bottom",
|
||||
],
|
||||
"climbing!=route_bottom"
|
||||
]
|
||||
})
|
||||
const together = new Or([club_tags, gym_tags, other_climbing])
|
||||
const opt = together.optimize()
|
||||
|
@ -270,7 +270,7 @@ describe("Tag optimalization", () => {
|
|||
or: [
|
||||
"club=climbing",
|
||||
{
|
||||
and: ["sport=climbing", { or: ["club~*", "office~*"] }],
|
||||
and: ["sport=climbing", { or: ["club~*", "office~*"] }]
|
||||
},
|
||||
{
|
||||
and: [
|
||||
|
@ -283,15 +283,70 @@ describe("Tag optimalization", () => {
|
|||
"climbing!~route",
|
||||
"climbing!=route_top",
|
||||
"climbing!=route_bottom",
|
||||
"leisure!~sports_centre",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"leisure!~sports_centre"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should optimize a complicated nested case", () => {
|
||||
const spec = {
|
||||
"and":
|
||||
["service:bicycle:retail=yes",
|
||||
{
|
||||
"or": [
|
||||
{
|
||||
"and": [
|
||||
{ "or": ["shop=outdoor", "shop=sport", "shop=diy", "shop=doityourself"] },
|
||||
{
|
||||
"or": ["service:bicycle:repair=yes", "shop=bicycle",
|
||||
{
|
||||
"and": ["shop=sports",
|
||||
{ "or": ["sport=bicycle", "sport=cycling", "sport="] },
|
||||
"service:bicycle:retail!=no",
|
||||
"service:bicycle:repair!=no"]
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
"and":
|
||||
[
|
||||
{
|
||||
"or":
|
||||
["shop=outdoor", "shop=sport", "shop=diy", "shop=doityourself"]
|
||||
},
|
||||
{
|
||||
"or": ["service:bicycle:repair=yes", "shop=bicycle",
|
||||
{
|
||||
"and": ["shop=sports",
|
||||
{ "or": ["sport=bicycle", "sport=cycling", "sport="] },
|
||||
"service:bicycle:retail!=no", "service:bicycle:repair!=no"]
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
"and":
|
||||
[{
|
||||
"or":
|
||||
["craft=shoe_repair", "craft=key_cutter", "shop~.+"]
|
||||
},
|
||||
{ "or": ["shop=outdoor", "shop=sport", "shop=diy", "shop=doityourself"] },
|
||||
"shop!=mall"]
|
||||
},
|
||||
"service:bicycle:retail~.+",
|
||||
"service:bicycle:retail~.+"]
|
||||
}]
|
||||
}
|
||||
|
||||
const tag = TagUtils.Tag(spec)
|
||||
const opt = tag.optimize()
|
||||
if (opt === false || opt === true) {
|
||||
throw "Did not expect a boolean"
|
||||
}
|
||||
console.log(opt)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -9,7 +9,7 @@ describe("TagUtils", () => {
|
|||
})
|
||||
|
||||
it("should handle compare tag <=5", () => {
|
||||
let compare = TagUtils.Tag("key<=5")
|
||||
const compare = TagUtils.Tag("key<=5")
|
||||
equal(compare.matchesProperties({ key: undefined }), false)
|
||||
equal(compare.matchesProperties({ key: "6" }), false)
|
||||
equal(compare.matchesProperties({ key: "5" }), true)
|
||||
|
|
Loading…
Reference in a new issue