Add comparison tagsfilter with <=, >=, < and >

This commit is contained in:
pietervdvn 2021-07-27 13:44:05 +02:00
parent 7c03a185ac
commit d9cc99c447
5 changed files with 137 additions and 37 deletions

View file

@ -6,12 +6,13 @@ import {And} from "../../Logic/Tags/And";
import {Tag} from "../../Logic/Tags/Tag";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
import ComparingTag from "../../Logic/Tags/ComparingTag";
export class FromJSON {
public static SimpleTag(json: string, context?: string): Tag {
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"})`
}
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 {
if (json === undefined) {
@ -33,6 +43,27 @@ export class FromJSON {
}
if (typeof (json) == "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) {
const split = Utils.SplitFirst(tag, "!~");
if (split[1] === "*") {
@ -54,7 +85,7 @@ export class FromJSON {
new RegExp("^" + split[1] + "$")
);
}
if(tag.indexOf(":=") >= 0){
if (tag.indexOf(":=") >= 0) {
const split = Utils.SplitFirst(tag, ":=");
return new SubstitutingTag(split[0], split[1]);
}

View file

@ -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.
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
------------

View 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];
}
}

View file

@ -1,7 +1,6 @@
import {Utils} from "../../Utils";
import {RegexTag} from "./RegexTag";
import {TagsFilter} from "./TagsFilter";
import {TagUtils} from "./TagUtils";
export class Tag extends TagsFilter {
public key: string
@ -46,11 +45,6 @@ export class Tag extends TagsFilter {
}
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) {
let v = this.value;
if (shorten) {

View file

@ -4,19 +4,16 @@ import T from "./TestHelper";
import {FromJSON} from "../Customizations/JSON/FromJSON";
import Locale from "../UI/i18n/Locale";
import Translations from "../UI/i18n/Translations";
import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
import EditableTagRendering from "../UI/Popup/EditableTagRendering";
import {Translation} from "../UI/i18n/Translation";
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 {And} from "../Logic/Tags/And";
import {centerOfMass} from "@turf/turf";
Utils.runningFromConsole = true;
export default class TagSpec extends T{
export default class TagSpec extends T {
constructor() {
super("Tags", [
@ -90,9 +87,35 @@ export default class TagSpec extends T{
equal(assign.matchesProperties({"some_key": "2021-03-29"}), false);
const notEmptyList = FromJSON.Tag("xyz!~\\[\\]")
equal(notEmptyList.matchesProperties({"xyz":undefined}), true);
equal(notEmptyList.matchesProperties({"xyz":"[]"}), false);
equal(notEmptyList.matchesProperties({"xyz":"[\"abc\"]"}), true);
equal(notEmptyList.matchesProperties({"xyz": undefined}), true);
equal(notEmptyList.matchesProperties({"xyz": "[]"}), false);
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,7 +381,7 @@ export default class TagSpec extends T{
]);
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(
OH.MergeTimes([
@ -378,7 +401,7 @@ export default class TagSpec extends T{
]));
equal(rules, "Tu 23:00-00:00");
}], ["JOIN OH with overflowed hours", () =>{
}], ["JOIN OH with overflowed hours", () => {
const rules = OH.ToString(
OH.MergeTimes([