diff --git a/Docs/Misc/Climbing.png b/Docs/Misc/Climbing.png new file mode 100644 index 0000000000..e4b9c9f688 Binary files /dev/null and b/Docs/Misc/Climbing.png differ diff --git a/Docs/Misc/Kakampink.png b/Docs/Misc/Kakampink.png new file mode 100644 index 0000000000..fa97551999 Binary files /dev/null and b/Docs/Misc/Kakampink.png differ diff --git a/Docs/Misc/StatisticsOnwheels.png b/Docs/Misc/StatisticsOnwheels.png new file mode 100644 index 0000000000..106e698ba8 Binary files /dev/null and b/Docs/Misc/StatisticsOnwheels.png differ diff --git a/Docs/Misc/TreesWikidataExample b/Docs/Misc/TreesWikidataExample.png similarity index 100% rename from Docs/Misc/TreesWikidataExample rename to Docs/Misc/TreesWikidataExample.png diff --git a/Docs/Misc/shop_selection.gif b/Docs/Misc/shop_selection.gif new file mode 100644 index 0000000000..a645bdbdc9 Binary files /dev/null and b/Docs/Misc/shop_selection.gif differ diff --git a/Docs/Presentations/LinkedData4Wikidata.odp b/Docs/Presentations/LinkedData4Wikidata.odp index f51a505ea6..6289a58495 100644 Binary files a/Docs/Presentations/LinkedData4Wikidata.odp and b/Docs/Presentations/LinkedData4Wikidata.odp differ diff --git a/Docs/Presentations/MapComplete_Theme_Building_Workshop_SOTM2022.odp b/Docs/Presentations/MapComplete_Theme_Building_Workshop_SOTM2022.odp new file mode 100644 index 0000000000..5b6f2e7c9b Binary files /dev/null and b/Docs/Presentations/MapComplete_Theme_Building_Workshop_SOTM2022.odp differ diff --git a/Docs/Presentations/SOTM2022-workshop.abstract.txt b/Docs/Presentations/SOTM2022-workshop.abstract.txt new file mode 100644 index 0000000000..fe30bbca0f --- /dev/null +++ b/Docs/Presentations/SOTM2022-workshop.abstract.txt @@ -0,0 +1,53 @@ +# What do I want to tell at SOTM '22? + +4 main topics: + +The MapComplete Editor: + +- What is MapComplete? The vision + + Simple to use viewer + + Simple to contribute + + Packed with features under the hood (where applicable) + + Pareto frontier + +- What is a theme? + + One or more layers + introduction text + some meta (such as icon) + + Vision on what the target group is (who will use it? For what purpose?) + + Written in a .json-file + +- What is a .json-file + + A flat text-file (create a .txt) + +- What is a layer? + + One logical type of feature + - Specifiy the tags that are needed to match + - Uses OSM as default datasource, but an online (sliced) geojson is an option too, e.g. to show/import + + Specifies the elements to show in the popup + + Specifies how it is rendered + + Specifies if new elements can be added and with which tags + -> Don't mix, e.g. don't mix post-boxes with post offices + -> Don't use an adverb, e.g. "all shops ~~which accept cash~~". What if the shop is already there but the payment methods are not known yet? (use a filter instead) + +- What is a TagRendering? + + Converts the objects attributes into a text on the screen + + If a question is added, it'll ask the user the right answer + -> Do use full sentences + -> Start with the step 'attributes to text', then add the question + -> Don't write "yes, this POI has ..." or even worse "yes"/"no" + +- Deploying + + copy paste into a base64-decoder + + Go to "mapcomplete.osm.be/index?userlayout=true#" + + Oh Noes! An error msg! + +- sharing + + Share often and early. I'd rather have a half-finished theme via chat/wiki that can be improved upon then no theme at all + + When done: make a pull request via github (you'll get translations, documentation, integration with taginfo and a chatbot for free) + +- A dash of Magic + + Calculated tags + + Twin layers (fritures) + + Imports + +- Future work: + + Improve this flow \ No newline at end of file diff --git a/Docs/Presentations/WikidataAndMapComplete.mp4 b/Docs/Presentations/WikidataAndMapComplete.mp4 new file mode 100644 index 0000000000..d781f521a6 Binary files /dev/null and b/Docs/Presentations/WikidataAndMapComplete.mp4 differ diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index ab665e6c37..fd9fe1914d 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -235,7 +235,7 @@ export default class SimpleMetaTaggers { private static canonicalize = new SimpleMetaTagger( { - doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`)", + doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`; `1` will be rewritten to `1m` as well)", keys: ["Theme-defined keys"], }, @@ -261,13 +261,14 @@ export default class SimpleMetaTaggers { continue; } const value = feature.properties[key] - const denom = unit.findDenomination(value) + const denom = unit.findDenomination(value, () => feature.properties["_country"]) if (denom === undefined) { // no valid value found break; } const [, denomination] = denom; - let canonical = denomination?.canonicalValue(value) ?? undefined; + const defaultDenom = unit.getDefaultDenomination(() => feature.properties["_country"]) + let canonical = denomination?.canonicalValue(value, defaultDenom == denomination) ?? undefined; if (canonical === value) { break; } diff --git a/Models/Denomination.ts b/Models/Denomination.ts index e287e08846..a222041802 100644 --- a/Models/Denomination.ts +++ b/Models/Denomination.ts @@ -1,21 +1,22 @@ import {Translation} from "../UI/i18n/Translation"; -import {ApplicableUnitJson} from "./ThemeConfig/Json/UnitConfigJson"; +import {DenominationConfigJson} from "./ThemeConfig/Json/UnitConfigJson"; import Translations from "../UI/i18n/Translations"; -import {Store, UIEventSource} from "../Logic/UIEventSource"; +import {Store} from "../Logic/UIEventSource"; import BaseUIElement from "../UI/BaseUIElement"; import Toggle from "../UI/Input/Toggle"; export class Denomination { public readonly canonical: string; public readonly _canonicalSingular: string; - public readonly default: boolean; + public readonly useAsDefaultInput: boolean | string[] + public readonly useIfNoUnitGiven : boolean | string[] public readonly prefix: boolean; public readonly alternativeDenominations: string []; private readonly _human: Translation; private readonly _humanSingular?: Translation; - constructor(json: ApplicableUnitJson, context: string) { + constructor(json: DenominationConfigJson, context: string) { context = `${context}.unit(${json.canonicalDenomination})` this.canonical = json.canonicalDenomination.trim() if (this.canonical === undefined) { @@ -32,8 +33,12 @@ export class Denomination { this.alternativeDenominations = json.alternativeDenomination?.map(v => v.trim()) ?? [] - this.default = json.default ?? false; - + if(json["default"] !== undefined) { + throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead` + } + this.useIfNoUnitGiven = json.useIfNoUnitGiven + this.useAsDefaultInput = json.useAsDefaultInput ?? json.useIfNoUnitGiven + this._human = Translations.T(json.human, context + "human") this._humanSingular = Translations.T(json.humanSingular, context + "humanSingular") @@ -68,32 +73,31 @@ export class Denomination { * const unit = new Denomination({ * canonicalDenomination: "m", * alternativeDenomination: ["meter"], - * 'default': true, * human: { * en: "meter" * } * }, "test") - * unit.canonicalValue("42m") // =>"42 m" - * unit.canonicalValue("42") // =>"42 m" - * unit.canonicalValue("42 m") // =>"42 m" - * unit.canonicalValue("42 meter") // =>"42 m" - * + * unit.canonicalValue("42m", true) // =>"42 m" + * unit.canonicalValue("42", true) // =>"42 m" + * unit.canonicalValue("42 m", true) // =>"42 m" + * unit.canonicalValue("42 meter", true) // =>"42 m" + * unit.canonicalValue("42m", true) // =>"42 m" + * unit.canonicalValue("42", true) // =>"42 m" * * // Should be trimmed if canonical is empty * const unit = new Denomination({ * canonicalDenomination: "", * alternativeDenomination: ["meter","m"], - * 'default': true, * human: { * en: "meter" * } * }, "test") - * unit.canonicalValue("42m") // =>"42" - * unit.canonicalValue("42") // =>"42" - * unit.canonicalValue("42 m") // =>"42" - * unit.canonicalValue("42 meter") // =>"42" + * unit.canonicalValue("42m", true) // =>"42" + * unit.canonicalValue("42", true) // =>"42" + * unit.canonicalValue("42 m", true) // =>"42" + * unit.canonicalValue("42 meter", true) // =>"42" */ - public canonicalValue(value: string, actAsDefault?: boolean) : string { + public canonicalValue(value: string, actAsDefault: boolean) : string { if (value === undefined) { return undefined; } @@ -114,7 +118,7 @@ export class Denomination { * * Returns null if it doesn't match this unit */ - public StrippedValue(value: string, actAsDefault?: boolean): string { + public StrippedValue(value: string, actAsDefault: boolean): string { if (value === undefined) { return undefined; @@ -153,15 +157,26 @@ export class Denomination { } } - if (this.default || actAsDefault) { - const parsed = Number(value.trim()) - if (!isNaN(parsed)) { - return value.trim(); - } + if (!actAsDefault) { + return null + } + + const parsed = Number(value.trim()) + if (!isNaN(parsed)) { + return value.trim(); } return null; } + isDefaultUnit(country: () => string) { + if(this.useIfNoUnitGiven === true){ + return true + } + if(this.useIfNoUnitGiven === false){ + return false + } + return this.useIfNoUnitGiven.indexOf(country()) >= 0 + } } \ No newline at end of file diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 649bb672c5..74d7174b80 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -13,11 +13,16 @@ import {AddContextToTranslations} from "./AddContextToTranslations"; class ExpandTagRendering extends Conversion { private readonly _state: DesugaringContext; private readonly _self: LayerConfigJson; + private readonly _options: { + /* If true, will copy the 'osmSource'-tags into the condition */ + applyCondition?: true | boolean; + }; - constructor(state: DesugaringContext, self: LayerConfigJson) { + constructor(state: DesugaringContext, self: LayerConfigJson, options?: { applyCondition?: true | boolean;}) { super("Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question", [], "ExpandTagRendering"); this._state = state; this._self = self; + this._options = options; } convert(json: string | TagRenderingConfigJson | { builtin: string | string[]; override: any }, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings: string[] } { @@ -65,12 +70,14 @@ class ExpandTagRendering extends Conversion("layers:") for (let i = 0; i < matchingTrs.length; i++) { - // The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown let found: TagRenderingConfigJson = Utils.Clone(matchingTrs[i]); - if (found.condition === undefined) { - found.condition = layer.source.osmTags - } else { - found.condition = {and: [found.condition, layer.source.osmTags]} + if(this._options?.applyCondition){ + // The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown + if (found.condition === undefined) { + found.condition = layer.source.osmTags + } else { + found.condition = {and: [found.condition, layer.source.osmTags]} + } } found = contextWriter.convertStrict(found, layer.id + ".tagRenderings." + found["id"]) @@ -561,7 +568,7 @@ export class PrepareLayer extends Fuse { new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On("tagRenderings", layer => new Concat(new ExpandTagRendering(state, layer))), new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), - new On("mapRendering", layer => new Each(new On("icon", new FirstOf(new ExpandTagRendering(state, layer))))), + new On("mapRendering", layer => new Each(new On("icon", new FirstOf(new ExpandTagRendering(state, layer, {applyCondition: false}))))), new SetDefault("titleIcons", ["defaults"]), new On("titleIcons", layer => new Concat(new ExpandTagRendering(state, layer))) ); diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index 1347da5fab..f6a877df8a 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -613,6 +613,13 @@ export class ValidateLayer extends DesugaringStep { information.push(...(r.information ?? [])) } + { + const hasCondition = json.mapRendering?.filter(mr => mr["icon"] !== undefined && mr["icon"]["condition"] !== undefined) + if(hasCondition?.length > 0){ + errors.push("At "+context+":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n"+JSON.stringify(hasCondition, null, " ")) + } + } + if (json.presets !== undefined) { // Check that a preset will be picked up by the layer itself diff --git a/Models/ThemeConfig/Json/UnitConfigJson.ts b/Models/ThemeConfig/Json/UnitConfigJson.ts index f69212f000..269cd8eb7a 100644 --- a/Models/ThemeConfig/Json/UnitConfigJson.ts +++ b/Models/ThemeConfig/Json/UnitConfigJson.ts @@ -14,13 +14,32 @@ export default interface UnitConfigJson { /** * The possible denominations */ - applicableUnits: ApplicableUnitJson[] + applicableUnits: DenominationConfigJson[] } -export interface ApplicableUnitJson { +export interface DenominationConfigJson { + + /** - * The canonical value which will be added to the value in OSM. + * If this evaluates to true and the value to interpret has _no_ unit given, assumes that this unit is meant. + * Alternatively, a list of country codes can be given where this acts as the default interpretation + * + * E.g., a denomination using "meter" would probably set this flag to "true"; + * a denomination for "mp/h" will use the condition "_country=gb" to indicate that it is the default in the UK. + * + * If none of the units indicate that they are the default, the first denomination will be used instead + */ + useIfNoUnitGiven?: boolean | string[] + + /** + * Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default). + * If unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false + */ + useAsDefaultInput?: boolean | string[] + + /** + * The canonical value for this denomination which will be added to the value in OSM. * e.g. "m" for meters * If the user inputs '42', the canonical value will be added and it'll become '42m'. * @@ -28,8 +47,11 @@ export interface ApplicableUnitJson { * In this case, an empty string should be used */ canonicalDenomination: string, + + /** - * The canonical denomination in the case that the unit is precisely '1' + * The canonical denomination in the case that the unit is precisely '1'. + * Used for display purposes */ canonicalDenominationSingular?: string, @@ -63,9 +85,5 @@ export interface ApplicableUnitJson { */ prefix?: boolean - /** - * The default interpretation - only one can be set. - * If none is set, the first unit will be considered the default interpretation of a value without a unit - */ - default?: boolean + } \ No newline at end of file diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index bffe1e7f1f..1685344b10 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -14,8 +14,6 @@ import List from "../../UI/Base/List"; import {MappingConfigJson, QuestionableTagRenderingConfigJson} from "./Json/QuestionableTagRenderingConfigJson"; import {FixedUiElement} from "../../UI/Base/FixedUiElement"; import {Paragraph} from "../../UI/Base/Paragraph"; -import spec = Mocha.reporters.spec; -import SpecialVisualizations from "../../UI/SpecialVisualizations"; export interface Mapping { readonly if: TagsFilter, diff --git a/Models/Unit.ts b/Models/Unit.ts index 88f41ee417..93247752c1 100644 --- a/Models/Unit.ts +++ b/Models/Unit.ts @@ -8,14 +8,11 @@ export class Unit { public readonly appliesToKeys: Set; public readonly denominations: Denomination[]; public readonly denominationsSorted: Denomination[]; - public readonly defaultDenom: Denomination; public readonly eraseInvalid: boolean; - private readonly possiblePostFixes: string[] = [] - constructor(appliesToKeys: string[], applicableUnits: Denomination[], eraseInvalid: boolean) { + constructor(appliesToKeys: string[], applicableDenominations: Denomination[], eraseInvalid: boolean) { this.appliesToKeys = new Set(appliesToKeys); - this.denominations = applicableUnits; - this.defaultDenom = applicableUnits.filter(denom => denom.default)[0] + this.denominations = applicableDenominations; this.eraseInvalid = eraseInvalid const seenUnitExtensions = new Set(); @@ -52,8 +49,6 @@ export class Unit { addPostfixesOf(denomination._canonicalSingular) denomination.alternativeDenominations.forEach(addPostfixesOf) } - this.possiblePostFixes = Array.from(possiblePostFixes) - this.possiblePostFixes.sort((a, b) => b.length - a.length) } @@ -71,16 +66,12 @@ export class Unit { } // Some keys do have unit handling - const defaultSet = json.applicableUnits.filter(u => u.default === true) - // No default is defined - we pick the first as default - if (defaultSet.length === 0) { - json.applicableUnits[0].default = true - } - - // Check that there are not multiple defaults - if (defaultSet.length > 1) { - throw `Multiple units are set as default: they have canonical values of ${defaultSet.map(u => u.canonicalDenomination).join(", ")}` + if(json.applicableUnits.some(denom => denom.useAsDefaultInput !== undefined)){ + json.applicableUnits.forEach(denom => { + denom.useAsDefaultInput = denom.useAsDefaultInput ?? false + }) } + const applicable = json.applicableUnits.map((u, i) => new Denomination(u, `${ctx}.units[${i}]`)) return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false) } @@ -96,12 +87,13 @@ export class Unit { /** * Finds which denomination is applicable and gives the stripped value back */ - findDenomination(valueWithDenom: string): [string, Denomination] { + findDenomination(valueWithDenom: string, country: () => string): [string, Denomination] { if (valueWithDenom === undefined) { return undefined; } + const defaultDenom = this.getDefaultDenomination(country) for (const denomination of this.denominationsSorted) { - const bare = denomination.StrippedValue(valueWithDenom) + const bare = denomination.StrippedValue(valueWithDenom, defaultDenom === denomination) if (bare !== null) { return [bare, denomination] } @@ -109,11 +101,11 @@ export class Unit { return [undefined, undefined] } - asHumanLongValue(value: string): BaseUIElement { + asHumanLongValue(value: string, country: () => string): BaseUIElement { if (value === undefined) { return undefined; } - const [stripped, denom] = this.findDenomination(value) + const [stripped, denom] = this.findDenomination(value, country) const human = stripped === "1" ? denom?.humanSingular : denom?.human if (human === undefined) { return new FixedUiElement(stripped ?? value); @@ -124,24 +116,46 @@ export class Unit { } - /** - * Returns the value without any (sub)parts of any denomination - usefull as preprocessing step for validating inputs. - * E.g. - * if 'megawatt' is a possible denomination, then '5 Meg' will be rewritten to '5' (which can then be validated as a valid pnat) - * - * Returns the original string if nothign matches - */ - stripUnitParts(str: string) { - if (str === undefined) { - return undefined; - } - for (const denominationPart of this.possiblePostFixes) { - if (str.endsWith(denominationPart)) { - return str.substring(0, str.length - denominationPart.length).trim() + public getDefaultInput(country: () => string | string[]) { + console.log("Searching the default denomination for input", country) + for (const denomination of this.denominations) { + if (denomination.useAsDefaultInput === true) { + return denomination + } + if (denomination.useAsDefaultInput === undefined || denomination.useAsDefaultInput === false) { + continue + } + let countries: string | string[] = country() + if (typeof countries === "string") { + countries = countries.split(",") + } + const denominationCountries: string[] = denomination.useAsDefaultInput + if (countries.some(country => denominationCountries.indexOf(country) >= 0)) { + return denomination } } - - return str; + return this.denominations[0] } + + public getDefaultDenomination(country: () => string){ + for (const denomination of this.denominations) { + if (denomination.useIfNoUnitGiven === true || denomination.canonical === "") { + return denomination + } + if (denomination.useIfNoUnitGiven === undefined || denomination.useIfNoUnitGiven === false) { + continue + } + let countries: string | string[] = country() + if (typeof countries === "string") { + countries = countries.split(",") + } + const denominationCountries: string[] = denomination.useIfNoUnitGiven + if (countries.some(country => denominationCountries.indexOf(country) >= 0)) { + return denomination + } + } + return this.denominations[0] + } + } \ No newline at end of file diff --git a/UI/Base/ChartJs.ts b/UI/Base/ChartJs.ts index a4250f4c43..0389ee754c 100644 --- a/UI/Base/ChartJs.ts +++ b/UI/Base/ChartJs.ts @@ -1,6 +1,6 @@ import BaseUIElement from "../BaseUIElement"; import {Chart, ChartConfiguration, ChartType, DefaultDataPoint, registerables} from 'chart.js'; -Chart.register(...registerables); +Chart?.register(...(registerables ?? [])); export default class ChartJs< diff --git a/UI/Base/SubtleButton.ts b/UI/Base/SubtleButton.ts index 32931845fa..ccc09b156f 100644 --- a/UI/Base/SubtleButton.ts +++ b/UI/Base/SubtleButton.ts @@ -78,7 +78,6 @@ export class SubtleButton extends UIElement { }) const loading = new Lazy(() => new Loading(loadingText) ) return new VariableUiElement(state.map(st => { - console.log("State is: ", st) if(st === "idle"){ return button } diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index 94e6b7d972..59bd73d080 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -20,6 +20,7 @@ import {QueryParameters} from "../../Logic/Web/QueryParameters"; import {TagUtils} from "../../Logic/Tags/TagUtils"; import {InputElement} from "../Input/InputElement"; import {DropDown} from "../Input/DropDown"; +import {FixedUiElement} from "../Base/FixedUiElement"; export default class FilterView extends VariableUiElement { constructor(filteredLayer: UIEventSource, @@ -91,7 +92,7 @@ export default class FilterView extends VariableUiElement { if (filteredLayer.layerDef.name === undefined) { // Name is not defined: we hide this one return new Toggle( - filteredLayer?.layerDef?.description?.Clone()?.SetClass("subtle") , + new FixedUiElement(filteredLayer?.layerDef?.id ).SetClass("block") , undefined, state?.featureSwitchIsDebugging ); diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index e2d107ff23..7a316a4d9c 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -90,7 +90,7 @@ export class TextFieldDef { if (options.unit !== undefined) { // Reformatting is handled by the unit in this case options["isValid"] = str => { - const denom = options.unit.findDenomination(str); + const denom = options.unit.findDenomination(str, options?.country); if (denom === undefined) { return false; } @@ -153,7 +153,7 @@ export class TextFieldDef { } }) ) - unitDropDown.GetValue().setData(unit.defaultDenom) + unitDropDown.GetValue().setData(unit.getDefaultInput(options.country)) unitDropDown.SetClass("w-min") const fixedDenom = unit.denominations.length === 1 ? unit.denominations[0] : undefined @@ -169,7 +169,7 @@ export class TextFieldDef { }, (valueWithDenom: string) => { // Take the value from OSM and feed it into the textfield and the dropdown - const withDenom = unit.findDenomination(valueWithDenom); + const withDenom = unit.findDenomination(valueWithDenom, options?.country); if (withDenom === undefined) { // Not a valid value at all - we give it undefined and leave the details up to the other elements (but we keep the previous denomination) return [undefined, fixedDenom] diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 2f2dd157ac..3dd846f1d2 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -617,6 +617,7 @@ export default class TagRenderingQuestion extends Combine { const tagsData = tags.data; const feature = state?.allElements?.ContainingFeatures?.get(tagsData.id) const center = feature != undefined ? GeoOperations.centerpointCoordinates(feature) : [0, 0] + console.log("Creating a tr-question with applicableUnit", applicableUnit) const input: InputElement = ValidatedTextField.ForType(configuration.freeform.type)?.ConstructInputElement({ country: () => tagsData._country, location: [center[1], center[0]], diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 062563118f..fe12e191ff 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -1273,6 +1273,9 @@ export default class SpecialVisualizations { const tagRendering = layer.tagRenderings.find(tr => tr.id === tagRenderingId) tagRenderings.push([layer, tagRendering]) } + if(tagRenderings.length === 0){ + throw "Could not create stolen tagrenddering: tagRenderings not found" + } return new VariableUiElement(featureTags.map(tags => { const featureId = tags[featureIdKey] if (featureId === undefined) { diff --git a/Utils.ts b/Utils.ts index 370e76f5f9..f727cbad97 100644 --- a/Utils.ts +++ b/Utils.ts @@ -151,8 +151,8 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be return res; } - public static NoNull(array: T[]): T[] { - return array?.filter(o => o !== undefined && o !== null) + public static NoNull(array: T[]): NonNullable[] { + return array?.filter(o => o !== undefined && o !== null) } public static Hist(array: string[]): Map { diff --git a/assets/layers/cafe_pub/cafe_pub.json b/assets/layers/cafe_pub/cafe_pub.json index 3343f4b5ff..22250d79f6 100644 --- a/assets/layers/cafe_pub/cafe_pub.json +++ b/assets/layers/cafe_pub/cafe_pub.json @@ -271,6 +271,9 @@ "smoking", "service:electricity", "dog-access", + "internet", + "internet-fee", + "internet-ssid", "reviews" ], "filter": [ diff --git a/assets/layers/elevator/elevator.json b/assets/layers/elevator/elevator.json index 8db1b68100..74b1abc278 100644 --- a/assets/layers/elevator/elevator.json +++ b/assets/layers/elevator/elevator.json @@ -185,6 +185,7 @@ "alternativeDenomination": [ "meter" ], + "useIfNoUnitGiven": true, "human": { "en": "meter", "fr": "mètre", @@ -193,7 +194,7 @@ } }, { - "default": true, + "useAsDefaultInput": true, "canonicalDenomination": "cm", "alternativeDenomination": [ "centimeter", diff --git a/assets/layers/entrance/entrance.json b/assets/layers/entrance/entrance.json index 3feea6258a..058330ef8e 100644 --- a/assets/layers/entrance/entrance.json +++ b/assets/layers/entrance/entrance.json @@ -467,10 +467,12 @@ "units": [ { "appliesToKey": [ - "kerb:height" + "kerb:height", + "width" ], "applicableUnits": [ - { + { + "useIfNoUnitGiven": true, "canonicalDenomination": "m", "alternativeDenomination": [ "meter" @@ -483,7 +485,7 @@ } }, { - "default": true, + "useAsDefaultInput": true, "canonicalDenomination": "cm", "alternativeDenomination": [ "centimeter", diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index ba1cbfa7d5..8888218b7b 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -764,6 +764,9 @@ }, "service:electricity", "dog-access", + "internet", + "internet-fee", + "internet-ssid", "reviews" ], "filter": [ diff --git a/assets/layers/hotel/hotel.json b/assets/layers/hotel/hotel.json index d6a504e5cd..be618ac3b0 100644 --- a/assets/layers/hotel/hotel.json +++ b/assets/layers/hotel/hotel.json @@ -80,7 +80,10 @@ "phone", "email", "website", - "wheelchair-access" + "wheelchair-access", + "internet", + "internet-fee", + "internet-ssid" ], "allowMove": { "enableImproveAccuracy": true, diff --git a/assets/layers/hotel/hotel.svg b/assets/layers/hotel/hotel.svg index 879d08301a..efd17fb6bb 100644 --- a/assets/layers/hotel/hotel.svg +++ b/assets/layers/hotel/hotel.svg @@ -1,3 +1,45 @@ - - + + + + + + + diff --git a/assets/layers/hydrant/hydrant.json b/assets/layers/hydrant/hydrant.json index f1176762a0..50a0f8ea76 100644 --- a/assets/layers/hydrant/hydrant.json +++ b/assets/layers/hydrant/hydrant.json @@ -456,7 +456,6 @@ { "applicableUnits": [ { - "default": true, "canonicalDenomination": "", "alternativeDenomination": [ "mm", diff --git a/assets/layers/indoors/indoors.json b/assets/layers/indoors/indoors.json index 9b00ee2269..101fbaf4da 100644 --- a/assets/layers/indoors/indoors.json +++ b/assets/layers/indoors/indoors.json @@ -178,8 +178,6 @@ "centroid" ], "icon": { - "render": null, - "condition": "indoor=room", "mappings": [ { "if": { diff --git a/assets/layers/kerbs/kerbs.json b/assets/layers/kerbs/kerbs.json index 883c6c2d91..846d345cba 100644 --- a/assets/layers/kerbs/kerbs.json +++ b/assets/layers/kerbs/kerbs.json @@ -339,8 +339,7 @@ "nl": "centimeter", "de": "Zentimeter", "fr": "centimètre" - }, - "default": true + } }, { "canonicalDenomination": "m", diff --git a/assets/layers/maxspeed/maxspeed.json b/assets/layers/maxspeed/maxspeed.json index a3c7d005e0..7c0f9d47d5 100644 --- a/assets/layers/maxspeed/maxspeed.json +++ b/assets/layers/maxspeed/maxspeed.json @@ -110,16 +110,19 @@ "allowSplit": true, "mapRendering": [ { - "render": null, "icon": { + "render": null, "mappings": [ { "if": "maxspeed~[1-9]0|1[0-4]0", + "then": "./assets/themes/maxspeed/maxspeed_{maxspeed} mph.svg" + }, + { + "if": "maxspeed~[1-9]0|1[0-4]0 mph", "then": "./assets/themes/maxspeed/maxspeed_{maxspeed}.svg" } ] }, - "condition": "maxspeed!=30", "iconSize": { "render": "32,32,center" }, @@ -154,7 +157,6 @@ "kmh", "kph" ], - "default": true, "human": { "en": "kilometers/hour", "ca": "quilòmetres/hora", @@ -172,6 +174,7 @@ }, { "canonicalDenomination": "mph", + "useIfNoUnitGiven": ["gb","us"], "alternativeDenomination": [ "m/u", "mh", diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index 2e52f7833e..6452f92827 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -164,6 +164,15 @@ "fr": "Il n'y a pas de places de stationnement pour personnes à mobilité réduite" }, "hideInAnswer": true + }, + { + "if": "capacity:disabled=0", + "then": { + "en": "There are no disabled parking spots", + "nl": "Er zijn geen parkeerplaatsen voor gehandicapten", + "de": "Es gibt keine barrierefreien Stellplätze", + "fr": "Il n'y a pas de places de stationnement pour personnes à mobilité réduite" + } } ], "question": { @@ -247,9 +256,9 @@ "iconBadges": [ { "if": { - "and": [ - "capacity:disabled~*", - "capacity:disabled!=no" + "or": [ + "capacity:disabled>0", + "capacity:disabled=yes" ] }, "then": "circle:white;./assets/layers/toilet/wheelchair.svg" diff --git a/assets/layers/parking_spaces/license_info.json b/assets/layers/parking_spaces/license_info.json new file mode 100644 index 0000000000..e623b60aba --- /dev/null +++ b/assets/layers/parking_spaces/license_info.json @@ -0,0 +1,10 @@ +[ + { + "path": "parking_space.svg", + "license": "CC0", + "authors": [ + "Robin van der Linde" + ], + "sources": [] + } +] \ No newline at end of file diff --git a/assets/layers/parking_spaces/parking_space.svg b/assets/layers/parking_spaces/parking_space.svg new file mode 100644 index 0000000000..fc1cb2ff41 --- /dev/null +++ b/assets/layers/parking_spaces/parking_space.svg @@ -0,0 +1,70 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/assets/layers/parking_spaces/parking_spaces.json b/assets/layers/parking_spaces/parking_spaces.json new file mode 100644 index 0000000000..1405789f52 --- /dev/null +++ b/assets/layers/parking_spaces/parking_spaces.json @@ -0,0 +1,155 @@ +{ + "id": "parking_spaces", + "name": { + "en": "Parking Spaces" + }, + "description": { + "en": "Layer showing individual parking spaces." + }, + "minzoom": 20, + "source": { + "osmTags": "amenity=parking_space" + }, + "tagRenderings": [ + { + "id": "type", + "question": { + "en": "What kind of parking space is this?" + }, + "mappings": [ + { + "if": "parking_space=", + "then": { + "en": "This is a normal parking space" + }, + "hideInAnswer": true + }, + { + "if": "parking_space=normal", + "then": { + "en": "This is a normal parking space." + } + }, + { + "if": "parking_space=disabled", + "then": { + "en": "This is a disabled parking space." + } + }, + { + "if": "parking_space=private", + "then": { + "en": "This is a private parking space." + } + }, + { + "if": "parking_space=charging", + "then": { + "en": "This is parking space reserved for charging vehicles." + } + }, + { + "if": "parking_space=delivery", + "then": { + "en": "This is parking space reserved for deliveries." + } + }, + { + "if": "parking_space=hgv", + "then": { + "en": "This is parking space reserved for heavy goods vehicles." + } + }, + { + "if": "parking_space=caravan", + "then": { + "en": "This is parking space reserved for caravans or RVs." + } + }, + { + "if": "parking_space=bus", + "then": { + "en": "This is parking space reserved for buses." + } + }, + { + "if": "parking_space=motorcycle", + "then": { + "en": "This is parking space reserved for motorcycles." + } + }, + { + "if": "parking_space=parent", + "then": { + "en": "This is a parking space reserved for parents with children." + } + }, + { + "if": "parking_space=staff", + "then": { + "en": "This is a parking space reserved for staff." + } + }, + { + "if": "parking_space=taxi", + "then": { + "en": "This is a parking space reserved for taxis." + } + }, + { + "if": "parking_space=trailer", + "then": { + "en": "This is a parking space reserved for vehicles towing a trailer." + } + }, + { + "if": "parking_space=car_sharing", + "then": { + "en": "This is a parking space reserved for car sharing." + } + } + ] + }, + { + "id": "capacity", + "render": { + "en": "This parking spaces has {capacity} spaces." + }, + "mappings": [ + { + "if": "capacity=1", + "then": { + "en": "This parking space has 1 space." + } + } + ] + } + ], + "title": { + "render": { + "en": "Parking Space" + } + }, + "mapRendering": [ + { + "icon": { + "render": "./assets/layers/parking_spaces/parking_space.svg", + "mappings": [ + { + "if": "parking_space=disabled", + "then": "./assets/layers/toilet/wheelchair.svg" + } + ] + }, + "iconSize": "20,20,center", + "location": [ + "point", + "centroid" + ] + }, + { + "color": "#696969", + "width": "1" + } + ] +} \ No newline at end of file diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index f4a7f59e3a..324ee0634b 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -201,6 +201,9 @@ } ] }, + "internet", + "internet-fee", + "internet-ssid", "questions", "reviews" ], @@ -365,4 +368,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/layers/sport_pitch/sport_pitch.json b/assets/layers/sport_pitch/sport_pitch.json index 5ed7edc725..b1cfeb67c7 100644 --- a/assets/layers/sport_pitch/sport_pitch.json +++ b/assets/layers/sport_pitch/sport_pitch.json @@ -264,7 +264,7 @@ }, "mappings": [ { - "if": "access=public", + "if": "access=yes", "then": { "nl": "Publiek toegankelijk", "fr": "Accessible au public", @@ -309,6 +309,19 @@ "de": "Der Sportplatz ist nicht öffentlich zugänglich (es ist ein privater Sportplatz)", "es": "Privada - no accesible al público" } + }, + { + "if": "access=public", + "then": { + "nl": "Publiek toegankelijk", + "fr": "Accessible au public", + "en": "Public access", + "it": "Aperto al pubblico", + "ru": "Свободный доступ", + "de": "Der Sportplatz ist öffentlich zugänglich", + "es": "Acceso público" + }, + "hideInAnswer": true } ] }, diff --git a/assets/layers/walls_and_buildings/walls_and_buildings.json b/assets/layers/walls_and_buildings/walls_and_buildings.json index 91306ced03..bd8103078f 100644 --- a/assets/layers/walls_and_buildings/walls_and_buildings.json +++ b/assets/layers/walls_and_buildings/walls_and_buildings.json @@ -63,6 +63,7 @@ ], "applicableUnits": [ { + "useIfNoUnitGiven": true, "canonicalDenomination": "m", "alternativeDenomination": [ "meter" @@ -74,16 +75,16 @@ } }, { - "default": true, + "useAsDefaultInput": true, "canonicalDenomination": "cm", "alternativeDenomination": [ "centimeter", "cms" ], "human": { - "en": "centimeter", - "fr": "centimètre", - "de": "Zentimeter" + "en": " centimeter", + "fr": " centimètre", + "de": " Zentimeter" } } ] diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index c122f83e5e..e6237dbde5 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -1148,5 +1148,110 @@ } } ] + }, + "internet": { + "question": { + "en": "Does this place offer internet access?", + "nl": "Biedt deze plaats internettoegang aan?" + }, + "mappings": [ + { + "if": "internet_access=wlan", + "then": { + "en": "This place offers wireless internet access", + "nl": "Deze plaats biedt draadloze internettoegang aan" + } + }, + { + "if": "internet_access=no", + "then": { + "en": "This place does not offer internet access", + "nl": "Deze plaats biedt geen internettoegang aan" + } + }, + { + "if": "internet_access=yes", + "then": { + "en": "This place offers internet access", + "nl": "Deze plaats biedt internettoegang aan" + }, + "hideInAnswer": true + }, + { + "if": "internet_access=terminal", + "then": { + "en": "This place offers internet access via a terminal or computer", + "nl": "Deze plaats biedt internettoegang via een terminal of computer aan" + } + }, + { + "if": "internet_access=wired", + "then": { + "en": "This place offers wired internet access", + "nl": "Deze plaats biedt bedrade internettoegang aan" + } + } + ] + }, + "internet-fee": { + "condition": { + "and": [ + "internet_access!=no", + "internet_access!=" + ] + }, + "question": { + "en": "Is there a fee for internet access?", + "nl": "Zijn er kosten voor internettoegang?" + }, + "mappings": [ + { + "if": "internet_access:fee=yes", + "then": { + "en": "There is a fee for the internet access at this place", + "nl": "Er zijn kosten voor internettoegang op deze plaats" + } + }, + { + "if": "internet_access:fee=no", + "then": { + "en": "Internet access is free at this place", + "nl": "Internettoegang is gratis op deze plaats" + } + }, + { + "if": "internet_access:fee=customers", + "then": { + "en": "Internet access is free at this place, for customers only", + "nl": "Internettoegang is gratis op deze plaats, alleen voor klanten" + } + } + ] + }, + "internet-ssid": { + "condition": "internet_access=wlan", + "question": { + "en": "What is the network name for the wireless internet access?", + "nl": "Wat is de netwerknaam voor de draadloze internettoegang?" + }, + "freeform": { + "key": "internet_access:ssid", + "type": "string", + "placeholder": { + "en": "Enter the network name", + "nl": "Voer de netwerknaam in" + } + }, + "mappings": [ + { + "if": "internet_access:ssid=Telekom", + "then": "Telekom", + "hideInAnswer": "_country!=de" + } + ], + "render": { + "en": "The network name is {internet_access:ssid}", + "nl": "De netwerknaam is {internet_access:ssid}" + } } } \ No newline at end of file diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index 7d03bb2ef9..8eba848d79 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -132,8 +132,7 @@ "ca": " metre", "nb_NO": " meter", "es": " metro" - }, - "default": true + } }, { "canonicalDenomination": "ft", diff --git a/assets/themes/hotels/hotels.json b/assets/themes/hotels/hotels.json new file mode 100644 index 0000000000..ac681e073e --- /dev/null +++ b/assets/themes/hotels/hotels.json @@ -0,0 +1,18 @@ +{ + "id": "hotels", + "title": { + "en": "Hotels" + }, + "description": { + "en": "On this map, you'll find hotels in your area" + }, + "maintainer": "MapComplete", + "version": "0.0.1", + "icon": "./assets/layers/hotel/hotel.svg", + "startLat": 50.8552, + "startLon": 4.3755, + "startZoom": 13, + "layers": [ + "hotel" + ] +} \ No newline at end of file diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 102c5b3e2f..eeb934ac2a 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -207,6 +207,10 @@ "if": "theme=healthcare", "then": "./assets/layers/doctors/doctors.svg" }, + { + "if": "theme=hotels", + "then": "./assets/layers/hotel/hotel.svg" + }, { "if": "theme=indoors", "then": "./assets/layers/entrance/entrance.svg" diff --git a/assets/themes/maxspeed/license_info.json b/assets/themes/maxspeed/license_info.json index a712dc2b63..3aa1a76fce 100644 --- a/assets/themes/maxspeed/license_info.json +++ b/assets/themes/maxspeed/license_info.json @@ -1,142 +1,142 @@ [ { - "path": "maxspeed_10.svg", + "path": "maxspeed_10 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_100.svg", + "path": "maxspeed_100 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_110.svg", + "path": "maxspeed_110 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_120.svg", + "path": "maxspeed_120 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_130.svg", + "path": "maxspeed_130 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_140.svg", + "path": "maxspeed_140 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_20.svg", + "path": "maxspeed_20 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_30.svg", + "path": "maxspeed_30 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_40.svg", + "path": "maxspeed_40 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_50.svg", + "path": "maxspeed_50 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_60.svg", + "path": "maxspeed_60 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_70.svg", + "path": "maxspeed_70 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_80.svg", + "path": "maxspeed_80 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { - "path": "maxspeed_90.svg", + "path": "maxspeed_90 mph.svg", "license": "CC 4.0", "authors": [ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] }, { @@ -146,7 +146,7 @@ "yopaseopor" ], "sources": [ - "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30.svg" + "https://raw.githubusercontent.com/yopaseopor/beta_preset_josm/master/ES/traffic_signs/ES/ES_R301-30 mph.svg" ] } ] \ No newline at end of file diff --git a/assets/themes/maxspeed/maxspeed_10.svg b/assets/themes/maxspeed/maxspeed_10 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_10.svg rename to assets/themes/maxspeed/maxspeed_10 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_100.svg b/assets/themes/maxspeed/maxspeed_100 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_100.svg rename to assets/themes/maxspeed/maxspeed_100 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_110.svg b/assets/themes/maxspeed/maxspeed_110 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_110.svg rename to assets/themes/maxspeed/maxspeed_110 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_120.svg b/assets/themes/maxspeed/maxspeed_120 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_120.svg rename to assets/themes/maxspeed/maxspeed_120 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_130.svg b/assets/themes/maxspeed/maxspeed_130 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_130.svg rename to assets/themes/maxspeed/maxspeed_130 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_140.svg b/assets/themes/maxspeed/maxspeed_140 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_140.svg rename to assets/themes/maxspeed/maxspeed_140 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_20.svg b/assets/themes/maxspeed/maxspeed_20 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_20.svg rename to assets/themes/maxspeed/maxspeed_20 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_30.svg b/assets/themes/maxspeed/maxspeed_30 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_30.svg rename to assets/themes/maxspeed/maxspeed_30 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_40.svg b/assets/themes/maxspeed/maxspeed_40 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_40.svg rename to assets/themes/maxspeed/maxspeed_40 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_50.svg b/assets/themes/maxspeed/maxspeed_50 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_50.svg rename to assets/themes/maxspeed/maxspeed_50 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_60.svg b/assets/themes/maxspeed/maxspeed_60 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_60.svg rename to assets/themes/maxspeed/maxspeed_60 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_70.svg b/assets/themes/maxspeed/maxspeed_70 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_70.svg rename to assets/themes/maxspeed/maxspeed_70 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_80.svg b/assets/themes/maxspeed/maxspeed_80 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_80.svg rename to assets/themes/maxspeed/maxspeed_80 mph.svg diff --git a/assets/themes/maxspeed/maxspeed_90.svg b/assets/themes/maxspeed/maxspeed_90 mph.svg similarity index 100% rename from assets/themes/maxspeed/maxspeed_90.svg rename to assets/themes/maxspeed/maxspeed_90 mph.svg diff --git a/assets/themes/parkings/parkings.json b/assets/themes/parkings/parkings.json index 4a03f647be..ec190ff972 100644 --- a/assets/themes/parkings/parkings.json +++ b/assets/themes/parkings/parkings.json @@ -45,6 +45,7 @@ "startZoom": 12, "widenFactor": 1.2, "layers": [ - "parking" + "parking", + "parking_spaces" ] } \ No newline at end of file diff --git a/langs/layers/de.json b/langs/layers/de.json index 78d09b57a0..75df80ec68 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -3566,7 +3566,7 @@ }, "width": { "question": "Wie breit ist diese Tür bzw. dieser Eingang?", - "render": "Diese Tür hat eine Durchgangsbreite von {canonical(width)} Meter" + "render": "Diese Tür hat eine Durchgangsbreite von {canonical(width)}" } }, "title": { @@ -4249,10 +4249,51 @@ }, "question": "Welche Bauform hat der Hydrant?", "render": " Hydranten-Typ: {fire_hydrant:type}" + }, + "hydrant-couplings-diameters": { + "question": "Welchen Durchmesser haben die Kupplungen dieses Hydranten?", + "freeform": { + "placeholder": "Kupplungsdurchmesser" + }, + "render": "Kupplungsdurchmesser: {couplings:diameters}" + }, + "hydrant-couplings": { + "freeform": { + "placeholder": "Art der Kupplung" + }, + "mappings": { + "0": { + "then": "Storz-Kupplung" + }, + "1": { + "then": "UNI-Kupplung" + }, + "2": { + "then": "Barcelona-Kupplung" + } + }, + "question": "Welche Art von Kupplungen hat dieser Hydrant?", + "render": "Kupplungen: {couplings:type}" + }, + "hydrant-diameter": { + "freeform": { + "placeholder": "Rohrdurchmesser" + }, + "question": "Was ist der Rohrdurchmesser dieses Hydranten?", + "render": "Rohrdurchmesser: {canonical(fire_hydrant:diameter)}" } }, "title": { "render": "Hydrant" + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": "Millimeter" + } + } + } } }, "indoors": { @@ -4366,7 +4407,7 @@ "then": "Der Bordstein hat kein taktiles Pflaster." }, "2": { - "then": "Der Bordstein hat ein taktiles Pflaster, das aber falsch ist" + "then": "Der Bordstein hat ein taktiles Pflaster, das aber falsch ist." } }, "question": "Gibt es am Bordstein ein taktiles Pflaster?" @@ -4648,7 +4689,7 @@ } }, "question": "Wie hoch ist die zulässige Höchstgeschwindigkeit, die man auf dieser Straße fahren darf?", - "render": "Die zulässige Höchstgeschwindigkeit auf dieser Straße ist {maxspeed}" + "render": "Die zulässige Höchstgeschwindigkeit auf dieser Straße ist {canonical(maxspeed)}" } }, "title": { @@ -7156,4 +7197,4 @@ } } } -} \ No newline at end of file +} diff --git a/langs/layers/en.json b/langs/layers/en.json index 50e29227ee..3dc11e5913 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -3566,7 +3566,7 @@ }, "width": { "question": "What is the width of this door/entrance?", - "render": "This door has a width of {canonical(width)} meter" + "render": "This door has a width of {canonical(width)}" } }, "title": { @@ -4438,7 +4438,7 @@ "then": "This kerb does not have tactile paving." }, "2": { - "then": "This kerb has tactile paving, but it is incorrect" + "then": "This kerb has tactile paving, but it is incorrect." } }, "question": "Is there tactile paving at this kerb?" @@ -4720,7 +4720,7 @@ } }, "question": "What is the legal maximum speed one is allowed to drive on this road?", - "render": "The maximum allowed speed on this road is {maxspeed}" + "render": "The maximum allowed speed on this road is {canonical(maxspeed)}" } }, "title": { @@ -7228,4 +7228,4 @@ } } } -} \ No newline at end of file +} diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 78753c279b..e9e10cb505 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -3049,7 +3049,7 @@ "then": "Cette bordure n'a pas de revêtement podotactile." }, "2": { - "then": "Cette bordure a un pavage tactile, mais il est incorrect" + "then": "Cette bordure a un pavage tactile, mais il est incorrect." } }, "question": "Y a-t-il un revêtement tactile sur cette bordure ?" @@ -4459,4 +4459,4 @@ } } } -} \ No newline at end of file +} diff --git a/langs/layers/nl.json b/langs/layers/nl.json index df5ba4b817..a3327973c4 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -3406,6 +3406,9 @@ "presets": { "0": { "title": "een toegang" + }, + "1": { + "title": "een binnendeur" } }, "tagRenderings": { @@ -3509,7 +3512,7 @@ }, "width": { "question": "Wat is de breedte van deze deur/toegang?", - "render": "Deze deur heeft een breedte van {canonical(width)} meter" + "render": "Deze deur heeft een breedte van {canonical(width)}" } }, "title": { @@ -4221,7 +4224,43 @@ "description": "Een basis voor indoor-navigatie: toont binnenruimtes", "name": "Binnenruimtes", "title": { - "render": "Binnenruimte {name}" + "render": "Binnenruimte {name}", + "mappings": { + "0": { + "then": "Binnenruimte {name}" + }, + "4": { + "then": "Binnendeur {name}" + }, + "3": { + "then": "Gang in gebouw {name}" + }, + "1": { + "then": "Gebied in gebouw {name}" + }, + "5": { + "then": "Verdieping in gebouw {name}" + }, + "2": { + "then": "Muur in gebouw {name}" + } + } + }, + "tagRenderings": { + "name": { + "render": "Deze ruimte heet {name}", + "question": "Wat is de naam van deze ruimte?", + "freeform": { + "placeholder": "Naam van de ruimte" + } + }, + "ref": { + "question": "Wat is het referentienummer van deze ruimte?", + "freeform": { + "placeholder": "Referentienummer van de ruimte (bv. '1.1' of A1' )" + }, + "render": "Deze ruimte heeft het referentienummer {ref}" + } } }, "information_board": { @@ -4264,7 +4303,16 @@ "1": { "options": { "0": { - "question": "Drempes met of zonder" + "question": "Stoepranden met of zonder voelbare bestrating" + }, + "1": { + "question": "Stoeprand met voelbare bestrating" + }, + "2": { + "question": "Stoeprand zonder voelbare bestrating" + }, + "3": { + "question": "Stoeprand zonder informatie over voelbare bestrating" } } } @@ -4303,6 +4351,20 @@ } }, "question": "Hoe hoog is deze stoeprand?" + }, + "tactile-paving": { + "mappings": { + "0": { + "then": "Deze stoeprand heeft voelbare bestrating." + }, + "1": { + "then": "Deze stoeprand heeft geen voelbare bestrating." + }, + "2": { + "then": "Deze stoeprand heeft voelbare bestrating, maar deze is incorrect." + } + }, + "question": "Is er voelbare bestrating bij deze stoeprand?" } }, "title": { @@ -4353,6 +4415,10 @@ "override": { "question": "Wanneer is deze kinderopvang geopend?" } + }, + "name": { + "question": "Wat is de naam van deze faciliteit?", + "render": "Deze faciliteit heet {name}" } }, "title": { @@ -4423,7 +4489,7 @@ "then": "Dit is een woonerf en heeft dus een maximale snelheid van 20km/h" } }, - "render": "De maximum toegestane snelheid op deze weg is {maxspeed}" + "render": "De maximum toegestane snelheid op deze weg is {canonical(maxspeed)}" } }, "title": { @@ -6800,5 +6866,92 @@ } } } + }, + "maproulette": { + "filter": { + "0": { + "options": { + "1": { + "question": "Toon aangemaakte taken" + }, + "8": { + "question": "Toon uitgeschakelde taken" + }, + "0": { + "question": "Toon taken met alle statussen" + }, + "3": { + "question": "Toon vals-positieve taken" + }, + "4": { + "question": "Toon overgeslagen taken" + }, + "5": { + "question": "Toon verwijderde taken" + }, + "6": { + "question": "Toon al opgeloste taken" + }, + "7": { + "question": "Toon taken die als te lastig gemarkeerd zijn" + }, + "2": { + "question": "Toon opgeloste taken" + } + } + }, + "1": { + "options": { + "0": { + "question": "Naam uitdaging bevat {search}" + } + } + }, + "2": { + "options": { + "0": { + "question": "ID uitdaging is {search}" + } + } + } + }, + "description": "Laag met alle taken uit MapRoulette", + "name": "MapRoulette Taken" + }, + "transit_routes": { + "tagRenderings": { + "operator": { + "question": "Welk bedrijf exploiteert deze buslijn?", + "render": "Deze buslijn wordt geëxploiteerd door {operator}" + }, + "from": { + "render": "Deze buslijn begint bij {from}", + "question": "Wat is het beginpunt van deze buslijn?" + }, + "to": { + "render": "Deze buslijn eindigt bij {to}", + "question": "Wat is het eindpunt van deze buslijn?" + }, + "colour": { + "question": "Wat is de kleur van deze buslijn?", + "render": "Deze buslijn heeft de kleur {colour}" + }, + "name": { + "question": "Wat is de naam van deze buslijn (bv. Bus XX: Van => Via => Naar)" + }, + "network": { + "question": "Bij welk netwerk hoort deze buslijn?", + "render": "Deze buslijn is onderdeel van het {network} netwerk" + }, + "via": { + "question": "Via welk punt gaat deze buslijn?", + "render": "Deze buslijn gaat via {via}" + } + }, + "title": { + "render": "Buslijn" + }, + "description": "Laag met buslijnen", + "name": "Buslijnen" } -} \ No newline at end of file +} diff --git a/langs/nl.json b/langs/nl.json index 5cdcf3a4eb..fda62796b1 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -26,7 +26,7 @@ "readMessages": "Je hebt ongelezen berichten. Je moet deze lezen voordat je een punt verwijderd, een andere bijdrager heeft misschien feedback", "reasons": { "disused": "Het wordt niet meer onderhouden of is verwijderd", - "duplicate": "Dit punt is een duplicaat van een ander punt", + "duplicate": "Dit object is een duplicaat van een ander object", "notFound": "Het kon niet gevonden worden", "test": "Dit punt was een test en was nooit echt aanwezig" }, @@ -49,19 +49,19 @@ "confirmIntro": "

