Optimize queries to overpass
This commit is contained in:
parent
fbcb72df7a
commit
9008e333ac
15 changed files with 787 additions and 18 deletions
|
@ -25,7 +25,12 @@ export class Overpass {
|
||||||
includeMeta = true) {
|
includeMeta = true) {
|
||||||
this._timeout = timeout;
|
this._timeout = timeout;
|
||||||
this._interpreterUrl = interpreterUrl;
|
this._interpreterUrl = interpreterUrl;
|
||||||
this._filter = filter
|
const optimized = filter.optimize()
|
||||||
|
if(optimized === true || optimized === false){
|
||||||
|
throw "Invalid filter: optimizes to true of false"
|
||||||
|
}
|
||||||
|
this._filter = optimized
|
||||||
|
console.log("Overpass filter is",this._filter)
|
||||||
this._extraScripts = extraScripts;
|
this._extraScripts = extraScripts;
|
||||||
this._includeMeta = includeMeta;
|
this._includeMeta = includeMeta;
|
||||||
this._relationTracker = relationTracker
|
this._relationTracker = relationTracker
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import {TagsFilter} from "./TagsFilter";
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
import {Or} from "./Or";
|
||||||
|
import {TagUtils} from "./TagUtils";
|
||||||
|
|
||||||
export class And extends TagsFilter {
|
export class And extends TagsFilter {
|
||||||
public and: TagsFilter[]
|
public and: TagsFilter[]
|
||||||
|
@ -109,6 +111,10 @@ export class And extends TagsFilter {
|
||||||
usedKeys(): string[] {
|
usedKeys(): string[] {
|
||||||
return [].concat(...this.and.map(subkeys => subkeys.usedKeys()));
|
return [].concat(...this.and.map(subkeys => subkeys.usedKeys()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedTags(): { key: string; value: string }[] {
|
||||||
|
return [].concat(...this.and.map(subkeys => subkeys.usedTags()));
|
||||||
|
}
|
||||||
|
|
||||||
asChange(properties: any): { k: string; v: string }[] {
|
asChange(properties: any): { k: string; v: string }[] {
|
||||||
const result = []
|
const result = []
|
||||||
|
@ -123,4 +129,89 @@ export class And extends TagsFilter {
|
||||||
and: this.and.map(a => a.AsJson())
|
and: this.and.map(a => a.AsJson())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optimize(): TagsFilter | boolean {
|
||||||
|
if(this.and.length === 0){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const optimized = this.and.map(t => t.optimize())
|
||||||
|
|
||||||
|
const newAnds : TagsFilter[] = []
|
||||||
|
|
||||||
|
let containedOrs : Or[] = []
|
||||||
|
for (const tf of optimized) {
|
||||||
|
if(tf === false){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(tf === true){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tf instanceof And){
|
||||||
|
newAnds.push(...tf.and)
|
||||||
|
}else if(tf instanceof Or){
|
||||||
|
containedOrs.push(tf)
|
||||||
|
} else {
|
||||||
|
newAnds.push(tf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
containedOrs = containedOrs.filter(ca => {
|
||||||
|
for (const element of ca.or) {
|
||||||
|
if(optimized.some(opt => typeof opt !== "boolean" && element.isEquivalent(opt) )){
|
||||||
|
// At least one part of the 'OR' is matched by the outer or, so this means that this OR isn't needed at all
|
||||||
|
// XY & (XY | AB) === XY
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
|
||||||
|
// Extract common keys from the OR
|
||||||
|
if(containedOrs.length === 1){
|
||||||
|
newAnds.push(containedOrs[0])
|
||||||
|
}
|
||||||
|
if(containedOrs.length > 1){
|
||||||
|
let commonValues : TagsFilter [] = containedOrs[0].or
|
||||||
|
for (let i = 1; i < containedOrs.length && commonValues.length > 0; i++){
|
||||||
|
const containedOr = containedOrs[i];
|
||||||
|
commonValues = commonValues.filter(cv => containedOr.or.some(candidate => candidate.isEquivalent(cv)))
|
||||||
|
}
|
||||||
|
if(commonValues.length === 0){
|
||||||
|
newAnds.push(...containedOrs)
|
||||||
|
}else{
|
||||||
|
const newOrs: TagsFilter[] = []
|
||||||
|
for (const containedOr of containedOrs) {
|
||||||
|
const elements = containedOr.or
|
||||||
|
.filter(candidate => !commonValues.some(cv => cv.isEquivalent(candidate)))
|
||||||
|
const or = new Or(elements).optimize()
|
||||||
|
if(or === true){
|
||||||
|
// neutral element
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if(or === false){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
newOrs.push(or)
|
||||||
|
}
|
||||||
|
|
||||||
|
commonValues.push(new And(newOrs))
|
||||||
|
const result = new Or(commonValues).optimize()
|
||||||
|
if(result === false){
|
||||||
|
return false
|
||||||
|
}else if(result === true){
|
||||||
|
// neutral element: skip
|
||||||
|
}else{
|
||||||
|
newAnds.push(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newAnds.length === 1){
|
||||||
|
return newAnds[0]
|
||||||
|
}
|
||||||
|
TagUtils.sortFilters(newAnds, true)
|
||||||
|
|
||||||
|
return new And(newAnds)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -38,9 +38,16 @@ export default class ComparingTag implements TagsFilter {
|
||||||
usedKeys(): string[] {
|
usedKeys(): string[] {
|
||||||
return [this._key];
|
return [this._key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedTags(): { key: string; value: string }[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
AsJson() {
|
AsJson() {
|
||||||
return this.asHumanString(false, false, {})
|
return this.asHumanString(false, false, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optimize(): TagsFilter | boolean {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import {TagsFilter} from "./TagsFilter";
|
import {TagsFilter} from "./TagsFilter";
|
||||||
|
import {TagUtils} from "./TagUtils";
|
||||||
|
import {And} from "./And";
|
||||||
|
|
||||||
|
|
||||||
export class Or extends TagsFilter {
|
export class Or extends TagsFilter {
|
||||||
|
@ -58,6 +60,10 @@ export class Or extends TagsFilter {
|
||||||
return [].concat(...this.or.map(subkeys => subkeys.usedKeys()));
|
return [].concat(...this.or.map(subkeys => subkeys.usedKeys()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedTags(): { key: string; value: string }[] {
|
||||||
|
return [].concat(...this.or.map(subkeys => subkeys.usedTags()));
|
||||||
|
}
|
||||||
|
|
||||||
asChange(properties: any): { k: string; v: string }[] {
|
asChange(properties: any): { k: string; v: string }[] {
|
||||||
const result = []
|
const result = []
|
||||||
for (const tagsFilter of this.or) {
|
for (const tagsFilter of this.or) {
|
||||||
|
@ -71,6 +77,83 @@ export class Or extends TagsFilter {
|
||||||
or: this.or.map(o => o.AsJson())
|
or: this.or.map(o => o.AsJson())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optimize(): TagsFilter | boolean {
|
||||||
|
|
||||||
|
if(this.or.length === 0){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const optimized = this.or.map(t => t.optimize())
|
||||||
|
|
||||||
|
const newOrs : TagsFilter[] = []
|
||||||
|
|
||||||
|
let containedAnds : And[] = []
|
||||||
|
for (const tf of optimized) {
|
||||||
|
if(tf === true){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if(tf === false){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tf instanceof Or){
|
||||||
|
newOrs.push(...tf.or)
|
||||||
|
}else if(tf instanceof And){
|
||||||
|
containedAnds.push(tf)
|
||||||
|
} else {
|
||||||
|
newOrs.push(tf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
containedAnds = containedAnds.filter(ca => {
|
||||||
|
for (const element of ca.and) {
|
||||||
|
if(optimized.some(opt => typeof opt !== "boolean" && element.isEquivalent(opt) )){
|
||||||
|
// At least one part of the 'AND' is matched by the outer or, so this means that this OR isn't needed at all
|
||||||
|
// XY | (XY & AB) === XY
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
|
||||||
|
// Extract common keys from the ANDS
|
||||||
|
if(containedAnds.length === 1){
|
||||||
|
newOrs.push(containedAnds[0])
|
||||||
|
} else if(containedAnds.length > 1){
|
||||||
|
let commonValues : TagsFilter [] = containedAnds[0].and
|
||||||
|
for (let i = 1; i < containedAnds.length && commonValues.length > 0; i++){
|
||||||
|
const containedAnd = containedAnds[i];
|
||||||
|
commonValues = commonValues.filter(cv => containedAnd.and.some(candidate => candidate.isEquivalent(cv)))
|
||||||
|
}
|
||||||
|
if(commonValues.length === 0){
|
||||||
|
newOrs.push(...containedAnds)
|
||||||
|
}else{
|
||||||
|
const newAnds: TagsFilter[] = []
|
||||||
|
for (const containedAnd of containedAnds) {
|
||||||
|
const elements = containedAnd.and.filter(candidate => !commonValues.some(cv => cv.isEquivalent(candidate)))
|
||||||
|
newAnds.push(new And(elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
commonValues.push(new Or(newAnds))
|
||||||
|
const result = new And(commonValues).optimize()
|
||||||
|
if(result === true){
|
||||||
|
return true
|
||||||
|
}else if(result === false){
|
||||||
|
// neutral element: skip
|
||||||
|
}else{
|
||||||
|
newOrs.push(new And(commonValues))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newOrs.length === 1){
|
||||||
|
return newOrs[0]
|
||||||
|
}
|
||||||
|
TagUtils.sortFilters(newOrs, false)
|
||||||
|
|
||||||
|
return new Or(newOrs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -43,10 +43,24 @@ export class RegexTag extends TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
asOverpass(): string[] {
|
asOverpass(): string[] {
|
||||||
if (typeof this.key === "string") {
|
const inv =this.invert ? "!" : ""
|
||||||
return [`["${this.key}"${this.invert ? "!" : ""}~"${RegexTag.source(this.value)}"]`];
|
if (typeof this.key !== "string") {
|
||||||
|
// The key is a regex too
|
||||||
|
return [`[~"${this.key.source}"${inv}~"${RegexTag.source(this.value)}"]`];
|
||||||
}
|
}
|
||||||
return [`[~"${this.key.source}"${this.invert ? "!" : ""}~"${RegexTag.source(this.value)}"]`];
|
|
||||||
|
if(this.value instanceof RegExp){
|
||||||
|
const src =this.value.source
|
||||||
|
if(src === "^..*$"){
|
||||||
|
// anything goes
|
||||||
|
return [`[${inv}"${this.key}"]`]
|
||||||
|
}
|
||||||
|
return [`["${this.key}"${inv}~"${src}"]`]
|
||||||
|
}else{
|
||||||
|
// Normal key and normal value
|
||||||
|
return [`["${this.key}"${inv}="${this.value}"]`];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isUsableAsAnswer(): boolean {
|
isUsableAsAnswer(): boolean {
|
||||||
|
@ -99,6 +113,10 @@ export class RegexTag extends TagsFilter {
|
||||||
}
|
}
|
||||||
throw "Key cannot be determined as it is a regex"
|
throw "Key cannot be determined as it is a regex"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedTags(): { key: string; value: string }[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
asChange(properties: any): { k: string; v: string }[] {
|
asChange(properties: any): { k: string; v: string }[] {
|
||||||
if (this.invert) {
|
if (this.invert) {
|
||||||
|
@ -120,4 +138,8 @@ export class RegexTag extends TagsFilter {
|
||||||
AsJson() {
|
AsJson() {
|
||||||
return this.asHumanString()
|
return this.asHumanString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optimize(): TagsFilter | boolean {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -59,6 +59,10 @@ export default class SubstitutingTag implements TagsFilter {
|
||||||
return [this._key];
|
return [this._key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedTags(): { key: string; value: string }[] {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
asChange(properties: any): { k: string; v: string }[] {
|
asChange(properties: any): { k: string; v: string }[] {
|
||||||
if (this._invert) {
|
if (this._invert) {
|
||||||
throw "An inverted substituting tag can not be used to create a change"
|
throw "An inverted substituting tag can not be used to create a change"
|
||||||
|
@ -73,4 +77,8 @@ export default class SubstitutingTag implements TagsFilter {
|
||||||
AsJson() {
|
AsJson() {
|
||||||
return this._key + (this._invert ? '!' : '') + "=" + this._value
|
return this._key + (this._invert ? '!' : '') + "=" + this._value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optimize(): TagsFilter | boolean {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -80,6 +80,13 @@ export class Tag extends TagsFilter {
|
||||||
return [this.key];
|
return [this.key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedTags(): { key: string; value: string }[] {
|
||||||
|
if(this.value == ""){
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [this]
|
||||||
|
}
|
||||||
|
|
||||||
asChange(properties: any): { k: string; v: string }[] {
|
asChange(properties: any): { k: string; v: string }[] {
|
||||||
return [{k: this.key, v: this.value}];
|
return [{k: this.key, v: this.value}];
|
||||||
}
|
}
|
||||||
|
@ -87,4 +94,8 @@ export class Tag extends TagsFilter {
|
||||||
AsJson() {
|
AsJson() {
|
||||||
return this.asHumanString(false, false)
|
return this.asHumanString(false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optimize(): TagsFilter | boolean {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -8,8 +8,10 @@ import SubstitutingTag from "./SubstitutingTag";
|
||||||
import {Or} from "./Or";
|
import {Or} from "./Or";
|
||||||
import {AndOrTagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
|
import {AndOrTagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||||
import {isRegExp} from "util";
|
import {isRegExp} from "util";
|
||||||
|
import * as key_counts from "../../assets/key_totals.json"
|
||||||
|
|
||||||
export class TagUtils {
|
export class TagUtils {
|
||||||
|
private static keyCounts : {keys: any, tags: any} = key_counts["default"] ?? key_counts
|
||||||
private static comparators
|
private static comparators
|
||||||
: [string, (a: number, b: number) => boolean][]
|
: [string, (a: number, b: number) => boolean][]
|
||||||
= [
|
= [
|
||||||
|
@ -174,6 +176,29 @@ export class TagUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INLINE sort of the given list
|
||||||
|
*/
|
||||||
|
public static sortFilters(filters: TagsFilter [], usePopularity: boolean): void {
|
||||||
|
filters.sort((a,b) => TagUtils.order(a, b, usePopularity))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toString(f: TagsFilter, toplevel = true): string {
|
||||||
|
let r: string
|
||||||
|
if (f instanceof Or) {
|
||||||
|
r = TagUtils.joinL(f.or, "|", toplevel)
|
||||||
|
} else if (f instanceof And) {
|
||||||
|
r = TagUtils.joinL(f.and, "&", toplevel)
|
||||||
|
} else {
|
||||||
|
r = f.asHumanString(false, false, {})
|
||||||
|
}
|
||||||
|
if(toplevel){
|
||||||
|
r = r.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
|
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
|
||||||
|
|
||||||
if (json === undefined) {
|
if (json === undefined) {
|
||||||
|
@ -285,10 +310,11 @@ export class TagUtils {
|
||||||
throw `Error while parsing tag '${tag}' in ${context}: no key part and value part were found`
|
throw `Error while parsing tag '${tag}' in ${context}: no key part and value part were found`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(json.and !== undefined && json.or !== undefined){
|
if (json.and !== undefined && json.or !== undefined) {
|
||||||
throw `Error while parsing a TagConfig: got an object where both 'and' and 'or' are defined`}
|
throw `Error while parsing a TagConfig: got an object where both 'and' and 'or' are defined`
|
||||||
|
}
|
||||||
|
|
||||||
if (json.and !== undefined) {
|
if (json.and !== undefined) {
|
||||||
return new And(json.and.map(t => TagUtils.Tag(t, context)));
|
return new And(json.and.map(t => TagUtils.Tag(t, context)));
|
||||||
}
|
}
|
||||||
|
@ -296,4 +322,56 @@ export class TagUtils {
|
||||||
return new Or(json.or.map(t => TagUtils.Tag(t, context)));
|
return new Or(json.or.map(t => TagUtils.Tag(t, context)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static GetCount(key: string, value?: string) {
|
||||||
|
if(key === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const tag = TagUtils.keyCounts.tags[key]
|
||||||
|
if(tag !== undefined && tag[value] !== undefined) {
|
||||||
|
return tag[value]
|
||||||
|
}
|
||||||
|
return TagUtils.keyCounts.keys[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
private static order(a: TagsFilter, b: TagsFilter, usePopularity: boolean): number {
|
||||||
|
const rta = a instanceof RegexTag
|
||||||
|
const rtb = b instanceof RegexTag
|
||||||
|
if(rta !== rtb) {
|
||||||
|
// Regex tags should always go at the end: these use a lot of computation at the overpass side, avoiding it is better
|
||||||
|
if(rta) {
|
||||||
|
return 1 // b < a
|
||||||
|
}else {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a["key"] !== undefined && b["key"] !== undefined) {
|
||||||
|
if(usePopularity) {
|
||||||
|
const countA = TagUtils.GetCount(a["key"], a["value"])
|
||||||
|
const countB = TagUtils.GetCount(b["key"], b["value"])
|
||||||
|
if(countA !== undefined && countB !== undefined) {
|
||||||
|
return countA - countB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a["key"] === b["key"]) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (a["key"] < b["key"]) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private static joinL(tfs: TagsFilter[], seperator: string, toplevel: boolean) {
|
||||||
|
const joined = tfs.map(e => TagUtils.toString(e, false)).join(seperator)
|
||||||
|
if (toplevel) {
|
||||||
|
return joined
|
||||||
|
}
|
||||||
|
return " (" + joined + ") "
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -12,6 +12,12 @@ export abstract class TagsFilter {
|
||||||
|
|
||||||
abstract usedKeys(): string[];
|
abstract usedKeys(): string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all normal key/value pairs
|
||||||
|
* Regex tags, substitutions, comparisons, ... are exempt
|
||||||
|
*/
|
||||||
|
abstract usedTags(): {key: string, value: string}[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the tagsFilter into a list of key-values that should be uploaded to OSM.
|
* Converts the tagsFilter into a list of key-values that should be uploaded to OSM.
|
||||||
* Throws an error if not applicable.
|
* Throws an error if not applicable.
|
||||||
|
@ -21,4 +27,11 @@ export abstract class TagsFilter {
|
||||||
abstract asChange(properties: any): { k: string, v: string }[]
|
abstract asChange(properties: any): { k: string, v: string }[]
|
||||||
|
|
||||||
abstract AsJson() ;
|
abstract AsJson() ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an optimized version (or self) of this tagsFilter
|
||||||
|
*/
|
||||||
|
abstract optimize(): TagsFilter | boolean;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ import {Utils} from "../Utils";
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
|
|
||||||
public static vNumber = "0.17.0-alpha-1";
|
public static vNumber = "0.17.0-alpha-2";
|
||||||
|
|
||||||
public static ImgurApiKey = '7070e7167f0a25a'
|
public static ImgurApiKey = '7070e7167f0a25a'
|
||||||
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
|
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
|
||||||
|
|
229
assets/key_totals.json
Normal file
229
assets/key_totals.json
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
{
|
||||||
|
"keys": {
|
||||||
|
"addr:street": 117211930,
|
||||||
|
"addr:housenumber": 125040768,
|
||||||
|
"emergency": 1939478,
|
||||||
|
"barrier": 18424246,
|
||||||
|
"tourism": 2683525,
|
||||||
|
"amenity": 20541353,
|
||||||
|
"bench": 894256,
|
||||||
|
"rental": 8838,
|
||||||
|
"bicycle_rental": 7447,
|
||||||
|
"vending": 206755,
|
||||||
|
"service:bicycle:rental": 3570,
|
||||||
|
"pub": 316,
|
||||||
|
"theme": 426,
|
||||||
|
"service:bicycle:.*": 0,
|
||||||
|
"service:bicycle:cleaning": 807,
|
||||||
|
"shop": 5062252,
|
||||||
|
"service:bicycle:retail": 9162,
|
||||||
|
"network": 2181336,
|
||||||
|
"sport": 2194801,
|
||||||
|
"service:bicycle:repair": 11381,
|
||||||
|
"association": 369,
|
||||||
|
"ngo": 42,
|
||||||
|
"leisure": 7368076,
|
||||||
|
"club": 38429,
|
||||||
|
"disused:amenity": 40880,
|
||||||
|
"planned:amenity": 205,
|
||||||
|
"tileId": 0,
|
||||||
|
"construction:amenity": 1206,
|
||||||
|
"cycleway": 906487,
|
||||||
|
"highway": 218189453,
|
||||||
|
"bicycle": 6218071,
|
||||||
|
"cyclestreet": 8185,
|
||||||
|
"camera:direction": 40676,
|
||||||
|
"direction": 1896015,
|
||||||
|
"access": 16030036,
|
||||||
|
"entrance": 2954076,
|
||||||
|
"name:etymology": 24485,
|
||||||
|
"memorial": 132172,
|
||||||
|
"indoor": 353116,
|
||||||
|
"name:etymology:wikidata": 285224,
|
||||||
|
"landuse": 35524214,
|
||||||
|
"name": 88330405,
|
||||||
|
"protect_class": 73801,
|
||||||
|
"information": 831513,
|
||||||
|
"man_made": 5116088,
|
||||||
|
"boundary": 2142378,
|
||||||
|
"tower:type": 451658,
|
||||||
|
"playground": 109175,
|
||||||
|
"route": 939184,
|
||||||
|
"surveillance:type": 116760,
|
||||||
|
"natural": 52353504,
|
||||||
|
"building": 500469053
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"emergency": {
|
||||||
|
"defibrillator": 51273,
|
||||||
|
"ambulance_station": 11047,
|
||||||
|
"fire_extinguisher": 7355,
|
||||||
|
"fire_hydrant": 1598739
|
||||||
|
},
|
||||||
|
"barrier": {
|
||||||
|
"cycle_barrier": 104166,
|
||||||
|
"bollard": 502220,
|
||||||
|
"wall": 3535056
|
||||||
|
},
|
||||||
|
"tourism": {
|
||||||
|
"artwork": 187470,
|
||||||
|
"map": 51,
|
||||||
|
"viewpoint": 191765
|
||||||
|
},
|
||||||
|
"amenity": {
|
||||||
|
"bench": 1736979,
|
||||||
|
"bicycle_library": 36,
|
||||||
|
"bicycle_rental": 49082,
|
||||||
|
"vending_machine": 201871,
|
||||||
|
"bar": 199662,
|
||||||
|
"pub": 174979,
|
||||||
|
"cafe": 467521,
|
||||||
|
"restaurant": 1211671,
|
||||||
|
"bicycle_wash": 44,
|
||||||
|
"bike_wash": 0,
|
||||||
|
"bicycle_repair_station": 9247,
|
||||||
|
"bicycle_parking": 435959,
|
||||||
|
"binoculars": 479,
|
||||||
|
"biergarten": 10309,
|
||||||
|
"charging_station": 65402,
|
||||||
|
"drinking_water": 250463,
|
||||||
|
"fast_food": 460079,
|
||||||
|
"fire_station": 122200,
|
||||||
|
"parking": 4255206,
|
||||||
|
"public_bookcase": 13120,
|
||||||
|
"toilets": 350648,
|
||||||
|
"recycling": 333925,
|
||||||
|
"waste_basket": 550357,
|
||||||
|
"waste_disposal": 156765
|
||||||
|
},
|
||||||
|
"bench": {
|
||||||
|
"stand_up_bench": 87,
|
||||||
|
"yes": 524993
|
||||||
|
},
|
||||||
|
"service:bicycle:rental": {
|
||||||
|
"yes": 3054
|
||||||
|
},
|
||||||
|
"pub": {
|
||||||
|
"cycling": 9,
|
||||||
|
"bicycle": 0
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"cycling": 8,
|
||||||
|
"bicycle": 16
|
||||||
|
},
|
||||||
|
"service:bicycle:cleaning": {
|
||||||
|
"yes": 607,
|
||||||
|
"diy": 0
|
||||||
|
},
|
||||||
|
"shop": {
|
||||||
|
"bicycle": 46488,
|
||||||
|
"sports": 37024
|
||||||
|
},
|
||||||
|
"sport": {
|
||||||
|
"cycling": 6045,
|
||||||
|
"bicycle": 96
|
||||||
|
},
|
||||||
|
"association": {
|
||||||
|
"cycling": 5,
|
||||||
|
"bicycle": 20
|
||||||
|
},
|
||||||
|
"ngo": {
|
||||||
|
"cycling": 0,
|
||||||
|
"bicycle": 0
|
||||||
|
},
|
||||||
|
"leisure": {
|
||||||
|
"bird_hide": 5669,
|
||||||
|
"nature_reserve": 117016,
|
||||||
|
"picnic_table": 206322,
|
||||||
|
"pitch": 1990293,
|
||||||
|
"playground": 705102
|
||||||
|
},
|
||||||
|
"club": {
|
||||||
|
"cycling": 3,
|
||||||
|
"bicycle": 49
|
||||||
|
},
|
||||||
|
"disused:amenity": {
|
||||||
|
"charging_station": 164
|
||||||
|
},
|
||||||
|
"planned:amenity": {
|
||||||
|
"charging_station": 115
|
||||||
|
},
|
||||||
|
"construction:amenity": {
|
||||||
|
"charging_station": 221
|
||||||
|
},
|
||||||
|
"cycleway": {
|
||||||
|
"lane": 314576,
|
||||||
|
"track": 86541,
|
||||||
|
"shared_lane": 60824
|
||||||
|
},
|
||||||
|
"highway": {
|
||||||
|
"residential": 61321708,
|
||||||
|
"crossing": 6119521,
|
||||||
|
"cycleway": 1423789,
|
||||||
|
"traffic_signals": 1512639,
|
||||||
|
"tertiary": 7051727,
|
||||||
|
"unclassified": 15756878,
|
||||||
|
"secondary": 4486617,
|
||||||
|
"primary": 3110552,
|
||||||
|
"footway": 16496620,
|
||||||
|
"path": 11438303,
|
||||||
|
"steps": 1327396,
|
||||||
|
"corridor": 27051,
|
||||||
|
"pedestrian": 685989,
|
||||||
|
"bridleway": 102280,
|
||||||
|
"track": 22670967,
|
||||||
|
"living_street": 1519108,
|
||||||
|
"street_lamp": 2811705
|
||||||
|
},
|
||||||
|
"bicycle": {
|
||||||
|
"designated": 1110839
|
||||||
|
},
|
||||||
|
"cyclestreet": {
|
||||||
|
"yes": 8164
|
||||||
|
},
|
||||||
|
"access": {
|
||||||
|
"public": 6222,
|
||||||
|
"yes": 1363526
|
||||||
|
},
|
||||||
|
"memorial": {
|
||||||
|
"ghost_bike": 503
|
||||||
|
},
|
||||||
|
"indoor": {
|
||||||
|
"door": 9722
|
||||||
|
},
|
||||||
|
"landuse": {
|
||||||
|
"grass": 4898559,
|
||||||
|
"village_green": 104681
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"Park Oude God": 1
|
||||||
|
},
|
||||||
|
"information": {
|
||||||
|
"board": 242007,
|
||||||
|
"map": 85912,
|
||||||
|
"office": 24139,
|
||||||
|
"visitor_centre": 285
|
||||||
|
},
|
||||||
|
"man_made": {
|
||||||
|
"surveillance": 148172,
|
||||||
|
"watermill": 9699
|
||||||
|
},
|
||||||
|
"boundary": {
|
||||||
|
"protected_area": 97075
|
||||||
|
},
|
||||||
|
"tower:type": {
|
||||||
|
"observation": 19654
|
||||||
|
},
|
||||||
|
"playground": {
|
||||||
|
"forest": 56
|
||||||
|
},
|
||||||
|
"surveillance:type": {
|
||||||
|
"camera": 112963,
|
||||||
|
"ALPR": 2522,
|
||||||
|
"ANPR": 3
|
||||||
|
},
|
||||||
|
"natural": {
|
||||||
|
"tree": 18245059
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
scripts/generateStats.ts
Normal file
77
scripts/generateStats.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import * as known_layers from "../assets/generated/known_layers.json"
|
||||||
|
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
|
||||||
|
import {TagUtils} from "../Logic/Tags/TagUtils";
|
||||||
|
import {Utils} from "../Utils";
|
||||||
|
import {writeFileSync} from "fs";
|
||||||
|
import ScriptUtils from "./ScriptUtils";
|
||||||
|
import Constants from "../Models/Constants";
|
||||||
|
|
||||||
|
/* Downloads stats on osmSource-tags and keys from tagInfo */
|
||||||
|
|
||||||
|
async function main(includeTags = true) {
|
||||||
|
ScriptUtils.fixUtils()
|
||||||
|
const layers: LayerConfigJson[] = (known_layers["default"] ?? known_layers).layers
|
||||||
|
|
||||||
|
const keysAndTags = new Map<string, Set<string>>()
|
||||||
|
|
||||||
|
for (const layer of layers) {
|
||||||
|
|
||||||
|
if (layer.source["geoJson"] !== undefined && !layer.source["isOsmCache"]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const sources = TagUtils.Tag(layer.source.osmTags)
|
||||||
|
const allKeys = sources.usedKeys()
|
||||||
|
for (const key of allKeys) {
|
||||||
|
if (!keysAndTags.has(key)) {
|
||||||
|
keysAndTags.set(key, new Set<string>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const allTags = includeTags ? sources.usedTags() : []
|
||||||
|
for (const tag of allTags) {
|
||||||
|
if (!keysAndTags.has(tag.key)) {
|
||||||
|
keysAndTags.set(tag.key, new Set<string>())
|
||||||
|
}
|
||||||
|
keysAndTags.get(tag.key).add(tag.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyTotal = new Map<string, number>()
|
||||||
|
const tagTotal = new Map<string, Map<string, number>>()
|
||||||
|
await Promise.all(Array.from(keysAndTags.keys()).map(async key => {
|
||||||
|
const values = keysAndTags.get(key)
|
||||||
|
const data = await Utils.downloadJson(`https://taginfo.openstreetmap.org/api/4/key/stats?key=${key}`)
|
||||||
|
const count = data.data.find(item => item.type === "all").count
|
||||||
|
keyTotal.set(key, count)
|
||||||
|
console.log(key, "-->", count)
|
||||||
|
|
||||||
|
|
||||||
|
if (values.size > 0) {
|
||||||
|
|
||||||
|
tagTotal.set(key, new Map<string, number>())
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(values).map(async value => {
|
||||||
|
const tagData = await Utils.downloadJson(`https://taginfo.openstreetmap.org/api/4/tag/stats?key=${key}&value=${value}`)
|
||||||
|
const count = tagData.data.find(item => item.type === "all").count
|
||||||
|
tagTotal.get(key).set(value, count)
|
||||||
|
console.log(key + "=" + value, "-->", count)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
writeFileSync("./assets/key_totals.json",
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
keys: Utils.MapToObj(keyTotal),
|
||||||
|
tags: Utils.MapToObj(tagTotal, v => Utils.MapToObj(v))
|
||||||
|
},
|
||||||
|
null, " "
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(() => console.log("All done"))
|
File diff suppressed because one or more lines are too long
141
test/Tag.spec.ts
141
test/Tag.spec.ts
|
@ -9,6 +9,9 @@ import {Tag} from "../Logic/Tags/Tag";
|
||||||
import {And} from "../Logic/Tags/And";
|
import {And} from "../Logic/Tags/And";
|
||||||
import {TagUtils} from "../Logic/Tags/TagUtils";
|
import {TagUtils} from "../Logic/Tags/TagUtils";
|
||||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
|
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
|
||||||
|
import {TagsFilter} from "../Logic/Tags/TagsFilter";
|
||||||
|
import {Or} from "../Logic/Tags/Or";
|
||||||
|
import {RegexTag} from "../Logic/Tags/RegexTag";
|
||||||
|
|
||||||
export default class TagSpec extends T {
|
export default class TagSpec extends T {
|
||||||
|
|
||||||
|
@ -21,6 +24,133 @@ export default class TagSpec extends T {
|
||||||
equal(tr.txt, "Test value abc");
|
equal(tr.txt, "Test value abc");
|
||||||
|
|
||||||
}],
|
}],
|
||||||
|
["Optimize tags", () => {
|
||||||
|
|
||||||
|
let t : TagsFilter= new And(
|
||||||
|
[
|
||||||
|
new And([
|
||||||
|
new Tag("x", "y")
|
||||||
|
]),
|
||||||
|
new Tag("a", "b")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
let opt =<TagsFilter> t.optimize()
|
||||||
|
console.log(TagUtils.toString(opt))
|
||||||
|
T.equals(`a=b&x=y`,TagUtils.toString(opt), "Optimization failed")
|
||||||
|
|
||||||
|
|
||||||
|
// foo&bar & (x=y | a=b) & (x=y | c=d) & foo=bar is equivalent too foo=bar & ((x=y) | (a=b & c=d))
|
||||||
|
t = new And([
|
||||||
|
new Tag("foo","bar"),
|
||||||
|
new Or([
|
||||||
|
new Tag("x", "y"),
|
||||||
|
new Tag("a", "b")
|
||||||
|
]),
|
||||||
|
new Or([
|
||||||
|
new Tag("x", "y"),
|
||||||
|
new Tag("c", "d")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
opt =<TagsFilter> t.optimize()
|
||||||
|
console.log(TagUtils.toString(opt))
|
||||||
|
T.equals(TagUtils.toString(opt), "foo=bar& (x=y| (a=b&c=d) )")
|
||||||
|
|
||||||
|
t = new Or([
|
||||||
|
new Tag("foo","bar"),
|
||||||
|
new And([
|
||||||
|
new Tag("foo", "bar"),
|
||||||
|
new Tag("x", "y"),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
opt =<TagsFilter> t.optimize()
|
||||||
|
console.log(TagUtils.toString(opt))
|
||||||
|
T.equals("foo=bar", TagUtils.toString(opt), "Optimizing away an unneeded factor failed")
|
||||||
|
|
||||||
|
|
||||||
|
t = new And([
|
||||||
|
new RegexTag("x","y"),
|
||||||
|
new Tag("a","b")
|
||||||
|
])
|
||||||
|
opt =<TagsFilter> t.optimize()
|
||||||
|
T.equals("a=b&x~^y$", TagUtils.toString(opt), "Regexes go to the end")
|
||||||
|
|
||||||
|
t = new And([
|
||||||
|
new Tag("bicycle","yes"),
|
||||||
|
new Tag("amenity","binoculars")
|
||||||
|
])
|
||||||
|
opt =<TagsFilter> t.optimize()
|
||||||
|
T.equals("amenity=binoculars&bicycle=yes", TagUtils.toString(opt), "Common keys go to the end")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const filter = TagUtils.Tag( {or: [
|
||||||
|
{
|
||||||
|
"and": [
|
||||||
|
{
|
||||||
|
"or": ["amenity=charging_station","disused:amenity=charging_station","planned:amenity=charging_station","construction:amenity=charging_station"]
|
||||||
|
},
|
||||||
|
"bicycle=yes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"and": [
|
||||||
|
{
|
||||||
|
"or": ["amenity=charging_station","disused:amenity=charging_station","planned:amenity=charging_station","construction:amenity=charging_station"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"amenity=toilets",
|
||||||
|
"amenity=bench",
|
||||||
|
"leisure=picnic_table",
|
||||||
|
{
|
||||||
|
"and": [
|
||||||
|
"tower:type=observation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"and": [
|
||||||
|
"amenity=bicycle_repair_station"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"and": [
|
||||||
|
{
|
||||||
|
"or": [
|
||||||
|
"amenity=bicycle_rental",
|
||||||
|
"bicycle_rental~*",
|
||||||
|
"service:bicycle:rental=yes",
|
||||||
|
"rental~.*bicycle.*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bicycle_rental!=docking_station"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"and": [
|
||||||
|
"leisure=playground",
|
||||||
|
"playground!=forest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]});
|
||||||
|
|
||||||
|
opt = <TagsFilter> filter.optimize()
|
||||||
|
console.log(TagUtils.toString(opt))
|
||||||
|
T.equals(("amenity=charging_station|" +
|
||||||
|
"amenity=toilets|" +
|
||||||
|
"amenity=bench|" +
|
||||||
|
"amenity=bicycle_repair_station" +
|
||||||
|
"|construction:amenity=charging_station|" +
|
||||||
|
"disused:amenity=charging_station|" +
|
||||||
|
"leisure=picnic_table|" +
|
||||||
|
"planned:amenity=charging_station|" +
|
||||||
|
"tower:type=observation| " +
|
||||||
|
"( (amenity=bicycle_rental|service:bicycle:rental=yes|bicycle_rental~^..*$|rental~^.*bicycle.*$) &bicycle_rental!~^docking_station$) |" +
|
||||||
|
" (leisure=playground&playground!~^forest$)").replace(/ /g, ""),
|
||||||
|
TagUtils.toString(opt).replace(/ /g, ""), "Advanced case failed")
|
||||||
|
}],
|
||||||
|
|
||||||
|
|
||||||
["Parse tag config", (() => {
|
["Parse tag config", (() => {
|
||||||
const tag = TagUtils.Tag("key=value") as Tag;
|
const tag = TagUtils.Tag("key=value") as Tag;
|
||||||
equal(tag.key, "key");
|
equal(tag.key, "key");
|
||||||
|
@ -217,6 +347,17 @@ export default class TagSpec extends T {
|
||||||
equal(3, overpassOrInor.length)
|
equal(3, overpassOrInor.length)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"Test regex to overpass",() => {
|
||||||
|
/*(Specifiation to parse, expected value for new RegexTag(spec).asOverpass()[0]) */
|
||||||
|
[["a~*", `"a"`],
|
||||||
|
["a~[xyz]",`"a"~"^[xyz]$"`]].forEach(([spec, expected]) =>{
|
||||||
|
T.equals(`[${expected}]`, TagUtils.Tag(
|
||||||
|
spec
|
||||||
|
).asOverpass()[0], "RegexRendering failed")
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"Merge touching opening hours",
|
"Merge touching opening hours",
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -20,11 +20,11 @@ export default class T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static equals(a, b, msg?) {
|
static equals(expected, got, msg?) {
|
||||||
if (a !== b) {
|
if (expected !== got) {
|
||||||
throw "Not the same: " + (msg ?? "") + "\n" +
|
throw "Not the same: " + (msg ?? "") + "\n" +
|
||||||
"Expcected: " + a + "\n" +
|
"Expected: " + expected + "\n" +
|
||||||
"Got : " + b
|
"Got : " + got
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ export default class T {
|
||||||
throw `ListIdentical failed: expected a list of length ${expected.length} but got a list of length ${actual.length}`
|
throw `ListIdentical failed: expected a list of length ${expected.length} but got a list of length ${actual.length}`
|
||||||
}
|
}
|
||||||
for (let i = 0; i < expected.length; i++) {
|
for (let i = 0; i < expected.length; i++) {
|
||||||
if (expected[i] !== undefined && expected[i]["length"] !== undefined) {
|
if (Array.isArray(expected[i])) {
|
||||||
T.listIdentical(<any>expected[i], <any>actual[i])
|
T.listIdentical(<any>expected[i], <any>actual[i])
|
||||||
} else if (expected[i] !== actual[i]) {
|
} else if (expected[i] !== actual[i]) {
|
||||||
throw `ListIdentical failed at index ${i}: expected ${expected[i]} but got ${actual[i]}`
|
throw `ListIdentical failed at index ${i}: expected ${expected[i]} but got ${actual[i]}`
|
||||||
|
|
Loading…
Reference in a new issue