diff --git a/Docs/CalculatedTags.md b/Docs/CalculatedTags.md index 2d16a79876..9d069dbb96 100644 --- a/Docs/CalculatedTags.md +++ b/Docs/CalculatedTags.md @@ -1,4 +1,5 @@ + Metatags ========== diff --git a/Docs/Tools/graphs/Changesets per host in 2020.png b/Docs/Tools/graphs/Changesets per host in 2020.png new file mode 100644 index 0000000000..b01e8aac38 Binary files /dev/null and b/Docs/Tools/graphs/Changesets per host in 2020.png differ diff --git a/Docs/Tools/graphs/Changesets per host in 2021.png b/Docs/Tools/graphs/Changesets per host in 2021.png new file mode 100644 index 0000000000..e1cdb34910 Binary files /dev/null and b/Docs/Tools/graphs/Changesets per host in 2021.png differ diff --git a/Docs/Tools/graphs/Changesets per host.png b/Docs/Tools/graphs/Changesets per host.png new file mode 100644 index 0000000000..dc0a98baf4 Binary files /dev/null and b/Docs/Tools/graphs/Changesets per host.png differ diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md index 6d96e02de0..5fa4715633 100644 --- a/Docs/URL_Parameters.md +++ b/Docs/URL_Parameters.md @@ -150,6 +150,17 @@ The language to display mapcomplete in. Will be ignored in case a logged-in-user If true, shows some extra debugging help such as all the available tags on every object The default value is _false_ +overpassUrl +------------- + +Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter The default value is _https://overpass.kumi.de/api/interpreter_ + + +overpassTimeout +----------------- + +Set a different timeout (in seconds) for queries in overpass The default value is _60_ + fake-user ----------- diff --git a/InitUiElements.ts b/InitUiElements.ts index 9c378f76ca..e74b7fa842 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -393,7 +393,9 @@ export class InitUiElements { const updater = new LoadFromOverpass( state.locationControl, state.layoutToUse, - state.leafletMap + state.leafletMap, + state.overpassUrl, + state.overpassTimeout ); State.state.layerUpdater = updater; diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index 6f03e3f28a..8f8957dc4a 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -35,6 +35,8 @@ export default class OverpassFeatureSource implements FeatureSource { private readonly _location: UIEventSource; private readonly _layoutToUse: UIEventSource; private readonly _leafletMap: UIEventSource; + private readonly _interpreterUrl: UIEventSource; + private readonly _timeout: UIEventSource; /** * The most important layer should go first, as that one gets first pick for the questions @@ -42,10 +44,14 @@ export default class OverpassFeatureSource implements FeatureSource { constructor( location: UIEventSource, layoutToUse: UIEventSource, - leafletMap: UIEventSource) { + leafletMap: UIEventSource, + interpreterUrl: UIEventSource, + timeout: UIEventSource,) { this._location = location; this._layoutToUse = layoutToUse; this._leafletMap = leafletMap; + this._interpreterUrl = interpreterUrl; + this._timeout = timeout; const self = this; this.sufficientlyZoomed = location.map(location => { @@ -123,7 +129,7 @@ export default class OverpassFeatureSource implements FeatureSource { if (filters.length + extraScripts.length === 0) { return undefined; } - return new Overpass(new Or(filters), extraScripts); + return new Overpass(new Or(filters), extraScripts, this._interpreterUrl, this._timeout); } private update(): void { diff --git a/Logic/Osm/Overpass.ts b/Logic/Osm/Overpass.ts index 59a4869f4f..d0aa14343c 100644 --- a/Logic/Osm/Overpass.ts +++ b/Logic/Osm/Overpass.ts @@ -3,6 +3,7 @@ import Bounds from "../../Models/Bounds"; import {TagsFilter} from "../Tags/TagsFilter"; import ExtractRelations from "./ExtractRelations"; import {Utils} from "../../Utils"; +import {UIEventSource} from "../UIEventSource"; /** * Interfaces overpass to get all the latest data @@ -10,10 +11,17 @@ import {Utils} from "../../Utils"; export class Overpass { public static testUrl: string = null private _filter: TagsFilter + private readonly _interpreterUrl: UIEventSource; + private readonly _timeout: UIEventSource; private readonly _extraScripts: string[]; private _includeMeta: boolean; - - constructor(filter: TagsFilter, extraScripts: string[], includeMeta = true) { + + constructor(filter: TagsFilter, extraScripts: string[], + interpreterUrl: UIEventSource, + timeout: UIEventSource, + includeMeta = true) { + this._timeout = timeout; + this._interpreterUrl = interpreterUrl; this._filter = filter this._extraScripts = extraScripts; this._includeMeta = includeMeta; @@ -54,7 +62,7 @@ export class Overpass { filter += '(' + extraScript + ');'; } const query = - `[out:json][timeout:25]${bbox};(${filter});out body;${this._includeMeta ? 'out meta;' : ''}>;out skel qt;` - return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) + `[out:json][timeout:${this._timeout.data}]${bbox};(${filter});out body;${this._includeMeta ? 'out meta;' : ''}>;out skel qt;` + return `${this._interpreterUrl.data}?data=${encodeURIComponent(query)}` } } diff --git a/Models/Constants.ts b/Models/Constants.ts index 31ecabfb7d..0d0b1af1db 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.9.1"; + public static vNumber = "0.9.2"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index fd332e8ca7..9ba97eaf8e 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -339,4 +339,12 @@ export interface LayoutConfigJson { enableDownload?: boolean; enablePdfDownload?: boolean; + /** + * Set a different overpass URL. Default: https://overpass-api.de/api/interpreter + */ + overpassUrl?: string; + /** + * Set a different timeout for overpass queries - in seconds. Default: 30s + */ + overpassTimeout?: number } \ No newline at end of file diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 9ce8105b6a..65b9cdade1 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -53,6 +53,9 @@ export default class LayoutConfig { public readonly cacheTimeout?: number; public readonly units: Unit[] = [] private readonly _official: boolean; + + public readonly overpassUrl: string; + public readonly overpassTimeout: number; constructor(json: LayoutConfigJson, official = true, context?: string) { this._official = official; @@ -160,7 +163,8 @@ export default class LayoutConfig { this.enablePdfDownload = json.enablePdfDownload ?? false; this.customCss = json.customCss; this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60) - + this.overpassUrl = json.overpassUrl ?? "https://overpass-api.de/api/interpreter" + this.overpassTimeout = json.overpassTimeout ?? 30 } diff --git a/State.ts b/State.ts index ddc44e72b9..6ef07ff749 100644 --- a/State.ts +++ b/State.ts @@ -94,6 +94,8 @@ export default class State { public readonly featureSwitchEnableExport: UIEventSource; public readonly featureSwitchFakeUser: UIEventSource; public readonly featureSwitchExportAsPdf: UIEventSource; + public readonly overpassUrl: UIEventSource; + public readonly overpassTimeout: UIEventSource; public featurePipeline: FeaturePipeline; @@ -254,7 +256,7 @@ export default class State { (layoutToUse) => layoutToUse?.enableBackgroundLayerSelection ?? true, "Disables/Enables the background layer control" ); - + this.featureSwitchFilter = featSw( "fs-filter", (layoutToUse) => layoutToUse?.enableLayers ?? true, @@ -307,6 +309,7 @@ export default class State { "Enable the PDF download button" ); + this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( "test", "false", @@ -338,6 +341,15 @@ export default class State { "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" ); + this.overpassUrl = QueryParameters.GetQueryParameter("overpassUrl", + layoutToUse?.overpassUrl, + "Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter" + ) + + this.overpassTimeout = QueryParameters.GetQueryParameter("overpassTimeout", + "" + layoutToUse?.overpassTimeout, + "Set a different timeout (in seconds) for queries in overpass") + .map(str => Number(str), [], n => "" + n) this.featureSwitchUserbadge.addCallbackAndRun(userbadge => { if (!userbadge) { diff --git a/UI/Base/Table.ts b/UI/Base/Table.ts index a901e17f75..11e2fd8715 100644 --- a/UI/Base/Table.ts +++ b/UI/Base/Table.ts @@ -50,21 +50,26 @@ export default class Table extends BaseUIElement { let row = this._contents[i]; const tr = document.createElement("tr") for (let j = 0; j < row.length; j++) { - let elem = row[j]; - const htmlElem = elem?.ConstructElement() - if (htmlElem === undefined) { - continue; - } + try { - let style = undefined; - if (this._contentStyle !== undefined && this._contentStyle[i] !== undefined && this._contentStyle[j] !== undefined) { - style = this._contentStyle[i][j] - } + let elem = row[j]; + const htmlElem = elem?.ConstructElement() + if (htmlElem === undefined) { + continue; + } - const td = document.createElement("td") - td.style.cssText = style; - td.appendChild(htmlElem) - tr.appendChild(td) + let style = undefined; + if (this._contentStyle !== undefined && this._contentStyle[i] !== undefined && this._contentStyle[j] !== undefined) { + style = this._contentStyle[i][j] + } + + const td = document.createElement("td") + td.style.cssText = style; + td.appendChild(htmlElem) + tr.appendChild(td) + } catch (e) { + console.error("Could not render an element in a table due to", e, row[j]) + } } table.appendChild(tr) } diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index d544149883..41be7f0300 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -14,6 +14,9 @@ export default class Translations { if (typeof (s) === "string") { return new FixedUiElement(s); } + if(typeof s === "number"){ + return new FixedUiElement(""+s) + } return s; } diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 338c46a96d..94a0d63adf 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -1,4 +1,12 @@ [ + { + "authors": [ + "Pieter Vander Vennet" + ], + "path": "add_pin.svg", + "license": "CC0", + "sources": [] + }, { "authors": [ "Pieter Vander Vennet" @@ -1346,5 +1354,11 @@ "path": "location_unlocked.svg", "license": "CC0", "sources": [] + }, + { + "authors": [], + "path": "loading.svg", + "license": "CC0; trivial", + "sources": [] } ] \ No newline at end of file diff --git a/assets/themes/cycle_highways/cycle_highways.json b/assets/themes/cycle_highways/cycle_highways.json new file mode 100644 index 0000000000..0e8ac1a66f --- /dev/null +++ b/assets/themes/cycle_highways/cycle_highways.json @@ -0,0 +1,176 @@ +{ + "id": "cycle_highways", + "title": { + "en": "Cycle highways" + }, + "hideFromOverview": true, + "maintainer": "L'imaginaire", + "icon": "./assets/themes/cycle_highways/fietssnelwegen-logo.svg", + "overpassUrl": "https://overpass.kumi.de/api/interpreter", + "overpassTimeout": 60, + "description": { + "en": "This map shows cycle highways" + }, + "language": [ + "en" + ], + "version": "2021-05-22", + "startLat": 51.1599, + "startLon": 3.3475, + "startZoom": 8, + "clustering": { + "maxZoom": 1 + }, + "enableDownload": true, + "enablePdfDownload": true, + "layers": [ + { + "id": "cycle_highways", + "tagRenderings": [ + { + "render": "The name is {name}", + "question": "What is the name of this cycle highway?", + "freeform": { + "key": "name" + } + }, + { + "render": "Referentienummer is {ref}", + "question": "What is the reference number of this cycle highway?", + "freeform": { + "key": "ref" + } + }, + { + "render": "The current state of this link is {state}", + "question": "What is the state of this link?", + "freeform": { + "key": "state" + }, + "mappings": [ + { + "if": "state=proposed", + "then": "This is a proposed route" + }, + { + "if": "state=temporary", + "then": "This is a temporary deviation" + }, + { + "if": "state=", + "then": "This link is operational and signposted" + } + ] + }, + { + "render": "This part is {_length:km}km long" + }, + "website", + { + "render":"{all_tags()}" + } + ], + "name": { + "en": "cycle highways" + }, + "source": { + "osmTags": "cycle_network=BE-VLG:cycle_highway" + }, + "minzoom": 8, + "title": { + "render": { + "en": "cycle highway" + } + }, + "width": { + "render": "4" + }, + "color": { + "render": "#ff7392", + "mappings": [ + { + "if": "state=", + "then": "#00acfc" + }, + { + "if": "state=temporary", + "then": "#00acfc" + } + ] + }, + "dashArray": { + "render": "", + "mappings": [ + { + "if": "state=temporary", + "then": "12 10" + } + ] + }, + "filter": [ + { + "options": [ + { + "question": "Name contains 'alt'", + "osmTags": "name~.*[aA]lt.*" + } + ] + }, + { + "options": [ + { + "question": "Name contains 'wenslijn'", + "osmTags": "name~.*[wW]enslijn.*" + } + ] + }, + { + "options": [ + { + "question": "Name contains 'omleiding'", + "osmTags": "name~.*[oO]mleiding.*" + } + ] + }, + { + "options": [ + { + "question": "Reference contains 'alt'", + "osmTags": "ref~.*[aA]lt.*" + } + ] + }, + { + "options": [ + { + "question": "No filter" + }, + { + "question": "state=proposed", + "osmTags": "state=proposed" + }, + { + "question": "state=temporary", + "osmTags": "state=temporary" + }, + { + "question": "state unset", + "osmTags": "state=" + }, + { + "question": "Other state", + "osmTags": { + "and": [ + "state!=", + "state!=proposed", + "state!=temporary" + ] + } + } + ] + } + ] + } + ], + "defaultBackgroundId": "CartoDB.Positron" +} \ No newline at end of file diff --git a/assets/themes/cycle_highways/fietssnelwegen-logo.svg b/assets/themes/cycle_highways/fietssnelwegen-logo.svg new file mode 100644 index 0000000000..c4d6c96658 --- /dev/null +++ b/assets/themes/cycle_highways/fietssnelwegen-logo.svg @@ -0,0 +1,64 @@ + + + + + + + + + diff --git a/assets/themes/cycle_highways/license_info.json b/assets/themes/cycle_highways/license_info.json new file mode 100644 index 0000000000..7c8090610f --- /dev/null +++ b/assets/themes/cycle_highways/license_info.json @@ -0,0 +1,12 @@ +[ + { + "authors": [ + "De Vlaamse Provincies" + ], + "path": "fietssnelwegen-logo.svg", + "license": "Logo by the government", + "sources": [ + "https://fietssnelwegen.be/" + ] + } +] \ No newline at end of file diff --git a/assets/themes/cycle_infra/cycle_infra.json b/assets/themes/cycle_infra/cycle_infra.json index 8f4887fb89..b91804ea97 100644 --- a/assets/themes/cycle_infra/cycle_infra.json +++ b/assets/themes/cycle_infra/cycle_infra.json @@ -17,7 +17,7 @@ "nl" ], "maintainer": "MapComplete", - "hideFromOverview": true, + "hideFromOverview": false, "defaultBackgroundId": "CartoDB.Voyager", "icon": "./assets/themes/cycle_infra/cycle-infra.svg", "version": "0", diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts index 128dfb2e19..eee044446c 100644 --- a/scripts/generateCache.ts +++ b/scripts/generateCache.ts @@ -52,7 +52,8 @@ function createOverpassObject(theme: LayoutConfig) { if (filters.length + extraScripts.length === 0) { throw "Nothing to download! The theme doesn't declare anything to download" } - return new Overpass(new Or(filters), extraScripts); + return new Overpass(new Or(filters), extraScripts, new UIEventSource("https://overpass-api.de/api/interpreter"), + new UIEventSource(60)); } function rawJsonName(targetDir: string, x: number, y: number, z: number): string { diff --git a/test.ts b/test.ts index d25afff734..81a1e1801f 100644 --- a/test.ts +++ b/test.ts @@ -1,29 +1,31 @@ -import {UIEventSource} from "./Logic/UIEventSource"; -import AllKnownLayers from "./Customizations/AllKnownLayers"; import {FixedUiElement} from "./UI/Base/FixedUiElement"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; -import {TagUtils} from "./Logic/Tags/TagUtils"; +import Hash from "./Logic/Web/Hash"; +import {InitUiElements} from "./InitUiElements"; +import {Utils} from "./Utils"; +import {UIEventSource} from "./Logic/UIEventSource"; +import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; +import LZString from "lz-string"; +import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; import Combine from "./UI/Base/Combine"; -import Svg from "./Svg"; -import Translations from "./UI/i18n/Translations"; -import LayerConfig from "./Models/ThemeConfig/LayerConfig"; -import AddNewMarker from "./UI/BigComponents/AddNewMarker"; -function genMarker(filteredLayers: UIEventSource<{ appliedFilters: undefined; isDisplayed: UIEventSource; layerDef: LayerConfig }[]>) { -return new AddNewMarker(filteredLayers) - -} - -let filteredLayers = new UIEventSource([ - { - layerDef: AllKnownLayers.sharedLayers.get("toilet"), - isDisplayed: new UIEventSource(true), - appliedFilters: undefined +new VariableUiElement(Hash.hash.map( + hash => { + let json: {}; + try { + json = atob(hash); + } catch (e) { + // We try to decode with lz-string + json = + Utils.UnMinify(LZString.decompressFromBase64(hash)) + } + return new Combine([ + new FixedUiElement("Base64 decoded: " + atob(hash)), + new FixedUiElement("LZ: " + LZString.decompressFromBase64(hash)), + new FixedUiElement("Base64 + unminify: " + Utils.UnMinify(atob(hash))), + new FixedUiElement("LZ + unminify: " + Utils.UnMinify(LZString.decompressFromBase64(hash))) + ]).SetClass("flex flex-col m-1") } -]) -genMarker(filteredLayers).SetStyle("width: 50px; height: 70px") - .SetClass("block border-black border") - .AttachTo("maindiv") - -new FixedUiElement("").AttachTo("extradiv") \ No newline at end of file +)) + .AttachTo("maindiv") \ No newline at end of file