Voeg een {title} toe?

Het punt dat je toevoegt, is zichtbaar voor iedereen. Veel applicaties gebruiken deze data, voeg dus enkel punten toe die echt bestaan.", "disableFilters": "Zet alle filters af", "disableFiltersExplanation": "Interessepunten kunnen verborgen zijn door een filter", - "hasBeenImported": "Dit object is reeds geimporteerd", + "hasBeenImported": "Dit object is reeds geïmporteerd", "import": { "hasBeenImported": "Dit object is geïmporteerd", "howToTest": "Voor testmode, voeg test=true of backend=osm-test to aan de URL. De wijzigingenset zal in de console geprint worden. Gelieve een PR te openen om dit thema als officieel thema toe te voegen en zo de import-knop te activeren.", "importTags": "Het element zal deze tags krijgen: {tags}", "officialThemesOnly": "In onofficiële thema's is de importeerknop uitgeschakeld om ongelukjes te vermijden", "wrongType": "Dit object is geen punt of lijn, en kan daarom niet geïmporteerd worden", - "wrongTypeToConflate": "Dit element is geen punt of weg en kan dus niet samengevoegd worden", + "wrongTypeToConflate": "Dit object is geen punt of weg en kan dus niet samengevoegd worden", "zoomInMore": "Zoom verder in om dit object af te handelen" }, "importTags": "Het object zal deze tags krijgen: {tags}", "intro": "Kies hieronder welk punt je wilt toevoegen
", - "layerNotEnabled": "De laag {layer} is gedeactiveerd. Activeer deze om een punt toe te voegen", + "layerNotEnabled": "De laag {layer} is gedeactiveerd. Activeer deze om een object toe te voegen", "openLayerControl": "Open de laag-instellingen", "pleaseLogin": "Gelieve je aan te melden om een punt toe te voegen", "presetInfo": "Het nieuwe object krijgt de attributen {tags}", @@ -272,7 +272,8 @@ "died": "Gestorven: {value}" }, "searchWikidata": "Zoek op Wikidata", - "wikipediaboxTitle": "Wikipedia" + "wikipediaboxTitle": "Wikipedia", + "searchToShort": "Je zoekopdracht is te kort, vul een langere tekst in" } }, "image": { @@ -578,7 +579,8 @@ "noNearOrIn": "Sorry, Ik begreep je opdracht niet omdat ik geen dichtbij of in in je zoekopdracht.\nProbeer iets als Zoek drinkwater in Londen, Zoek frituur in Brussel

