forked from MapComplete/MapComplete
Themes: automatically remove filters that are useless because they would filter everything or nothing; fix some logic bugs and add tests
This commit is contained in:
parent
860fc9994b
commit
50ef2ba636
3 changed files with 37 additions and 12 deletions
|
@ -124,7 +124,7 @@ export class And extends TagsFilter {
|
||||||
* t0.shadows(t0) // => true
|
* t0.shadows(t0) // => true
|
||||||
* t1.shadows(t1) // => true
|
* t1.shadows(t1) // => true
|
||||||
* t2.shadows(t2) // => true
|
* t2.shadows(t2) // => true
|
||||||
* t0.shadows(t1) // => false
|
* t0.shadows(t1) // => true
|
||||||
* t0.shadows(t2) // => false
|
* t0.shadows(t2) // => false
|
||||||
* t1.shadows(t0) // => false
|
* t1.shadows(t0) // => false
|
||||||
* t1.shadows(t2) // => false
|
* t1.shadows(t2) // => false
|
||||||
|
@ -135,13 +135,18 @@ export class And extends TagsFilter {
|
||||||
* const t1 = new And([new Tag("shop","clothes"), new Or([new Tag("brand","XYZ"),new Tag("brand:wikidata","Q1234")])])
|
* const t1 = new And([new Tag("shop","clothes"), new Or([new Tag("brand","XYZ"),new Tag("brand:wikidata","Q1234")])])
|
||||||
* const t2 = new And([new RegexTag("shop","mall",true), new Or([TagUtils.Tag("shop~*"), new Tag("craft","shoemaker")])])
|
* const t2 = new And([new RegexTag("shop","mall",true), new Or([TagUtils.Tag("shop~*"), new Tag("craft","shoemaker")])])
|
||||||
* t1.shadows(t2) // => true
|
* t1.shadows(t2) // => true
|
||||||
|
*
|
||||||
|
* const t1 = new Tag("a","b")
|
||||||
|
* const t2 = new And([new Tag("x","y"), new Tag("a","b")])
|
||||||
|
* t2.shadows(t1) // => true
|
||||||
|
* t1.shadows(t2) // => false
|
||||||
*/
|
*/
|
||||||
shadows(other: TagsFilter): boolean {
|
shadows(other: TagsFilter): boolean {
|
||||||
const phrases: TagsFilter[] = other instanceof And ? other.and : [other]
|
// The phrases of the _other_ and
|
||||||
|
const phrases: readonly TagsFilter[] = other instanceof And ? other.and : [other]
|
||||||
// A phrase might be shadowed by a certain subsection. We keep track of this here
|
// A phrase might be shadowed by a certain subsection. We keep track of this here
|
||||||
const shadowedOthers = phrases.map(() => false)
|
const shadowedOthers = phrases.map(() => false)
|
||||||
for (const selfTag of this.and) {
|
for (const selfTag of this.and) {
|
||||||
let shadowsSome = false
|
|
||||||
let shadowsAll = true
|
let shadowsAll = true
|
||||||
for (let i = 0; i < phrases.length; i++) {
|
for (let i = 0; i < phrases.length; i++) {
|
||||||
const otherTag = phrases[i]
|
const otherTag = phrases[i]
|
||||||
|
@ -149,7 +154,6 @@ export class And extends TagsFilter {
|
||||||
if (doesShadow) {
|
if (doesShadow) {
|
||||||
shadowedOthers[i] = true
|
shadowedOthers[i] = true
|
||||||
}
|
}
|
||||||
shadowsSome ||= doesShadow
|
|
||||||
shadowsAll &&= doesShadow
|
shadowsAll &&= doesShadow
|
||||||
}
|
}
|
||||||
// If A => X and A => Y, then
|
// If A => X and A => Y, then
|
||||||
|
@ -157,9 +161,6 @@ export class And extends TagsFilter {
|
||||||
if (shadowsAll) {
|
if (shadowsAll) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (!shadowsSome) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return !shadowedOthers.some((v) => !v)
|
return !shadowedOthers.some((v) => !v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -690,6 +690,9 @@ export class TagUtils {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TagUtils.removeKnownParts(TagUtils.Tag({and: ["vending=excrement_bag"}),TagUtils.Tag({and: ["amenity=waste_basket", "vending=excrement_bag"]}), true) // => true
|
||||||
|
*/
|
||||||
public static removeKnownParts(
|
public static removeKnownParts(
|
||||||
tag: TagsFilter,
|
tag: TagsFilter,
|
||||||
known: TagsFilter,
|
known: TagsFilter,
|
||||||
|
@ -702,7 +705,7 @@ export class TagUtils {
|
||||||
if (tagOrBool instanceof And) {
|
if (tagOrBool instanceof And) {
|
||||||
return tagOrBool.removePhraseConsideredKnown(known, valueOfKnown)
|
return tagOrBool.removePhraseConsideredKnown(known, valueOfKnown)
|
||||||
}
|
}
|
||||||
return tagOrBool
|
return new And([tagOrBool]).removePhraseConsideredKnown(known, valueOfKnown)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { Or } from "../../../Logic/Tags/Or"
|
||||||
import Translations from "../../../UI/i18n/Translations"
|
import Translations from "../../../UI/i18n/Translations"
|
||||||
import { FlatTag, OptimizedTag, TagsFilterClosed } from "../../../Logic/Tags/TagTypes"
|
import { FlatTag, OptimizedTag, TagsFilterClosed } from "../../../Logic/Tags/TagTypes"
|
||||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||||
import { And } from "../../../Logic/Tags/And"
|
import { Translation } from "../../../UI/i18n/Translation"
|
||||||
|
|
||||||
export class PruneFilters extends DesugaringStep<LayerConfigJson> {
|
export class PruneFilters extends DesugaringStep<LayerConfigJson> {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -24,11 +24,30 @@ export class PruneFilters extends DesugaringStep<LayerConfigJson> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prunes a filter; returns null/undefined if keeping the filter is useless
|
||||||
|
*/
|
||||||
private prune(
|
private prune(
|
||||||
sourceTags: FlatTag,
|
sourceTags: FlatTag,
|
||||||
filter: FilterConfigJson,
|
filter: FilterConfigJson,
|
||||||
context: ConversionContext
|
context: ConversionContext
|
||||||
): FilterConfigJson {
|
): FilterConfigJson {
|
||||||
|
|
||||||
|
if (filter.options.length === 1) {
|
||||||
|
const option = filter.options[0]
|
||||||
|
const tags = TagUtils.Tag(option.osmTags)
|
||||||
|
const optimized = TagUtils.removeKnownParts(tags, sourceTags, true)
|
||||||
|
if (optimized === true) {
|
||||||
|
context.warn("Removing filter as always known: ", new Translation(option.question).textFor("en"))
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (optimized === false) {
|
||||||
|
context.warn("Removing filter as not possible: ", new Translation(option.question).textFor("en"))
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!filter.strict) {
|
if (!filter.strict) {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
@ -47,7 +66,7 @@ export class PruneFilters extends DesugaringStep<LayerConfigJson> {
|
||||||
if (!option.osmTags) {
|
if (!option.osmTags) {
|
||||||
return option
|
return option
|
||||||
}
|
}
|
||||||
let basetags = TagUtils.Tag(option.osmTags)
|
const basetags = TagUtils.Tag(option.osmTags)
|
||||||
return {
|
return {
|
||||||
...option,
|
...option,
|
||||||
osmTags: (<TagsFilter>TagUtils.removeKnownParts(basetags, sourceTags)).asJson(),
|
osmTags: (<TagsFilter>TagUtils.removeKnownParts(basetags, sourceTags)).asJson(),
|
||||||
|
@ -65,6 +84,8 @@ export class PruneFilters extends DesugaringStep<LayerConfigJson> {
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return { ...filter, options: newOptions, strict: undefined }
|
return { ...filter, options: newOptions, strict: undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +99,9 @@ export class PruneFilters extends DesugaringStep<LayerConfigJson> {
|
||||||
const sourceTags = TagUtils.Tag(json.source["osmTags"])
|
const sourceTags = TagUtils.Tag(json.source["osmTags"])
|
||||||
return {
|
return {
|
||||||
...json,
|
...json,
|
||||||
filter: json.filter?.map((obj) =>
|
filter: Utils.NoNull(json.filter?.map((obj) =>
|
||||||
this.prune(sourceTags, <FilterConfigJson>obj, context)
|
this.prune(sourceTags, <FilterConfigJson>obj, context)
|
||||||
),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue