mapcomplete/Logic/Tags.ts

251 lines
6.6 KiB
TypeScript
Raw Normal View History

2020-08-25 22:21:34 +00:00
import {Utils} from "../Utils";
2020-07-16 12:21:06 +00:00
export abstract class TagsFilter {
abstract matches(tags: { k: string, v: string }[]): boolean
abstract asOverpass(): string[]
abstract substituteValues(tags: any) : TagsFilter;
matchesProperties(properties: Map<string, string>): boolean {
2020-07-16 12:21:06 +00:00
return this.matches(TagUtils.proprtiesToKV(properties));
}
abstract asHumanString(linkToWiki: boolean, shorten: boolean);
2020-07-16 12:21:06 +00:00
}
export class RegexTag extends TagsFilter {
private readonly key: RegExp;
private readonly value: RegExp;
private readonly invert: boolean;
2020-06-23 22:35:19 +00:00
constructor(key: string | RegExp, value: RegExp, invert: boolean = false) {
2020-07-16 12:21:06 +00:00
super();
this.key = typeof (key) === "string" ? new RegExp(key) : key;
this.value = value;
this.invert = invert;
2020-06-23 22:35:19 +00:00
}
asOverpass(): string[] {
return [`['${this.key.source}'${this.invert ? "!" : ""}~'${this.value.source}']`];
2020-06-23 22:35:19 +00:00
}
matches(tags: { k: string; v: string }[]): boolean {
for (const tag of tags) {
if (tag.k.match(this.key)) {
return tag.v.match(this.value) !== null;
2020-06-23 22:35:19 +00:00
}
}
return false;
}
2020-07-05 16:59:47 +00:00
substituteValues(tags: any) : TagsFilter{
console.warn("Not substituting values on regex tags");
return this;
2020-07-05 16:59:47 +00:00
}
asHumanString() {
return `${this.key.source}${this.invert ? "!" : ""}~${this.value.source}`;
}
2020-06-23 22:35:19 +00:00
}
2020-07-16 12:21:06 +00:00
export class Tag extends TagsFilter {
2020-07-29 22:59:08 +00:00
public key: string
public value: string
2020-07-29 15:16:59 +00:00
constructor(key: string, value: string) {
2020-07-16 12:21:06 +00:00
super()
this.key = key
this.value = value
if(key === undefined || key === ""){
throw "Invalid key";
}
if(value === undefined){
throw "Invalid value";
}
if(value === "*"){
console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}!~*`)
}
2020-06-23 22:35:19 +00:00
}
matches(tags: { k: string; v: string }[]): boolean {
if (this.value === "") {
return true
}
2020-06-23 22:35:19 +00:00
for (const tag of tags) {
if (this.key == tag.k) {
2020-06-23 22:35:19 +00:00
if (tag.v === "") {
// This tag has been removed -> always matches false
return false;
2020-06-23 22:35:19 +00:00
}
if (this.value === tag.v) {
return true;
}
2020-06-23 22:35:19 +00:00
}
}
return false;
2020-06-23 22:35:19 +00:00
}
asOverpass(): string[] {
if (this.value === "") {
// NOT having this key
return ['[!"' + this.key + '"]'];
2020-06-23 22:35:19 +00:00
}
return [`["${this.key}"="${this.value}"]`];
2020-06-23 22:35:19 +00:00
}
2020-07-05 16:59:47 +00:00
substituteValues(tags: any) {
2020-07-29 15:16:59 +00:00
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
2020-07-05 16:59:47 +00:00
}
asHumanString(linkToWiki: boolean, shorten: boolean) {
let v = this.value;
if (shorten) {
v = Utils.EllipsesAfter(v, 25);
}
if (linkToWiki) {
return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` +
`=` +
2020-08-25 22:21:34 +00:00
`<a href='https://wiki.openstreetmap.org/wiki/Tag:${this.key}%3D${this.value}' target='_blank'>${v}</a>`
}
return this.key + "=" + v;
}
2020-06-23 22:35:19 +00:00
}
export class Or extends TagsFilter {
2020-06-23 22:35:19 +00:00
public or: TagsFilter[]
constructor(or: TagsFilter[]) {
2020-07-16 12:21:06 +00:00
super();
2020-06-23 22:35:19 +00:00
this.or = or;
}
matches(tags: { k: string; v: string }[]): boolean {
for (const tagsFilter of this.or) {
if (tagsFilter.matches(tags)) {
return true;
}
}
return false;
}
asOverpass(): string[] {
const choices = [];
for (const tagsFilter of this.or) {
const subChoices = tagsFilter.asOverpass();
for(const subChoice of subChoices){
choices.push(subChoice)
}
}
return choices;
}
2020-07-05 16:59:47 +00:00
substituteValues(tags: any): TagsFilter {
const newChoices = [];
for (const c of this.or) {
newChoices.push(c.substituteValues(tags));
}
return new Or(newChoices);
}
asHumanString(linkToWiki: boolean, shorten: boolean) {
return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|");
}
2020-06-23 22:35:19 +00:00
}
export class And extends TagsFilter {
2020-06-23 22:35:19 +00:00
public and: TagsFilter[]
constructor(and: TagsFilter[]) {
2020-07-16 12:21:06 +00:00
super();
2020-06-23 22:35:19 +00:00
this.and = and;
}
matches(tags: { k: string; v: string }[]): boolean {
for (const tagsFilter of this.and) {
if (!tagsFilter.matches(tags)) {
return false;
}
}
return true;
}
private static combine(filter: string, choices: string[]): string[] {
const values = [];
2020-06-23 22:35:19 +00:00
for (const or of choices) {
values.push(filter + or);
}
return values;
}
asOverpass(): string[] {
let allChoices: string[] = null;
2020-06-23 22:35:19 +00:00
for (const andElement of this.and) {
const andElementFilter = andElement.asOverpass();
2020-06-23 22:35:19 +00:00
if (allChoices === null) {
allChoices = andElementFilter;
continue;
}
const newChoices: string[] = [];
for (const choice of allChoices) {
2020-06-23 22:35:19 +00:00
newChoices.push(
...And.combine(choice, andElementFilter)
2020-06-23 22:35:19 +00:00
)
}
allChoices = newChoices;
}
return allChoices;
}
2020-07-05 16:59:47 +00:00
substituteValues(tags: any): TagsFilter {
const newChoices = [];
for (const c of this.and) {
newChoices.push(c.substituteValues(tags));
}
return new And(newChoices);
}
asHumanString(linkToWiki: boolean, shorten: boolean) {
return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&");
}
2020-06-23 22:35:19 +00:00
}
2020-06-23 22:35:19 +00:00
export class TagUtils {
static proprtiesToKV(properties: any): { k: string, v: string }[] {
const result = [];
for (const k in properties) {
result.push({k: k, v: properties[k]})
}
return result;
}
2020-07-05 16:59:47 +00:00
static ApplyTemplate(template: string, tags: any): string {
for (const k in tags) {
while (template.indexOf("{" + k + "}") >= 0) {
2020-07-29 17:43:15 +00:00
const escaped = tags[k].replace(/</g, '&lt;').replace(/>/g, '&gt;');
template = template.replace("{" + k + "}", escaped);
2020-07-05 16:59:47 +00:00
}
}
return template;
}
static KVtoProperties(tags: Tag[]): any {
const properties = {};
for (const tag of tags) {
properties[tag.key] = tag.value
}
return properties;
}
}