\n

Daarnaast kan je ook info {cmd} proberen, om info te krijgen over een enkel object.

", "nothingFound": "Sorry, ik kon niets vinden voor {search}, dus kan ik {layerTitle} niet zoeken", "overview": "Ik heb {length} overeenkomende items gevonden.", - "searching": "Aan het zoeken naar {layerTitle} {mode} {search}…" + "searching": "Aan het zoeken naar {layerTitle} {mode} {search}…", + "docs": "Zoekt voor POIs in of nabij een locatie" }, "shutdown": { "argmode": "Geeft aan op welke manier ik moet afsluiten. Dit moet één van de volgende woorden zijn: {verbs}", diff --git a/langs/shared-questions/en.json b/langs/shared-questions/en.json index c716ca0b29..c251488a22 100644 --- a/langs/shared-questions/en.json +++ b/langs/shared-questions/en.json @@ -34,6 +34,47 @@ }, "question": "Does this place have an audio induction loop for people with reduced hearing?" }, + "internet": { + "mappings": { + "0": { + "then": "This place offers wireless internet access" + }, + "1": { + "then": "This place does not offer internet access" + }, + "2": { + "then": "This place offers internet access" + }, + "3": { + "then": "This place offers internet access via a terminal or computer" + }, + "4": { + "then": "This place offers wired internet access" + } + }, + "question": "Does this place offer internet access?" + }, + "internet-fee": { + "mappings": { + "0": { + "then": "There is a fee for the internet access at this place" + }, + "1": { + "then": "Internet access is free at this place" + }, + "2": { + "then": "Internet access is free at this place, for customers only" + } + }, + "question": "Is there a fee for internet access?" + }, + "internet-ssid": { + "freeform": { + "placeholder": "Enter the network name" + }, + "question": "What is the network name for the wireless internet access?", + "render": "The network name is {internet_access:ssid}" + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/nl.json b/langs/shared-questions/nl.json index c5bd2085e4..ed7cde93ac 100644 --- a/langs/shared-questions/nl.json +++ b/langs/shared-questions/nl.json @@ -23,6 +23,47 @@ "email": { "question": "Wat is het e-mailadres van {title()}?" }, + "internet": { + "mappings": { + "0": { + "then": "Deze plaats biedt draadloze internettoegang aan" + }, + "1": { + "then": "Deze plaats biedt geen internettoegang aan" + }, + "2": { + "then": "Deze plaats biedt internettoegang aan" + }, + "3": { + "then": "Deze plaats biedt internettoegang via een terminal of computer aan" + }, + "4": { + "then": "Deze plaats biedt bedrade internettoegang aan" + } + }, + "question": "Biedt deze plaats internettoegang aan?" + }, + "internet-fee": { + "mappings": { + "0": { + "then": "Er zijn kosten voor internettoegang op deze plaats" + }, + "1": { + "then": "Internettoegang is gratis op deze plaats" + }, + "2": { + "then": "Internettoegang is gratis op deze plaats, alleen voor klanten" + } + }, + "question": "Zijn er kosten voor internettoegang?" + }, + "internet-ssid": { + "freeform": { + "placeholder": "Voer de netwerknaam in" + }, + "question": "Wat is de netwerknaam voor de draadloze internettoegang?", + "render": "De netwerknaam is {internet_access:ssid}" + }, "level": { "mappings": { "0": { diff --git a/langs/themes/de.json b/langs/themes/de.json index 93aa44529a..f85c42136d 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -906,6 +906,28 @@ "tagRenderings": { "streetname": { "render": "Diese Straße heißt {name}" + }, + "left-right-questions": { + "renderings": { + "1": { + "question": "Gibt es auf dieser Straßenseite einen Bürgersteig?", + "mappings": { + "1": { + "then": "Nein, es gibt keinen Bürgersteig für Fußgänger" + }, + "0": { + "then": "Ja, es gibt einen Bürgersteig auf dieser Straßenseite" + }, + "2": { + "then": "Es gibt einen separat kartierten Bürgersteig für Fußgänger" + } + } + }, + "2": { + "question": "Wie breit ist der Bürgersteig auf dieser Straßenseite?", + "render": "Dieser Bürgersteig ist {sidewalk:left|right:width}m breit" + } + } } }, "title": { @@ -1008,4 +1030,4 @@ "shortDescription": "Eine Karte mit Abfalleimern", "title": "Abfalleimer" } -} \ No newline at end of file +} diff --git a/langs/zh_Hant.json b/langs/zh_Hant.json index 107c6a7f99..27359d123a 100644 --- a/langs/zh_Hant.json +++ b/langs/zh_Hant.json @@ -10,7 +10,7 @@ "cannotBeDeleted": "這圖徵無法刪除", "delete": "刪除", "explanations": { - "hardDelete": "這個點已經在開放街圖被刪除了,可以被實驗性的貢獻者恢復", + "hardDelete": "這個圖徵已經在開放街圖被刪除了,可以被實驗性的貢獻者恢復", "selectReason": "請選擇為什麼這個圖徵應該被刪除", "softDelete": "這個圖徵已經被更新,然後從程式被隱藏了。{reason}" }, diff --git a/test/Models/Units.spec.ts b/test/Models/Units.spec.ts index cfc54be9c4..e5045acb09 100644 --- a/test/Models/Units.spec.ts +++ b/test/Models/Units.spec.ts @@ -7,22 +7,21 @@ describe("Unit", () => { it("should convert a value back and forth", () => { - const unit = new Denomination({ + const denomintion = new Denomination({ "canonicalDenomination": "MW", "alternativeDenomination": ["megawatts", "megawatt"], "human": { "en": " megawatts", "nl": " megawatt" }, - "default": true }, "test"); - const canonical = unit.canonicalValue("5") + const canonical = denomintion.canonicalValue("5", true) expect(canonical).eq( "5 MW") - const units = new Unit(["key"], [unit], false) - const [detected, detectedDenom] = units.findDenomination("5 MW") + const units = new Unit(["key"], [denomintion], false) + const [detected, detectedDenom] = units.findDenomination("5 MW", () => "be") expect(detected).eq( "5") - expect(detectedDenom).eq( unit) + expect(detectedDenom).eq( denomintion) } ) })