Add comparison tagsfilter with <=, >=, < and >
This commit is contained in:
parent
7c03a185ac
commit
d9cc99c447
5 changed files with 137 additions and 37 deletions
|
@ -6,12 +6,13 @@ import {And} from "../../Logic/Tags/And";
|
||||||
import {Tag} from "../../Logic/Tags/Tag";
|
import {Tag} from "../../Logic/Tags/Tag";
|
||||||
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
|
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
|
||||||
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
|
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
|
||||||
|
import ComparingTag from "../../Logic/Tags/ComparingTag";
|
||||||
|
|
||||||
export class FromJSON {
|
export class FromJSON {
|
||||||
|
|
||||||
public static SimpleTag(json: string, context?: string): Tag {
|
public static SimpleTag(json: string, context?: string): Tag {
|
||||||
const tag = Utils.SplitFirst(json, "=");
|
const tag = Utils.SplitFirst(json, "=");
|
||||||
if(tag.length !== 2){
|
if (tag.length !== 2) {
|
||||||
throw `Invalid tag: no (or too much) '=' found (in ${context ?? "unkown context"})`
|
throw `Invalid tag: no (or too much) '=' found (in ${context ?? "unkown context"})`
|
||||||
}
|
}
|
||||||
return new Tag(tag[0], tag[1]);
|
return new Tag(tag[0], tag[1]);
|
||||||
|
@ -26,6 +27,15 @@ export class FromJSON {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static comparators
|
||||||
|
: [string, (a: number, b: number) => boolean][]
|
||||||
|
= [
|
||||||
|
["<=", (a, b) => a <= b],
|
||||||
|
[">=", (a, b) => a >= b],
|
||||||
|
["<", (a, b) => a < b],
|
||||||
|
[">", (a, b) => a > b],
|
||||||
|
]
|
||||||
|
|
||||||
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
|
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
|
||||||
|
|
||||||
if (json === undefined) {
|
if (json === undefined) {
|
||||||
|
@ -33,6 +43,27 @@ export class FromJSON {
|
||||||
}
|
}
|
||||||
if (typeof (json) == "string") {
|
if (typeof (json) == "string") {
|
||||||
const tag = json as string;
|
const tag = json as string;
|
||||||
|
|
||||||
|
for (const [operator, comparator] of FromJSON.comparators) {
|
||||||
|
if (tag.indexOf(operator) >= 0) {
|
||||||
|
const split = Utils.SplitFirst(tag, operator);
|
||||||
|
|
||||||
|
const val = Number(split[1].trim())
|
||||||
|
if (isNaN(val)) {
|
||||||
|
throw `Error: not a valid value for a comparison: ${split[1]}, make sure it is a number and nothing more (at ${context})`
|
||||||
|
}
|
||||||
|
|
||||||
|
const f = (value: string | undefined) => {
|
||||||
|
const b = Number(value?.replace(/[^\d.]/g,''))
|
||||||
|
if (isNaN(b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return comparator(b, val)
|
||||||
|
}
|
||||||
|
return new ComparingTag(split[0], f, operator + val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tag.indexOf("!~") >= 0) {
|
if (tag.indexOf("!~") >= 0) {
|
||||||
const split = Utils.SplitFirst(tag, "!~");
|
const split = Utils.SplitFirst(tag, "!~");
|
||||||
if (split[1] === "*") {
|
if (split[1] === "*") {
|
||||||
|
@ -54,7 +85,7 @@ export class FromJSON {
|
||||||
new RegExp("^" + split[1] + "$")
|
new RegExp("^" + split[1] + "$")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if(tag.indexOf(":=") >= 0){
|
if (tag.indexOf(":=") >= 0) {
|
||||||
const split = Utils.SplitFirst(tag, ":=");
|
const split = Utils.SplitFirst(tag, ":=");
|
||||||
return new SubstitutingTag(split[0], split[1]);
|
return new SubstitutingTag(split[0], split[1]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,16 @@ To check if a key does _not_ equal a certain value, use `key!=value`. This is co
|
||||||
|
|
||||||
This implies that, to check if a key is present, `key!=` can be used. This will only match if the key is present and not empty.
|
This implies that, to check if a key is present, `key!=` can be used. This will only match if the key is present and not empty.
|
||||||
|
|
||||||
|
Number comparison
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If the value of a tag is a number (e.g. `key=42`), one can use a filter `key<=42`, `key>=35`, `key>40` or `key<50` to match this, e.g. in conditions for renderings.
|
||||||
|
These tags cannot be used to generate an answer nor can they be used to request data upstream from overpass.
|
||||||
|
|
||||||
|
Note that the value coming from OSM will first be stripped by removing all non-numeric characters. For example, `length=42 meter` will be interpreted as `length=42` and will thus match `length<=42` and `length>=42`.
|
||||||
|
In special circumstances (e.g. `surface_area=42 m2` or `length=100 feet`), this will result in erronous values (`surface=422` or if a length in meters is compared to).
|
||||||
|
However, this can be partially alleviated by using 'Units' to rewrite to a default format.
|
||||||
|
|
||||||
Regex equals
|
Regex equals
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
42
Logic/Tags/ComparingTag.ts
Normal file
42
Logic/Tags/ComparingTag.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
|
||||||
|
export default class ComparingTag implements TagsFilter {
|
||||||
|
private readonly _key: string;
|
||||||
|
private readonly _predicate: (value: string) => boolean;
|
||||||
|
private readonly _representation: string;
|
||||||
|
|
||||||
|
constructor(key: string, predicate : (value:string | undefined) => boolean, representation: string = "") {
|
||||||
|
this._key = key;
|
||||||
|
this._predicate = predicate;
|
||||||
|
this._representation = representation;
|
||||||
|
}
|
||||||
|
|
||||||
|
asChange(properties: any): { k: string; v: string }[] {
|
||||||
|
throw "A comparable tag can not be used to be uploaded to OSM"
|
||||||
|
}
|
||||||
|
|
||||||
|
asHumanString(linkToWiki: boolean, shorten: boolean, properties: any) {
|
||||||
|
return this._key+this._representation
|
||||||
|
}
|
||||||
|
|
||||||
|
asOverpass(): string[] {
|
||||||
|
throw "A comparable tag can not be used as overpass filter"
|
||||||
|
}
|
||||||
|
|
||||||
|
isEquivalent(other: TagsFilter): boolean {
|
||||||
|
return other === this;
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesProperties(properties: any): boolean {
|
||||||
|
return this._predicate(properties[this._key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
usedKeys(): string[] {
|
||||||
|
return [this._key];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {RegexTag} from "./RegexTag";
|
import {RegexTag} from "./RegexTag";
|
||||||
import {TagsFilter} from "./TagsFilter";
|
import {TagsFilter} from "./TagsFilter";
|
||||||
import {TagUtils} from "./TagUtils";
|
|
||||||
|
|
||||||
export class Tag extends TagsFilter {
|
export class Tag extends TagsFilter {
|
||||||
public key: string
|
public key: string
|
||||||
|
@ -46,11 +45,6 @@ export class Tag extends TagsFilter {
|
||||||
}
|
}
|
||||||
return [`["${this.key}"="${this.value}"]`];
|
return [`["${this.key}"="${this.value}"]`];
|
||||||
}
|
}
|
||||||
|
|
||||||
substituteValues(tags: any) {
|
|
||||||
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
|
|
||||||
}
|
|
||||||
|
|
||||||
asHumanString(linkToWiki?: boolean, shorten?: boolean) {
|
asHumanString(linkToWiki?: boolean, shorten?: boolean) {
|
||||||
let v = this.value;
|
let v = this.value;
|
||||||
if (shorten) {
|
if (shorten) {
|
||||||
|
|
|
@ -4,19 +4,16 @@ import T from "./TestHelper";
|
||||||
import {FromJSON} from "../Customizations/JSON/FromJSON";
|
import {FromJSON} from "../Customizations/JSON/FromJSON";
|
||||||
import Locale from "../UI/i18n/Locale";
|
import Locale from "../UI/i18n/Locale";
|
||||||
import Translations from "../UI/i18n/Translations";
|
import Translations from "../UI/i18n/Translations";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
|
||||||
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
|
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
|
||||||
import EditableTagRendering from "../UI/Popup/EditableTagRendering";
|
|
||||||
import {Translation} from "../UI/i18n/Translation";
|
import {Translation} from "../UI/i18n/Translation";
|
||||||
import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours";
|
import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours";
|
||||||
import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput";
|
|
||||||
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
|
|
||||||
import {Tag} from "../Logic/Tags/Tag";
|
import {Tag} from "../Logic/Tags/Tag";
|
||||||
import {And} from "../Logic/Tags/And";
|
import {And} from "../Logic/Tags/And";
|
||||||
|
import {centerOfMass} from "@turf/turf";
|
||||||
|
|
||||||
Utils.runningFromConsole = true;
|
Utils.runningFromConsole = true;
|
||||||
|
|
||||||
export default class TagSpec extends T{
|
export default class TagSpec extends T {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("Tags", [
|
super("Tags", [
|
||||||
|
@ -90,9 +87,35 @@ export default class TagSpec extends T{
|
||||||
equal(assign.matchesProperties({"some_key": "2021-03-29"}), false);
|
equal(assign.matchesProperties({"some_key": "2021-03-29"}), false);
|
||||||
|
|
||||||
const notEmptyList = FromJSON.Tag("xyz!~\\[\\]")
|
const notEmptyList = FromJSON.Tag("xyz!~\\[\\]")
|
||||||
equal(notEmptyList.matchesProperties({"xyz":undefined}), true);
|
equal(notEmptyList.matchesProperties({"xyz": undefined}), true);
|
||||||
equal(notEmptyList.matchesProperties({"xyz":"[]"}), false);
|
equal(notEmptyList.matchesProperties({"xyz": "[]"}), false);
|
||||||
equal(notEmptyList.matchesProperties({"xyz":"[\"abc\"]"}), true);
|
equal(notEmptyList.matchesProperties({"xyz": "[\"abc\"]"}), true);
|
||||||
|
|
||||||
|
let compare = FromJSON.Tag("key<=5")
|
||||||
|
equal(compare.matchesProperties({"key": undefined}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "6"}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "5"}), true);
|
||||||
|
equal(compare.matchesProperties({"key": "4"}), true);
|
||||||
|
|
||||||
|
|
||||||
|
compare = FromJSON.Tag("key<5")
|
||||||
|
equal(compare.matchesProperties({"key": undefined}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "6"}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "5"}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "4.2"}), true);
|
||||||
|
|
||||||
|
compare = FromJSON.Tag("key>5")
|
||||||
|
equal(compare.matchesProperties({"key": undefined}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "6"}), true);
|
||||||
|
equal(compare.matchesProperties({"key": "5"}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "4.2"}), false);
|
||||||
|
compare = FromJSON.Tag("key>=5")
|
||||||
|
equal(compare.matchesProperties({"key": undefined}), false);
|
||||||
|
equal(compare.matchesProperties({"key": "6"}), true);
|
||||||
|
equal(compare.matchesProperties({"key": "5"}), true);
|
||||||
|
equal(compare.matchesProperties({"key": "4.2"}), false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})],
|
})],
|
||||||
|
@ -358,27 +381,27 @@ export default class TagSpec extends T{
|
||||||
]);
|
]);
|
||||||
equal(rules, "Tu 10:00-12:00; Su 13:00-17:00");
|
equal(rules, "Tu 10:00-12:00; Su 13:00-17:00");
|
||||||
}],
|
}],
|
||||||
["JOIN OH with end hours", () =>{
|
["JOIN OH with end hours", () => {
|
||||||
const rules = OH.ToString(
|
const rules = OH.ToString(
|
||||||
OH.MergeTimes([
|
OH.MergeTimes([
|
||||||
|
|
||||||
{
|
{
|
||||||
weekday: 1,
|
weekday: 1,
|
||||||
endHour: 23,
|
endHour: 23,
|
||||||
endMinutes: 30,
|
endMinutes: 30,
|
||||||
startHour: 23,
|
startHour: 23,
|
||||||
startMinutes: 0
|
startMinutes: 0
|
||||||
}, {
|
}, {
|
||||||
weekday: 1,
|
weekday: 1,
|
||||||
endHour: 24,
|
endHour: 24,
|
||||||
endMinutes: 0,
|
endMinutes: 0,
|
||||||
startHour: 23,
|
startHour: 23,
|
||||||
startMinutes: 30
|
startMinutes: 30
|
||||||
},
|
},
|
||||||
|
|
||||||
]));
|
]));
|
||||||
equal(rules, "Tu 23:00-00:00");
|
equal(rules, "Tu 23:00-00:00");
|
||||||
}], ["JOIN OH with overflowed hours", () =>{
|
}], ["JOIN OH with overflowed hours", () => {
|
||||||
const rules = OH.ToString(
|
const rules = OH.ToString(
|
||||||
OH.MergeTimes([
|
OH.MergeTimes([
|
||||||
|
|
||||||
|
@ -464,7 +487,7 @@ export default class TagSpec extends T{
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagRendering = new TagRenderingConfig(config, null, "test");
|
const tagRendering = new TagRenderingConfig(config, null, "test");
|
||||||
equal(true, tagRendering.IsKnown({bottle: "yes"}))
|
equal(true, tagRendering.IsKnown({bottle: "yes"}))
|
||||||
equal(false, tagRendering.IsKnown({}))
|
equal(false, tagRendering.IsKnown({}))
|
||||||
}]]);
|
}]]);
|
||||||
|
|
Loading…
Reference in a new issue