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) {
|
||||
this._timeout = timeout;
|
||||
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._includeMeta = includeMeta;
|
||||
this._relationTracker = relationTracker
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import {TagsFilter} from "./TagsFilter";
|
||||
import {Or} from "./Or";
|
||||
import {TagUtils} from "./TagUtils";
|
||||
|
||||
export class And extends TagsFilter {
|
||||
public and: TagsFilter[]
|
||||
|
@ -109,6 +111,10 @@ export class And extends TagsFilter {
|
|||
usedKeys(): string[] {
|
||||
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 }[] {
|
||||
const result = []
|
||||
|
@ -123,4 +129,89 @@ export class And extends TagsFilter {
|
|||
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[] {
|
||||
return [this._key];
|
||||
}
|
||||
|
||||
usedTags(): { key: string; value: string }[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
AsJson() {
|
||||
return this.asHumanString(false, false, {})
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import {TagsFilter} from "./TagsFilter";
|
||||
import {TagUtils} from "./TagUtils";
|
||||
import {And} from "./And";
|
||||
|
||||
|
||||
export class Or extends TagsFilter {
|
||||
|
@ -58,6 +60,10 @@ export class Or extends TagsFilter {
|
|||
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 }[] {
|
||||
const result = []
|
||||
for (const tagsFilter of this.or) {
|
||||
|
@ -71,6 +77,83 @@ export class Or extends TagsFilter {
|
|||
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[] {
|
||||
if (typeof this.key === "string") {
|
||||
return [`["${this.key}"${this.invert ? "!" : ""}~"${RegexTag.source(this.value)}"]`];
|
||||
const inv =this.invert ? "!" : ""
|
||||
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 {
|
||||
|
@ -99,6 +113,10 @@ export class RegexTag extends TagsFilter {
|
|||
}
|
||||
throw "Key cannot be determined as it is a regex"
|
||||
}
|
||||
|
||||
usedTags(): { key: string; value: string }[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
asChange(properties: any): { k: string; v: string }[] {
|
||||
if (this.invert) {
|
||||
|
@ -120,4 +138,8 @@ export class RegexTag extends TagsFilter {
|
|||
AsJson() {
|
||||
return this.asHumanString()
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -59,6 +59,10 @@ export default class SubstitutingTag implements TagsFilter {
|
|||
return [this._key];
|
||||
}
|
||||
|
||||
usedTags(): { key: string; value: string }[] {
|
||||
return []
|
||||
}
|
||||
|
||||
asChange(properties: any): { k: string; v: string }[] {
|
||||
if (this._invert) {
|
||||
throw "An inverted substituting tag can not be used to create a change"
|
||||
|
@ -73,4 +77,8 @@ export default class SubstitutingTag implements TagsFilter {
|
|||
AsJson() {
|
||||
return this._key + (this._invert ? '!' : '') + "=" + this._value
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -80,6 +80,13 @@ export class Tag extends TagsFilter {
|
|||
return [this.key];
|
||||
}
|
||||
|
||||
usedTags(): { key: string; value: string }[] {
|
||||
if(this.value == ""){
|
||||
return []
|
||||
}
|
||||
return [this]
|
||||
}
|
||||
|
||||
asChange(properties: any): { k: string; v: string }[] {
|
||||
return [{k: this.key, v: this.value}];
|
||||
}
|
||||
|
@ -87,4 +94,8 @@ export class Tag extends TagsFilter {
|
|||
AsJson() {
|
||||
return this.asHumanString(false, false)
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -8,8 +8,10 @@ import SubstitutingTag from "./SubstitutingTag";
|
|||
import {Or} from "./Or";
|
||||
import {AndOrTagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import {isRegExp} from "util";
|
||||
import * as key_counts from "../../assets/key_totals.json"
|
||||
|
||||
export class TagUtils {
|
||||
private static keyCounts : {keys: any, tags: any} = key_counts["default"] ?? key_counts
|
||||
private static comparators
|
||||
: [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 {
|
||||
|
||||
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`
|
||||
|
||||
}
|
||||
|
||||
if(json.and !== undefined && json.or !== undefined){
|
||||
throw `Error while parsing a TagConfig: got an object where both 'and' and 'or' are defined`}
|
||||
|
||||
|
||||
if (json.and !== undefined && json.or !== undefined) {
|
||||
throw `Error while parsing a TagConfig: got an object where both 'and' and 'or' are defined`
|
||||
}
|
||||
|
||||
if (json.and !== undefined) {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
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[];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Throws an error if not applicable.
|
||||
|
@ -21,4 +27,11 @@ export abstract class TagsFilter {
|
|||
abstract asChange(properties: any): { k: string, v: string }[]
|
||||
|
||||
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 {
|
||||
|
||||
public static vNumber = "0.17.0-alpha-1";
|
||||
public static vNumber = "0.17.0-alpha-2";
|
||||
|
||||
public static ImgurApiKey = '7070e7167f0a25a'
|
||||
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 {TagUtils} from "../Logic/Tags/TagUtils";
|
||||
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 {
|
||||
|
||||
|
@ -21,6 +24,133 @@ export default class TagSpec extends T {
|
|||
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", (() => {
|
||||
const tag = TagUtils.Tag("key=value") as Tag;
|
||||
equal(tag.key, "key");
|
||||
|
@ -217,6 +347,17 @@ export default class TagSpec extends T {
|
|||
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",
|
||||
() => {
|
||||
|
|
|
@ -20,11 +20,11 @@ export default class T {
|
|||
}
|
||||
}
|
||||
|
||||
static equals(a, b, msg?) {
|
||||
if (a !== b) {
|
||||
static equals(expected, got, msg?) {
|
||||
if (expected !== got) {
|
||||
throw "Not the same: " + (msg ?? "") + "\n" +
|
||||
"Expcected: " + a + "\n" +
|
||||
"Got : " + b
|
||||
"Expected: " + expected + "\n" +
|
||||
"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}`
|
||||
}
|
||||
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])
|
||||
} else if (expected[i] !== actual[i]) {
|
||||
throw `ListIdentical failed at index ${i}: expected ${expected[i]} but got ${actual[i]}`
|
||||
|
|
Loading…
Reference in a new issue