From 80d4bf0ccfbdf060f014a05c402f99bec7ff3624 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 13 May 2024 16:12:09 +0200 Subject: [PATCH 1/3] Improve typing --- src/Utils.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Utils.ts b/src/Utils.ts index 503c7ed7d..141bdaa0e 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1011,11 +1011,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be }) } - public static async downloadJsonCached( + public static async downloadJsonCached( url: string, maxCacheTimeMs: number, headers?: Record - ): Promise { + ): Promise { const result = await Utils.downloadJsonCachedAdvanced(url, maxCacheTimeMs, headers) if (result["content"]) { return result["content"] @@ -1023,11 +1023,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be throw result["error"] } - public static async downloadJsonCachedAdvanced( + public static async downloadJsonCachedAdvanced( url: string, maxCacheTimeMs: number, headers?: Record - ): Promise<{ content: object | [] } | { error: string; url: string; statuscode?: number }> { + ): Promise<{ content: T } | { error: string; url: string; statuscode?: number }> { const cached = Utils._download_cache.get(url) if (cached !== undefined) { if (new Date().getTime() - cached.timestamp <= maxCacheTimeMs) { @@ -1042,10 +1042,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() }) return await promise } - public static async downloadJson( + public static async downloadJson( url: string, headers?: Record - ): Promise + ): Promise public static async downloadJson( url: string, headers?: Record From 6038f0d6ad7dcacd61e7a8b6cc904a9c65dc4089 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 13 May 2024 17:14:16 +0200 Subject: [PATCH 2/3] Include all countries in _ country --- src/Logic/SimpleMetaTagger.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Logic/SimpleMetaTagger.ts b/src/Logic/SimpleMetaTagger.ts index 15d358668..41399a239 100644 --- a/src/Logic/SimpleMetaTagger.ts +++ b/src/Logic/SimpleMetaTagger.ts @@ -119,7 +119,7 @@ class CountryTagger extends SimpleMetaTagger { constructor() { super({ keys: ["_country"], - doc: "The country code of the property (with latlon2country)", + doc: "The country codes of the of the country/countries that the feature is located in (with latlon2country). Might contain _multiple_ countries, separated by a `;`", includesDates: false, }) } @@ -138,7 +138,7 @@ class CountryTagger extends SimpleMetaTagger { return } const oldCountry = feature.properties["_country"] - const newCountry = countries[0].trim().toLowerCase() + const newCountry = countries.join(";").trim().toLowerCase() if (oldCountry !== newCountry) { if (typeof window === undefined) { tagsSource.data["_country"] = newCountry @@ -150,7 +150,7 @@ class CountryTagger extends SimpleMetaTagger { /** * What is this weird construction? * - * For a theme with a hundreds of items (e.g. shops) + * For a theme with hundreds of items (e.g. shops) * the country for all those shops will probably arrive at the same time. * * This means that all those stores will be pinged around the same time. From f5d1b47bda5c54dd7c25d9624ff14068ff7bccb5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 13 May 2024 17:14:59 +0200 Subject: [PATCH 3/3] Fetch taginfo: add code to get all countries, imrove docs --- src/Logic/Web/TagInfo.ts | 78 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/src/Logic/Web/TagInfo.ts b/src/Logic/Web/TagInfo.ts index dfc6fb957..213a81ac7 100644 --- a/src/Logic/Web/TagInfo.ts +++ b/src/Logic/Web/TagInfo.ts @@ -1,35 +1,48 @@ import { Utils } from "../../Utils" +import type { FeatureCollection } from "geojson" export interface TagInfoStats { /** * The total number of entries in the data array, **not** the total number of objects known in OSM! * * Use `data.find(item => item.type==="all").count` for this + * @deprecated: you probably want to use data.find(item => item.type==="all").count */ total: number data: { type: "all" | "nodes" | "ways" | "relations" + // Absolute number count: number + // Relative, percentage count_fraction: number }[] } +interface GeofabrikCountryProperties { + id: string, + parent: string | "europe" | "asia", + urls: string[], + name: string, + "iso3166-1:alpha2": string[] +} + export default class TagInfo { public static readonly global = new TagInfo() private readonly _backend: string + private static _geofabrikCountries = undefined constructor(backend = "https://taginfo.openstreetmap.org/") { this._backend = backend } - public getStats(key: string, value?: string): Promise { + public async getStats(key: string, value?: string): Promise { let url: string if (value) { url = `${this._backend}api/4/tag/stats?key=${key}&value=${value}` } else { url = `${this._backend}api/4/key/stats?key=${key}` } - return Utils.downloadJsonCached(url, 1000 * 60 * 60) + return await Utils.downloadJsonCached(url, 1000 * 60 * 60) } /** @@ -44,4 +57,65 @@ export default class TagInfo { return `${this._backend}/keys/${k}#overview` } } + + /** + * Does not work in the browser due to some resources not being CORS-accessible + */ + public static async geofabrikCountries(): Promise { + if (TagInfo._geofabrikCountries) { + return TagInfo._geofabrikCountries + } + const countriesFC: FeatureCollection = await Utils.downloadJsonCached("https://download.geofabrik.de/index-v1-nogeom.json", 24 * 1000 * 60 * 60) + TagInfo._geofabrikCountries = countriesFC.features.map(f => f.properties) + return TagInfo._geofabrikCountries + } + + /** + * Creates a TagInfo-api-object for geofabrik for the given country. + * Returns undefined if geofabrik does not have such a country + * + * Does not work in the browser due to some resources not being CORS-accessible + * @param countryCode: an iso3166-1:alpha2 code + */ + public static async getInstanceFor(countryCode: string) { + const countries = await this.geofabrikCountries() + countryCode = countryCode.toUpperCase() + const country = countries.find(c => c["iso3166-1:alpha2"]?.indexOf(countryCode) >= 0) + if (!country || !country?.parent || !country?.id) { + return undefined + } + const url = `https://taginfo.geofabrik.de/${country.parent}:${country.id}/` + return new TagInfo(url) + } + + private static async getDistributionsFor(countryCode: string, key: string, value?: string): Promise{ + if (!countryCode) { + return undefined + } + const ti = await TagInfo.getInstanceFor(countryCode) + if (!ti) { + return undefined + } + try { + return await ti.getStats(key, value) + } catch (e) { + console.warn("Could not fetch info for", countryCode,key,value, "due to", e) + return undefined + } + } + + public static async getGlobalDistributionsFor(key: string, value?: string): Promise> { + const countries = await this.geofabrikCountries() + const perCountry: Record = {} + const results = await Promise.all(countries.map(country => TagInfo.getDistributionsFor(country?.["iso3166-1:alpha2"]?.[0], key, value))) + for (let i = 0; i < countries.length; i++){ + const country = countries[i] + const countryCode = country["iso3166-1:alpha2"]?.[0] + if(results[i]){ + perCountry[countryCode] = results[i] + } + } + return perCountry + } + }