Split TagsFilter into multiple files
This commit is contained in:
parent
830f34b183
commit
d8c1f72857
26 changed files with 541 additions and 518 deletions
|
@ -1,7 +1,11 @@
|
||||||
import {AndOrTagConfigJson} from "./TagConfigJson";
|
import {AndOrTagConfigJson} from "./TagConfigJson";
|
||||||
import {And, Or, RegexTag, Tag, TagsFilter} from "../../Logic/Tags";
|
import {Or} from "../../Logic/Or";
|
||||||
|
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
import {TagsFilter} from "../../Logic/TagsFilter";
|
||||||
|
import {RegexTag} from "../../Logic/RegexTag";
|
||||||
|
import {Tag} from "../../Logic/Tag";
|
||||||
|
import {And} from "../../Logic/And";
|
||||||
|
|
||||||
export class FromJSON {
|
export class FromJSON {
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import TagRenderingConfig from "./TagRenderingConfig";
|
import TagRenderingConfig from "./TagRenderingConfig";
|
||||||
import {Tag, TagsFilter} from "../../Logic/Tags";
|
|
||||||
import {LayerConfigJson} from "./LayerConfigJson";
|
import {LayerConfigJson} from "./LayerConfigJson";
|
||||||
import {FromJSON} from "./FromJSON";
|
import {FromJSON} from "./FromJSON";
|
||||||
import SharedTagRenderings from "../SharedTagRenderings";
|
import SharedTagRenderings from "../SharedTagRenderings";
|
||||||
|
@ -16,6 +15,8 @@ import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
||||||
import {UIElement} from "../../UI/UIElement";
|
import {UIElement} from "../../UI/UIElement";
|
||||||
import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
|
import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
|
||||||
import SourceConfig from "./SourceConfig";
|
import SourceConfig from "./SourceConfig";
|
||||||
|
import {TagsFilter} from "../../Logic/TagsFilter";
|
||||||
|
import {Tag} from "../../Logic/Tag";
|
||||||
|
|
||||||
export default class LayerConfig {
|
export default class LayerConfig {
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {TagsFilter} from "../../Logic/Tags";
|
import {TagsFilter} from "../../Logic/TagsFilter";
|
||||||
|
|
||||||
export default class SourceConfig {
|
export default class SourceConfig {
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import {And, TagsFilter, TagUtils} from "../../Logic/Tags";
|
|
||||||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import {FromJSON} from "./FromJSON";
|
import {FromJSON} from "./FromJSON";
|
||||||
import ValidatedTextField from "../../UI/Input/ValidatedTextField";
|
import ValidatedTextField from "../../UI/Input/ValidatedTextField";
|
||||||
import {Translation} from "../../UI/i18n/Translation";
|
import {Translation} from "../../UI/i18n/Translation";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
import {TagsFilter} from "../../Logic/TagsFilter";
|
||||||
|
import {And} from "../../Logic/And";
|
||||||
|
import {TagUtils} from "../../Logic/TagUtils";
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* The parsed version of TagRenderingConfigJSON
|
* The parsed version of TagRenderingConfigJSON
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import Loc from "../../Models/Loc";
|
import Loc from "../../Models/Loc";
|
||||||
import {Or, TagsFilter} from "../Tags";
|
import {Or} from "../Or";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
import {Overpass} from "../Osm/Overpass";
|
import {Overpass} from "../Osm/Overpass";
|
||||||
import Bounds from "../../Models/Bounds";
|
import Bounds from "../../Models/Bounds";
|
||||||
import FeatureSource from "../FeatureSource/FeatureSource";
|
import FeatureSource from "../FeatureSource/FeatureSource";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
import {TagsFilter} from "../TagsFilter";
|
||||||
|
|
||||||
|
|
||||||
export default class UpdateFromOverpass implements FeatureSource {
|
export default class UpdateFromOverpass implements FeatureSource {
|
||||||
|
|
119
Logic/And.ts
Normal file
119
Logic/And.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
|
||||||
|
export class And extends TagsFilter {
|
||||||
|
public and: TagsFilter[]
|
||||||
|
|
||||||
|
constructor(and: TagsFilter[]) {
|
||||||
|
super();
|
||||||
|
this.and = and;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static combine(filter: string, choices: string[]): string[] {
|
||||||
|
const values = [];
|
||||||
|
for (const or of choices) {
|
||||||
|
values.push(filter + or);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesProperties(tags: any): boolean {
|
||||||
|
for (const tagsFilter of this.and) {
|
||||||
|
if (!tagsFilter.matchesProperties(tags)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
asOverpass(): string[] {
|
||||||
|
let allChoices: string[] = null;
|
||||||
|
for (const andElement of this.and) {
|
||||||
|
const andElementFilter = andElement.asOverpass();
|
||||||
|
if (allChoices === null) {
|
||||||
|
allChoices = andElementFilter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newChoices: string[] = [];
|
||||||
|
for (const choice of allChoices) {
|
||||||
|
newChoices.push(
|
||||||
|
...And.combine(choice, andElementFilter)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
allChoices = newChoices;
|
||||||
|
}
|
||||||
|
return allChoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
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("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
for (const t of this.and) {
|
||||||
|
if (!t.isUsableAsAnswer()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEquivalent(other: TagsFilter): boolean {
|
||||||
|
if (!(other instanceof And)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const selfTag of this.and) {
|
||||||
|
let matchFound = false;
|
||||||
|
for (let i = 0; i < other.and.length && !matchFound; i++) {
|
||||||
|
let otherTag = other.and[i];
|
||||||
|
matchFound = selfTag.isEquivalent(otherTag);
|
||||||
|
}
|
||||||
|
if (!matchFound) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const selfTag of this.and) {
|
||||||
|
let matchFound = false;
|
||||||
|
for (const otherTag of other.and) {
|
||||||
|
matchFound = selfTag.isEquivalent(otherTag);
|
||||||
|
if (matchFound) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matchFound) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const otherTag of other.and) {
|
||||||
|
let matchFound = false;
|
||||||
|
for (const selfTag of this.and) {
|
||||||
|
matchFound = selfTag.isEquivalent(otherTag);
|
||||||
|
if (matchFound) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matchFound) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedKeys(): string[] {
|
||||||
|
return [].concat(...this.and.map(subkeys => subkeys.usedKeys()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
import {GeoOperations} from "./GeoOperations";
|
import {GeoOperations} from "./GeoOperations";
|
||||||
import State from "../State";
|
import State from "../State";
|
||||||
import opening_hours from "opening_hours";
|
import opening_hours from "opening_hours";
|
||||||
import {And, Or, Tag} from "./Tags";
|
import {Or} from "./Or";
|
||||||
import {Utils} from "../Utils";
|
import {Utils} from "../Utils";
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import Combine from "../UI/Base/Combine";
|
import Combine from "../UI/Base/Combine";
|
||||||
|
import {Tag} from "./Tag";
|
||||||
|
import {And} from "./And";
|
||||||
|
|
||||||
class SimpleMetaTagger {
|
class SimpleMetaTagger {
|
||||||
public readonly keys: string[];
|
public readonly keys: string[];
|
||||||
|
|
72
Logic/Or.ts
Normal file
72
Logic/Or.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
|
||||||
|
|
||||||
|
export class Or extends TagsFilter {
|
||||||
|
public or: TagsFilter[]
|
||||||
|
|
||||||
|
constructor(or: TagsFilter[]) {
|
||||||
|
super();
|
||||||
|
this.or = or;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesProperties(properties: any): boolean {
|
||||||
|
for (const tagsFilter of this.or) {
|
||||||
|
if (tagsFilter.matchesProperties(properties)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEquivalent(other: TagsFilter): boolean {
|
||||||
|
if (other instanceof Or) {
|
||||||
|
|
||||||
|
for (const selfTag of this.or) {
|
||||||
|
let matchFound = false;
|
||||||
|
for (let i = 0; i < other.or.length && !matchFound; i++) {
|
||||||
|
let otherTag = other.or[i];
|
||||||
|
matchFound = selfTag.isEquivalent(otherTag);
|
||||||
|
}
|
||||||
|
if (!matchFound) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedKeys(): string[] {
|
||||||
|
return [].concat(...this.or.map(subkeys => subkeys.usedKeys()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import {OsmNode, OsmObject} from "./OsmObject";
|
import {OsmNode, OsmObject} from "./OsmObject";
|
||||||
import {And, Tag, TagsFilter} from "../Tags";
|
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import Constants from "../../Models/Constants";
|
import Constants from "../../Models/Constants";
|
||||||
import FeatureSource from "../FeatureSource/FeatureSource";
|
import FeatureSource from "../FeatureSource/FeatureSource";
|
||||||
|
import {TagsFilter} from "../TagsFilter";
|
||||||
|
import {Tag} from "../Tag";
|
||||||
|
import {And} from "../And";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all changes made to OSM.
|
* Handles all changes made to OSM.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {TagsFilter} from "../Tags";
|
|
||||||
import * as $ from "jquery"
|
import * as $ from "jquery"
|
||||||
import * as OsmToGeoJson from "osmtogeojson";
|
import * as OsmToGeoJson from "osmtogeojson";
|
||||||
import Bounds from "../../Models/Bounds";
|
import Bounds from "../../Models/Bounds";
|
||||||
|
import {TagsFilter} from "../TagsFilter";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interfaces overpass to get all the latest data
|
* Interfaces overpass to get all the latest data
|
||||||
|
|
85
Logic/RegexTag.ts
Normal file
85
Logic/RegexTag.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import {Tag} from "./Tag";
|
||||||
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
|
||||||
|
export class RegexTag extends TagsFilter {
|
||||||
|
private readonly key: RegExp | string;
|
||||||
|
private readonly value: RegExp | string;
|
||||||
|
private readonly invert: boolean;
|
||||||
|
private readonly matchesEmpty: boolean
|
||||||
|
|
||||||
|
constructor(key: string | RegExp, value: RegExp | string, invert: boolean = false) {
|
||||||
|
super();
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
this.invert = invert;
|
||||||
|
this.matchesEmpty = RegexTag.doesMatch("", this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean {
|
||||||
|
if (typeof possibleRegex === "string") {
|
||||||
|
return fromTag === possibleRegex;
|
||||||
|
}
|
||||||
|
return fromTag.match(possibleRegex) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static source(r: string | RegExp) {
|
||||||
|
if (typeof (r) === "string") {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
return r.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
asOverpass(): string[] {
|
||||||
|
if (typeof this.key === "string") {
|
||||||
|
return [`['${this.key}'${this.invert ? "!" : ""}~'${RegexTag.source(this.value)}']`];
|
||||||
|
}
|
||||||
|
return [`[~'${this.key.source}'${this.invert ? "!" : ""}~'${RegexTag.source(this.value)}']`];
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesProperties(tags: any): boolean {
|
||||||
|
for (const key in tags) {
|
||||||
|
if (RegexTag.doesMatch(key, this.key)) {
|
||||||
|
const value = tags[key]
|
||||||
|
return RegexTag.doesMatch(value, this.value) != this.invert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.matchesEmpty) {
|
||||||
|
// The value is 'empty'
|
||||||
|
return !this.invert;
|
||||||
|
}
|
||||||
|
// The matching key was not found
|
||||||
|
return this.invert;
|
||||||
|
}
|
||||||
|
|
||||||
|
substituteValues(tags: any): TagsFilter {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
asHumanString() {
|
||||||
|
if (typeof this.key === "string") {
|
||||||
|
return `${this.key}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`;
|
||||||
|
}
|
||||||
|
return `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
isEquivalent(other: TagsFilter): boolean {
|
||||||
|
if (other instanceof RegexTag) {
|
||||||
|
return other.asHumanString() == this.asHumanString();
|
||||||
|
}
|
||||||
|
if (other instanceof Tag) {
|
||||||
|
return RegexTag.doesMatch(other.key, this.key) && RegexTag.doesMatch(other.value, this.value);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedKeys(): string[] {
|
||||||
|
if (typeof this.key === "string") {
|
||||||
|
return [this.key];
|
||||||
|
}
|
||||||
|
throw "Key cannot be determined as it is a regex"
|
||||||
|
}
|
||||||
|
}
|
84
Logic/Tag.ts
Normal file
84
Logic/Tag.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import {Utils} from "../Utils";
|
||||||
|
import {RegexTag} from "./RegexTag";
|
||||||
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
import {TagUtils} from "./TagUtils";
|
||||||
|
|
||||||
|
export class Tag extends TagsFilter {
|
||||||
|
public key: string
|
||||||
|
public value: string
|
||||||
|
|
||||||
|
constructor(key: string, value: string) {
|
||||||
|
super()
|
||||||
|
this.key = key
|
||||||
|
this.value = value
|
||||||
|
if (key === undefined || key === "") {
|
||||||
|
throw "Invalid key: undefined or empty";
|
||||||
|
}
|
||||||
|
if (value === undefined) {
|
||||||
|
throw "Invalid value: value is undefined";
|
||||||
|
}
|
||||||
|
if (value === "*") {
|
||||||
|
console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}~* ?`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
matchesProperties(properties: any): boolean {
|
||||||
|
for (const propertiesKey in properties) {
|
||||||
|
if (this.key === propertiesKey) {
|
||||||
|
const value = properties[propertiesKey];
|
||||||
|
return value === this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The tag was not found
|
||||||
|
if (this.value === "") {
|
||||||
|
// and it shouldn't be found!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
asOverpass(): string[] {
|
||||||
|
if (this.value === "") {
|
||||||
|
// NOT having this key
|
||||||
|
return ['[!"' + this.key + '"]'];
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
v = Utils.EllipsesAfter(v, 25);
|
||||||
|
}
|
||||||
|
if (linkToWiki) {
|
||||||
|
return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` +
|
||||||
|
`=` +
|
||||||
|
`<a href='https://wiki.openstreetmap.org/wiki/Tag:${this.key}%3D${this.value}' target='_blank'>${v}</a>`
|
||||||
|
}
|
||||||
|
return this.key + "=" + v;
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEquivalent(other: TagsFilter): boolean {
|
||||||
|
if (other instanceof Tag) {
|
||||||
|
return this.key === other.key && this.value === other.value;
|
||||||
|
}
|
||||||
|
if (other instanceof RegexTag) {
|
||||||
|
other.isEquivalent(this);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedKeys(): string[] {
|
||||||
|
return [this.key];
|
||||||
|
}
|
||||||
|
}
|
121
Logic/TagUtils.ts
Normal file
121
Logic/TagUtils.ts
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import {Tag} from "./Tag";
|
||||||
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
import {And} from "./And";
|
||||||
|
import {Utils} from "../Utils";
|
||||||
|
|
||||||
|
export class TagUtils {
|
||||||
|
static ApplyTemplate(template: string, tags: any): string {
|
||||||
|
for (const k in tags) {
|
||||||
|
while (template.indexOf("{" + k + "}") >= 0) {
|
||||||
|
const escaped = tags[k].replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
template = template.replace("{" + k + "}", escaped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
static KVtoProperties(tags: Tag[]): any {
|
||||||
|
const properties = {};
|
||||||
|
for (const tag of tags) {
|
||||||
|
properties[tag.key] = tag.value
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two hashes of {key --> values[]}, makes sure that every neededTag is present in availableTags
|
||||||
|
*/
|
||||||
|
static AllKeysAreContained(availableTags: any, neededTags: any) {
|
||||||
|
for (const neededKey in neededTags) {
|
||||||
|
const availableValues: string[] = availableTags[neededKey]
|
||||||
|
if (availableValues === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const neededValues: string[] = neededTags[neededKey];
|
||||||
|
for (const neededValue of neededValues) {
|
||||||
|
if (availableValues.indexOf(neededValue) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Creates a hash {key --> [values]}, with all the values present in the tagsfilter
|
||||||
|
*
|
||||||
|
* @param tagsFilters
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
static SplitKeys(tagsFilters: TagsFilter[]) {
|
||||||
|
const keyValues = {} // Map string -> string[]
|
||||||
|
tagsFilters = [...tagsFilters] // copy all
|
||||||
|
while (tagsFilters.length > 0) {
|
||||||
|
// Queue
|
||||||
|
const tagsFilter = tagsFilters.shift();
|
||||||
|
|
||||||
|
if (tagsFilter === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagsFilter instanceof And) {
|
||||||
|
tagsFilters.push(...tagsFilter.and);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagsFilter instanceof Tag) {
|
||||||
|
if (keyValues[tagsFilter.key] === undefined) {
|
||||||
|
keyValues[tagsFilter.key] = [];
|
||||||
|
}
|
||||||
|
keyValues[tagsFilter.key].push(...tagsFilter.value.split(";"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("Invalid type to flatten the multiAnswer", tagsFilter);
|
||||||
|
throw "Invalid type to FlattenMultiAnswer"
|
||||||
|
}
|
||||||
|
return keyValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set.
|
||||||
|
* E.g:
|
||||||
|
*
|
||||||
|
* FlattenMultiAnswer([and: [ "x=a", "y=0;1"], and: ["x=b", "y=2"], and: ["x=", "y=3"]])
|
||||||
|
* will result in
|
||||||
|
* ["x=a;b", "y=0;1;2;3"]
|
||||||
|
*
|
||||||
|
* @param tagsFilters
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
static FlattenMultiAnswer(tagsFilters: TagsFilter[]): And {
|
||||||
|
if (tagsFilters === undefined) {
|
||||||
|
return new And([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyValues = TagUtils.SplitKeys(tagsFilters);
|
||||||
|
const and: TagsFilter[] = []
|
||||||
|
for (const key in keyValues) {
|
||||||
|
and.push(new Tag(key, Utils.Dedup(keyValues[key]).join(";")));
|
||||||
|
}
|
||||||
|
return new And(and);
|
||||||
|
}
|
||||||
|
|
||||||
|
static MatchesMultiAnswer(tag: TagsFilter, tags: any): boolean {
|
||||||
|
const splitted = TagUtils.SplitKeys([tag]);
|
||||||
|
for (const splitKey in splitted) {
|
||||||
|
const neededValues = splitted[splitKey];
|
||||||
|
if (tags[splitKey] === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualValue = tags[splitKey].split(";");
|
||||||
|
for (const neededValue of neededValues) {
|
||||||
|
if (actualValue.indexOf(neededValue) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
497
Logic/Tags.ts
497
Logic/Tags.ts
|
@ -1,497 +0,0 @@
|
||||||
import {Utils} from "../Utils";
|
|
||||||
|
|
||||||
export abstract class TagsFilter {
|
|
||||||
|
|
||||||
abstract asOverpass(): string[]
|
|
||||||
|
|
||||||
abstract substituteValues(tags: any): TagsFilter;
|
|
||||||
|
|
||||||
abstract isUsableAsAnswer(): boolean;
|
|
||||||
|
|
||||||
abstract isEquivalent(other: TagsFilter): boolean;
|
|
||||||
|
|
||||||
abstract matchesProperties(properties: any): boolean;
|
|
||||||
|
|
||||||
abstract asHumanString(linkToWiki: boolean, shorten: boolean);
|
|
||||||
|
|
||||||
abstract usedKeys(): string[];
|
|
||||||
|
|
||||||
public matches(tags: {k: string, v: string}[]){
|
|
||||||
const properties = {};
|
|
||||||
for (const kv of tags) {
|
|
||||||
properties[kv.k] = kv.v;
|
|
||||||
}
|
|
||||||
return this.matchesProperties(properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class RegexTag extends TagsFilter {
|
|
||||||
private readonly key: RegExp | string;
|
|
||||||
private readonly value: RegExp | string;
|
|
||||||
private readonly invert: boolean;
|
|
||||||
private readonly matchesEmpty: boolean
|
|
||||||
|
|
||||||
constructor(key: string | RegExp, value: RegExp | string, invert: boolean = false) {
|
|
||||||
super();
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
this.invert = invert;
|
|
||||||
this.matchesEmpty = RegexTag.doesMatch("", this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean {
|
|
||||||
if (typeof possibleRegex === "string") {
|
|
||||||
return fromTag === possibleRegex;
|
|
||||||
}
|
|
||||||
return fromTag.match(possibleRegex) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static source(r: string | RegExp) {
|
|
||||||
if (typeof (r) === "string") {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
return r.source;
|
|
||||||
}
|
|
||||||
|
|
||||||
asOverpass(): string[] {
|
|
||||||
if (typeof this.key === "string") {
|
|
||||||
return [`['${this.key}'${this.invert ? "!" : ""}~'${RegexTag.source(this.value)}']`];
|
|
||||||
}
|
|
||||||
return [`[~'${this.key.source}'${this.invert ? "!" : ""}~'${RegexTag.source(this.value)}']`];
|
|
||||||
}
|
|
||||||
|
|
||||||
isUsableAsAnswer(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
matchesProperties(tags: any): boolean {
|
|
||||||
for (const key in tags) {
|
|
||||||
if (RegexTag.doesMatch(key, this.key)) {
|
|
||||||
const value = tags[key]
|
|
||||||
return RegexTag.doesMatch(value, this.value) != this.invert;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.matchesEmpty) {
|
|
||||||
// The value is 'empty'
|
|
||||||
return !this.invert;
|
|
||||||
}
|
|
||||||
// The matching key was not found
|
|
||||||
return this.invert;
|
|
||||||
}
|
|
||||||
|
|
||||||
substituteValues(tags: any): TagsFilter {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
asHumanString() {
|
|
||||||
if (typeof this.key === "string") {
|
|
||||||
return `${this.key}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`;
|
|
||||||
}
|
|
||||||
return `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
isEquivalent(other: TagsFilter): boolean {
|
|
||||||
if (other instanceof RegexTag) {
|
|
||||||
return other.asHumanString() == this.asHumanString();
|
|
||||||
}
|
|
||||||
if (other instanceof Tag) {
|
|
||||||
return RegexTag.doesMatch(other.key, this.key) && RegexTag.doesMatch(other.value, this.value);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
usedKeys(): string[] {
|
|
||||||
if (typeof this.key === "string") {
|
|
||||||
return [this.key];
|
|
||||||
}
|
|
||||||
throw "Key cannot be determined as it is a regex"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Tag extends TagsFilter {
|
|
||||||
public key: string
|
|
||||||
public value: string
|
|
||||||
|
|
||||||
constructor(key: string, value: string) {
|
|
||||||
super()
|
|
||||||
this.key = key
|
|
||||||
this.value = value
|
|
||||||
if (key === undefined || key === "") {
|
|
||||||
throw "Invalid key: undefined or empty";
|
|
||||||
}
|
|
||||||
if (value === undefined) {
|
|
||||||
throw "Invalid value: value is undefined";
|
|
||||||
}
|
|
||||||
if (value === "*") {
|
|
||||||
console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}~* ?`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
matchesProperties(properties: any): boolean {
|
|
||||||
for (const propertiesKey in properties) {
|
|
||||||
if(this.key === propertiesKey){
|
|
||||||
const value = properties[propertiesKey];
|
|
||||||
return value === this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The tag was not found
|
|
||||||
if (this.value === "") {
|
|
||||||
// and it shouldn't be found!
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
asOverpass(): string[] {
|
|
||||||
if (this.value === "") {
|
|
||||||
// NOT having this key
|
|
||||||
return ['[!"' + this.key + '"]'];
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
v = Utils.EllipsesAfter(v, 25);
|
|
||||||
}
|
|
||||||
if (linkToWiki) {
|
|
||||||
return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` +
|
|
||||||
`=` +
|
|
||||||
`<a href='https://wiki.openstreetmap.org/wiki/Tag:${this.key}%3D${this.value}' target='_blank'>${v}</a>`
|
|
||||||
}
|
|
||||||
return this.key + "=" + v;
|
|
||||||
}
|
|
||||||
|
|
||||||
isUsableAsAnswer(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
isEquivalent(other: TagsFilter): boolean {
|
|
||||||
if (other instanceof Tag) {
|
|
||||||
return this.key === other.key && this.value === other.value;
|
|
||||||
}
|
|
||||||
if (other instanceof RegexTag) {
|
|
||||||
other.isEquivalent(this);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
usedKeys(): string[] {
|
|
||||||
return [this.key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Or extends TagsFilter {
|
|
||||||
public or: TagsFilter[]
|
|
||||||
|
|
||||||
constructor(or: TagsFilter[]) {
|
|
||||||
super();
|
|
||||||
this.or = or;
|
|
||||||
}
|
|
||||||
|
|
||||||
matchesProperties(properties: any): boolean {
|
|
||||||
for (const tagsFilter of this.or) {
|
|
||||||
if (tagsFilter.matchesProperties(properties)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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("|");
|
|
||||||
}
|
|
||||||
|
|
||||||
isUsableAsAnswer(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
isEquivalent(other: TagsFilter): boolean {
|
|
||||||
if (other instanceof Or) {
|
|
||||||
|
|
||||||
for (const selfTag of this.or) {
|
|
||||||
let matchFound = false;
|
|
||||||
for (let i = 0; i < other.or.length && !matchFound; i++) {
|
|
||||||
let otherTag = other.or[i];
|
|
||||||
matchFound = selfTag.isEquivalent(otherTag);
|
|
||||||
}
|
|
||||||
if (!matchFound) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
usedKeys(): string[] {
|
|
||||||
return [].concat(...this.or.map(subkeys => subkeys.usedKeys()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class And extends TagsFilter {
|
|
||||||
public and: TagsFilter[]
|
|
||||||
|
|
||||||
constructor(and: TagsFilter[]) {
|
|
||||||
super();
|
|
||||||
this.and = and;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static combine(filter: string, choices: string[]): string[] {
|
|
||||||
const values = [];
|
|
||||||
for (const or of choices) {
|
|
||||||
values.push(filter + or);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
matchesProperties(tags: any): boolean {
|
|
||||||
for (const tagsFilter of this.and) {
|
|
||||||
if (!tagsFilter.matchesProperties(tags)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
asOverpass(): string[] {
|
|
||||||
let allChoices: string[] = null;
|
|
||||||
for (const andElement of this.and) {
|
|
||||||
const andElementFilter = andElement.asOverpass();
|
|
||||||
if (allChoices === null) {
|
|
||||||
allChoices = andElementFilter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newChoices: string[] = [];
|
|
||||||
for (const choice of allChoices) {
|
|
||||||
newChoices.push(
|
|
||||||
...And.combine(choice, andElementFilter)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
allChoices = newChoices;
|
|
||||||
}
|
|
||||||
return allChoices;
|
|
||||||
}
|
|
||||||
|
|
||||||
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("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
isUsableAsAnswer(): boolean {
|
|
||||||
for (const t of this.and) {
|
|
||||||
if (!t.isUsableAsAnswer()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
isEquivalent(other: TagsFilter): boolean {
|
|
||||||
if (!(other instanceof And)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const selfTag of this.and) {
|
|
||||||
let matchFound = false;
|
|
||||||
for (let i = 0; i < other.and.length && !matchFound; i++) {
|
|
||||||
let otherTag = other.and[i];
|
|
||||||
matchFound = selfTag.isEquivalent(otherTag);
|
|
||||||
}
|
|
||||||
if (!matchFound) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const selfTag of this.and) {
|
|
||||||
let matchFound = false;
|
|
||||||
for (const otherTag of other.and) {
|
|
||||||
matchFound = selfTag.isEquivalent(otherTag);
|
|
||||||
if (matchFound) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!matchFound) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const otherTag of other.and) {
|
|
||||||
let matchFound = false;
|
|
||||||
for (const selfTag of this.and) {
|
|
||||||
matchFound = selfTag.isEquivalent(otherTag);
|
|
||||||
if (matchFound) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!matchFound) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
usedKeys(): string[] {
|
|
||||||
return [].concat(...this.and.map(subkeys => subkeys.usedKeys()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class TagUtils {
|
|
||||||
static ApplyTemplate(template: string, tags: any): string {
|
|
||||||
for (const k in tags) {
|
|
||||||
while (template.indexOf("{" + k + "}") >= 0) {
|
|
||||||
const escaped = tags[k].replace(/</g, '<').replace(/>/g, '>');
|
|
||||||
template = template.replace("{" + k + "}", escaped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
static KVtoProperties(tags: Tag[]): any {
|
|
||||||
const properties = {};
|
|
||||||
for (const tag of tags) {
|
|
||||||
properties[tag.key] = tag.value
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given two hashes of {key --> values[]}, makes sure that every neededTag is present in availableTags
|
|
||||||
*/
|
|
||||||
static AllKeysAreContained(availableTags: any, neededTags: any) {
|
|
||||||
for (const neededKey in neededTags) {
|
|
||||||
const availableValues: string[] = availableTags[neededKey]
|
|
||||||
if (availableValues === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const neededValues: string[] = neededTags[neededKey];
|
|
||||||
for (const neededValue of neededValues) {
|
|
||||||
if (availableValues.indexOf(neededValue) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Creates a hash {key --> [values]}, with all the values present in the tagsfilter
|
|
||||||
*
|
|
||||||
* @param tagsFilters
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
static SplitKeys(tagsFilters: TagsFilter[]) {
|
|
||||||
const keyValues = {} // Map string -> string[]
|
|
||||||
tagsFilters = [...tagsFilters] // copy all
|
|
||||||
while (tagsFilters.length > 0) {
|
|
||||||
// Queue
|
|
||||||
const tagsFilter = tagsFilters.shift();
|
|
||||||
|
|
||||||
if (tagsFilter === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagsFilter instanceof And) {
|
|
||||||
tagsFilters.push(...tagsFilter.and);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagsFilter instanceof Tag) {
|
|
||||||
if (keyValues[tagsFilter.key] === undefined) {
|
|
||||||
keyValues[tagsFilter.key] = [];
|
|
||||||
}
|
|
||||||
keyValues[tagsFilter.key].push(...tagsFilter.value.split(";"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error("Invalid type to flatten the multiAnswer", tagsFilter);
|
|
||||||
throw "Invalid type to FlattenMultiAnswer"
|
|
||||||
}
|
|
||||||
return keyValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set.
|
|
||||||
* E.g:
|
|
||||||
*
|
|
||||||
* FlattenMultiAnswer([and: [ "x=a", "y=0;1"], and: ["x=b", "y=2"], and: ["x=", "y=3"]])
|
|
||||||
* will result in
|
|
||||||
* ["x=a;b", "y=0;1;2;3"]
|
|
||||||
*
|
|
||||||
* @param tagsFilters
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
static FlattenMultiAnswer(tagsFilters: TagsFilter[]): And {
|
|
||||||
if (tagsFilters === undefined) {
|
|
||||||
return new And([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let keyValues = TagUtils.SplitKeys(tagsFilters);
|
|
||||||
const and: TagsFilter[] = []
|
|
||||||
for (const key in keyValues) {
|
|
||||||
and.push(new Tag(key, Utils.Dedup(keyValues[key]).join(";")));
|
|
||||||
}
|
|
||||||
return new And(and);
|
|
||||||
}
|
|
||||||
|
|
||||||
static MatchesMultiAnswer(tag: TagsFilter, tags: any): boolean {
|
|
||||||
const splitted = TagUtils.SplitKeys([tag]);
|
|
||||||
for (const splitKey in splitted) {
|
|
||||||
const neededValues = splitted[splitKey];
|
|
||||||
if (tags[splitKey] === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actualValue = tags[splitKey].split(";");
|
|
||||||
for (const neededValue of neededValues) {
|
|
||||||
if (actualValue.indexOf(neededValue) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
24
Logic/TagsFilter.ts
Normal file
24
Logic/TagsFilter.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export abstract class TagsFilter {
|
||||||
|
|
||||||
|
abstract asOverpass(): string[]
|
||||||
|
|
||||||
|
abstract substituteValues(tags: any): TagsFilter;
|
||||||
|
|
||||||
|
abstract isUsableAsAnswer(): boolean;
|
||||||
|
|
||||||
|
abstract isEquivalent(other: TagsFilter): boolean;
|
||||||
|
|
||||||
|
abstract matchesProperties(properties: any): boolean;
|
||||||
|
|
||||||
|
abstract asHumanString(linkToWiki: boolean, shorten: boolean);
|
||||||
|
|
||||||
|
abstract usedKeys(): string[];
|
||||||
|
|
||||||
|
public matches(tags: { k: string, v: string }[]) {
|
||||||
|
const properties = {};
|
||||||
|
for (const kv of tags) {
|
||||||
|
properties[kv.k] = kv.v;
|
||||||
|
}
|
||||||
|
return this.matchesProperties(properties);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
*/
|
*/
|
||||||
import Locale from "../i18n/Locale";
|
import Locale from "../i18n/Locale";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {Tag, TagUtils} from "../../Logic/Tags";
|
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {SubtleButton} from "../Base/SubtleButton";
|
import {SubtleButton} from "../Base/SubtleButton";
|
||||||
|
@ -13,6 +12,8 @@ import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Constants from "../../Models/Constants";
|
import Constants from "../../Models/Constants";
|
||||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
|
import {Tag} from "../../Logic/Tag";
|
||||||
|
import {TagUtils} from "../../Logic/TagUtils";
|
||||||
|
|
||||||
export default class SimpleAddUI extends UIElement {
|
export default class SimpleAddUI extends UIElement {
|
||||||
private readonly _loginButton: UIElement;
|
private readonly _loginButton: UIElement;
|
||||||
|
|
|
@ -4,8 +4,8 @@ import Translations from "../i18n/Translations";
|
||||||
import CheckBox from "../Input/CheckBox";
|
import CheckBox from "../Input/CheckBox";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import {Tag} from "../../Logic/Tags";
|
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
|
import {Tag} from "../../Logic/Tag";
|
||||||
|
|
||||||
|
|
||||||
export default class DeleteImage extends UIElement {
|
export default class DeleteImage extends UIElement {
|
||||||
|
|
|
@ -6,9 +6,9 @@ import Combine from "../Base/Combine";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import {Imgur} from "../../Logic/Web/Imgur";
|
import {Imgur} from "../../Logic/Web/Imgur";
|
||||||
import {DropDown} from "../Input/DropDown";
|
import {DropDown} from "../Input/DropDown";
|
||||||
import {Tag} from "../../Logic/Tags";
|
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
|
import {Tag} from "../../Logic/Tag";
|
||||||
|
|
||||||
export class ImageUploadFlow extends UIElement {
|
export class ImageUploadFlow extends UIElement {
|
||||||
private readonly _licensePicker: UIElement;
|
private readonly _licensePicker: UIElement;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Combine from "../Base/Combine";
|
||||||
import TagRenderingAnswer from "./TagRenderingAnswer";
|
import TagRenderingAnswer from "./TagRenderingAnswer";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {TagUtils} from "../../Logic/Tags";
|
import {TagUtils} from "../../Logic/TagUtils";
|
||||||
|
|
||||||
export default class EditableTagRendering extends UIElement {
|
export default class EditableTagRendering extends UIElement {
|
||||||
private readonly _tags: UIEventSource<any>;
|
private readonly _tags: UIEventSource<any>;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import State from "../../State";
|
||||||
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
||||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {Tag} from "../../Logic/Tags";
|
import {Tag} from "../../Logic/Tag";
|
||||||
|
|
||||||
export default class FeatureInfoBox extends ScrollableFullScreen {
|
export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
||||||
import TagRenderingQuestion from "./TagRenderingQuestion";
|
import TagRenderingQuestion from "./TagRenderingQuestion";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import {TagUtils} from "../../Logic/Tags";
|
import {TagUtils} from "../../Logic/TagUtils";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,9 +3,9 @@ import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {TagUtils} from "../../Logic/Tags";
|
|
||||||
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
||||||
import {Translation} from "../i18n/Translation";
|
import {Translation} from "../i18n/Translation";
|
||||||
|
import {TagUtils} from "../../Logic/TagUtils";
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Displays the correct value for a known tagrendering
|
* Displays the correct value for a known tagrendering
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
||||||
import {InputElement} from "../Input/InputElement";
|
import {InputElement} from "../Input/InputElement";
|
||||||
import {And, Tag, TagsFilter, TagUtils} from "../../Logic/Tags";
|
|
||||||
import ValidatedTextField from "../Input/ValidatedTextField";
|
import ValidatedTextField from "../Input/ValidatedTextField";
|
||||||
import {FixedInputElement} from "../Input/FixedInputElement";
|
import {FixedInputElement} from "../Input/FixedInputElement";
|
||||||
import {RadioButton} from "../Input/RadioButton";
|
import {RadioButton} from "../Input/RadioButton";
|
||||||
|
@ -19,6 +18,10 @@ import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import {Translation} from "../i18n/Translation";
|
import {Translation} from "../i18n/Translation";
|
||||||
import Constants from "../../Models/Constants";
|
import Constants from "../../Models/Constants";
|
||||||
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
||||||
|
import {TagsFilter} from "../../Logic/TagsFilter";
|
||||||
|
import {Tag} from "../../Logic/Tag";
|
||||||
|
import {And} from "../../Logic/And";
|
||||||
|
import {TagUtils} from "../../Logic/TagUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the question element.
|
* Shows the question element.
|
||||||
|
|
|
@ -774,8 +774,8 @@
|
||||||
"de": "<h3>Kundenspezifische Themen</h3>Dies sind zuvor besuchte benutzergenerierte Themen"
|
"de": "<h3>Kundenspezifische Themen</h3>Dies sind zuvor besuchte benutzergenerierte Themen"
|
||||||
},
|
},
|
||||||
"aboutMapcomplete": {
|
"aboutMapcomplete": {
|
||||||
"en": "<h3>About MapComplete</h3><p>MapComplete is an OpenStreetMap editor that is meant to help everyone to easily add information on a <b>single theme.</b></p><p>Only features relevant to a single theme are shown with a few predefined questions, in order to keep things <b>simple and extremly user-friendly</b>.The theme maintainer can also choose a language for the interface, choose to disable elements or even to embed it into a different website without any UI-element at all.</p><p>However, another important part of MapComplete is to always <b>offer the next step</b> to learn more about OpenStreetMap:<ul><li>An iframe without UI-elements will link to a full-screen version</li><li>The fullscreen version offers information about OpenStreetMap</li><li>If you're not logged in, you're asked to log in</li><li>If you answered a single question, you are allowed to add points</li><li>At a certain point, the actual added tags appear which later get linked to the wiki...</li></ul></p><p>Do you notice an issue with MapComplete? Do you have a feature request? Do you want to help translating? Head over to <a href='https://github.com/pietervdvn/MapComplete' target='_blank'>the source code</a> or <a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'>issue tracker.</a> Follow the edit count on <a href='https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D' target='_blank' >OsmCha</a></p>",
|
"en": "<h3>About MapComplete</h3><p>MapComplete is an OpenStreetMap editor that is meant to help everyone to easily add information on a <b>single theme.</b></p><p>Only features relevant to a single theme are shown with a few predefined questions, in order to keep things <b>simple and extremly user-friendly</b>.The theme maintainer can also choose a language for the interface, choose to disable elements or even to embed it into a different website without any UI-element at all.</p><p>However, another important part of MapComplete is to always <b>offer the next step</b> to learn more about OpenStreetMap:<ul><li>An iframe without UI-elements will link to a full-screen version</li><li>The fullscreen version offers information about OpenStreetMap</li><li>If you're not logged in, you're asked to log in</li><li>If you answered a single question, you are allowed to add points</li><li>At a certain point, the actual added tags appear which later get linked to the wiki...</li></ul></p><p>Do you notice an issue with MapComplete? Do you have a feature request? Do you want to help translating? Head over to <a href='https://github.com/pietervdvn/MapComplete' target='_blank'>the source code</a> or <a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'>issue tracker.</a> Follow the edit count on <a href='https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222021-01-01%22%2C%22value%22%3A%222021-01-01%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D' target='_blank' >OsmCha</a></p>",
|
||||||
"nl": "<h3>Over MapComplete</h3><p>MapComplete is een OpenStreetMap-editor om eenvoudig informatie toe te voegen over <b>één enkel onderwerp</b>.</p><p>Om de editor zo <b>simpel en gebruiksvriendelijk mogelijk</b> te houden, worden enkel objecten relevant voor het thema getoond.Voor deze objecten kunnen dan vragen beantwoord worden, of men kan een nieuw punt van dit thema toevoegen.De maker van het thema kan er ook voor opteren om een aantal elementen van de gebruikersinterface uit te schakelen of de taal ervan in te stellen.</p><p>Een ander belangrijk aspect is om bezoekers stap voor stap meer te leren over OpenStreetMap:<ul><li>Een iframe zonder verdere uitleg linkt naar de volledige versie van MapComplete</li><li>De volledige versie heeft uitleg over OpenStreetMap</li><li>Als je niet aangemeld bent, wordt er je gevraagd dit te doen</li><li>Als je minstens één vraag hebt beantwoord, kan je punten gaan toevoegen.</li><li>Heb je genoeg changesets, dan verschijnen de tags die wat later doorlinken naar de wiki</li></ul></p><p>Merk je een bug of wil je een extra feature? Wil je helpen vertalen? Bezoek dan de <a href='https://github.com/pietervdvn/MapComplete' target='_blank'>broncode</a> en <a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'>issue tracker</a>. Volg de edits <a href='https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D' target='_blank' >op OsmCha</a></p>",
|
"nl": "<h3>Over MapComplete</h3><p>MapComplete is een OpenStreetMap-editor om eenvoudig informatie toe te voegen over <b>één enkel onderwerp</b>.</p><p>Om de editor zo <b>simpel en gebruiksvriendelijk mogelijk</b> te houden, worden enkel objecten relevant voor het thema getoond.Voor deze objecten kunnen dan vragen beantwoord worden, of men kan een nieuw punt van dit thema toevoegen.De maker van het thema kan er ook voor opteren om een aantal elementen van de gebruikersinterface uit te schakelen of de taal ervan in te stellen.</p><p>Een ander belangrijk aspect is om bezoekers stap voor stap meer te leren over OpenStreetMap:<ul><li>Een iframe zonder verdere uitleg linkt naar de volledige versie van MapComplete</li><li>De volledige versie heeft uitleg over OpenStreetMap</li><li>Als je niet aangemeld bent, wordt er je gevraagd dit te doen</li><li>Als je minstens één vraag hebt beantwoord, kan je punten gaan toevoegen.</li><li>Heb je genoeg changesets, dan verschijnen de tags die wat later doorlinken naar de wiki</li></ul></p><p>Merk je een bug of wil je een extra feature? Wil je helpen vertalen? Bezoek dan de <a href='https://github.com/pietervdvn/MapComplete' target='_blank'>broncode</a> en <a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'>issue tracker</a>. Volg de edits <a href='https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222021-01-01%22%2C%22value%22%3A%222021-01-01%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D' target='_blank' >op OsmCha</a></p>",
|
||||||
"de": "<h3>Über MapComplete</h3><p>MapComplete ist ein OpenStreetMap-Editor, der jedem helfen soll, auf einfache Weise Informationen zu einem <b>Einzelthema hinzuzufügen.</b></p><p>Nur Merkmale, die für ein einzelnes Thema relevant sind, werden mit einigen vordefinierten Fragen gezeigt, um die Dinge <b>einfach und extrem benutzerfreundlich</b> zu halten.Der Themen-Betreuer kann auch eine Sprache für die Schnittstelle wählen, Elemente deaktivieren oder sogar in eine andere Website ohne jegliches UI-Element einbetten.</p><p>Ein weiterer wichtiger Teil von MapComplete ist jedoch, immer <b>den nächsten Schritt anzubieten</b>um mehr über OpenStreetMap zu erfahren:<ul><li>Ein iframe ohne UI-Elemente verlinkt zu einer Vollbildversion</li><li>Die Vollbildversion bietet Informationen über OpenStreetMap</li><li>Wenn Sie nicht eingeloggt sind, werden Sie gebeten, sich einzuloggen</li><li>Wenn Sie eine einzige Frage beantwortet haben, dürfen Sie Punkte hinzufügen</li><li>An einem bestimmten Punkt erscheinen die tatsächlich hinzugefügten Tags, die später mit dem Wiki verlinkt werden...</li></ul></p><p>Fällt Ihnen ein Problem mit MapComplete auf? Haben Sie einen Feature-Wunsch? Wollen Sie beim Übersetzen helfen? Gehen Sie <a href='https://github.com/pietervdvn/MapComplete' target='_blank'>zum Quellcode</a> oder <a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'>zur Problemverfolgung</a>.</p>"
|
"de": "<h3>Über MapComplete</h3><p>MapComplete ist ein OpenStreetMap-Editor, der jedem helfen soll, auf einfache Weise Informationen zu einem <b>Einzelthema hinzuzufügen.</b></p><p>Nur Merkmale, die für ein einzelnes Thema relevant sind, werden mit einigen vordefinierten Fragen gezeigt, um die Dinge <b>einfach und extrem benutzerfreundlich</b> zu halten.Der Themen-Betreuer kann auch eine Sprache für die Schnittstelle wählen, Elemente deaktivieren oder sogar in eine andere Website ohne jegliches UI-Element einbetten.</p><p>Ein weiterer wichtiger Teil von MapComplete ist jedoch, immer <b>den nächsten Schritt anzubieten</b>um mehr über OpenStreetMap zu erfahren:<ul><li>Ein iframe ohne UI-Elemente verlinkt zu einer Vollbildversion</li><li>Die Vollbildversion bietet Informationen über OpenStreetMap</li><li>Wenn Sie nicht eingeloggt sind, werden Sie gebeten, sich einzuloggen</li><li>Wenn Sie eine einzige Frage beantwortet haben, dürfen Sie Punkte hinzufügen</li><li>An einem bestimmten Punkt erscheinen die tatsächlich hinzugefügten Tags, die später mit dem Wiki verlinkt werden...</li></ul></p><p>Fällt Ihnen ein Problem mit MapComplete auf? Haben Sie einen Feature-Wunsch? Wollen Sie beim Übersetzen helfen? Gehen Sie <a href='https://github.com/pietervdvn/MapComplete' target='_blank'>zum Quellcode</a> oder <a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'>zur Problemverfolgung</a>.</p>"
|
||||||
},
|
},
|
||||||
"backgroundMap": {
|
"backgroundMap": {
|
||||||
|
|
|
@ -3,7 +3,6 @@ Utils.runningFromConsole = true;
|
||||||
import {equal} from "assert";
|
import {equal} from "assert";
|
||||||
import T from "./TestHelper";
|
import T from "./TestHelper";
|
||||||
import {FromJSON} from "../Customizations/JSON/FromJSON";
|
import {FromJSON} from "../Customizations/JSON/FromJSON";
|
||||||
import {And, Tag} from "../Logic/Tags";
|
|
||||||
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 {UIEventSource} from "../Logic/UIEventSource";
|
||||||
|
@ -13,6 +12,8 @@ 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 PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput";
|
||||||
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
|
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
|
||||||
|
import {Tag} from "../Logic/Tag";
|
||||||
|
import {And} from "../Logic/And";
|
||||||
|
|
||||||
|
|
||||||
new T("Tags", [
|
new T("Tags", [
|
||||||
|
|
|
@ -5,8 +5,6 @@ Utils.runningFromConsole = true;
|
||||||
import TagRenderingQuestion from "../UI/Popup/TagRenderingQuestion";
|
import TagRenderingQuestion from "../UI/Popup/TagRenderingQuestion";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
|
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
|
||||||
import {equal} from "assert";
|
|
||||||
import * as assert from "assert";
|
|
||||||
|
|
||||||
|
|
||||||
new T("TagQuestionElement",
|
new T("TagQuestionElement",
|
||||||
|
|
Loading…
Reference in a new issue