import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" import { HistogramViz } from "./HistogramViz" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Feature } from "geojson" import BaseUIElement from "../BaseUIElement" import SvelteUIElement from "../Base/SvelteUIElement" import DirectionIndicator from "../Base/DirectionIndicator.svelte" import { VariableUiElement } from "../Base/VariableUIElement" import { GeoOperations } from "../../Logic/GeoOperations" import Translations from "../i18n/Translations" import Constants from "../../Models/Constants" import opening_hours from "opening_hours" import { OH } from "../OpeningHours/OpeningHours" import OpeningHoursWithError from "../OpeningHours/Visualisation/OpeningHoursWithError.svelte" import NextChangeViz from "../OpeningHours/NextChangeViz.svelte" import { Unit } from "../../Models/Unit" import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte" import { LanguageElement } from "./LanguageElement/LanguageElement" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import { And } from "../../Logic/Tags/And" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte" import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte" import { FixedUiElement } from "../Base/FixedUiElement" import { TagUtils } from "../../Logic/Tags/TagUtils" class DirectionIndicatorVis extends SpecialVisualization { funcName = "direction_indicator" args = [] docs = "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object" group = "data" constr( state: SpecialVisualizationState, tagSource: UIEventSource>, argument: string[], feature: Feature, ): BaseUIElement { return new SvelteUIElement(DirectionIndicator, { state, feature }) } } class DirectionAbsolute extends SpecialVisualization { funcName = "direction_absolute" docs = "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'" args = [ { name: "key", type: "key", doc: "The attribute containing the degrees", defaultValue: "_direction:centerpoint", }, { name: "offset", doc: "Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction", defaultValue: "0", }, ] group = "data" constr( state: SpecialVisualizationState, tagSource: UIEventSource>, args: string[], ): BaseUIElement { const key = args[0] === "" ? "_direction:centerpoint" : args[0] const offset = args[1] === "" ? 0 : Number(args[1]) return new VariableUiElement( tagSource .map((tags) => { console.log("Direction value", tags[key], key) return tags[key] }) .mapD((value) => { const dir = GeoOperations.bearingToHuman( GeoOperations.parseBearing(value) + offset, ) console.log("Human dir", dir) return Translations.t.general.visualFeedback.directionsAbsolute[dir] }), ) } } class OpeningHoursTableVis extends SpecialVisualizationSvelte { funcName = "opening_hours_table" docs = "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'." args = [ { name: "key", defaultValue: "opening_hours", type: "key", doc: "The tagkey from which the table is constructed.", }, { name: "prefix", defaultValue: "", doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__", }, { name: "postfix", defaultValue: "", doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", }, ] group = "data" needsUrls = [Constants.countryCoderEndpoint] example = "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`" constr(state, tagSource: UIEventSource, args) { const [key, prefix, postfix] = args const openingHoursStore: Store = OH.CreateOhObjectStore(tagSource, key, prefix, postfix) return new SvelteUIElement(OpeningHoursWithError, { tags: tagSource, key, opening_hours_obj: openingHoursStore, }) } } class OpeningHoursState extends SpecialVisualizationSvelte { group = "data" funcName = "opening_hours_state" docs = "A small element, showing if the POI is currently open and when the next change is" args = [ { name: "key", type: "key", defaultValue: "opening_hours", doc: "The tagkey from which the opening hours are read.", }, { name: "prefix", defaultValue: "", doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__", }, { name: "postfix", defaultValue: "", doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", }, ] constr( state: SpecialVisualizationState, tags: UIEventSource>, args: string[], ): SvelteUIElement { const keyToUse = args[0] const prefix = args[1] const postfix = args[2] return new SvelteUIElement(NextChangeViz, { state, keyToUse, tags, prefix, postfix, }) } } class Canonical extends SpecialVisualization { group = "data" funcName = "canonical" docs = "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. " example = "If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ..." args = [ { name: "key", type: "key", doc: "The key of the tag to give the canonical text for", required: true, }, ] constr(state, tagSource, args) { const key = args[0] return new VariableUiElement( tagSource .map((tags) => tags[key]) .map((value) => { if (value === undefined) { return undefined } const allUnits: Unit[] = [].concat( ...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []), ) const unit = allUnits.filter((unit) => unit.isApplicableToKey(key), )[0] if (unit === undefined) { return value } const getCountry = () => tagSource.data._country return unit.asHumanLongValue(value, getCountry) }), ) } } class StatisticsVis extends SpecialVisualizationSvelte { funcName = "statistics" group = "data" docs = "Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer" args = [] constr(state) { return new SvelteUIElement(AllFeaturesStatistics, { state }) } } class PresetDescription extends SpecialVisualization { funcName = "preset_description" docs = "Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty" args = [] constr( state: SpecialVisualizationState, tagSource: UIEventSource>, ): BaseUIElement { const translation = tagSource.map((tags) => { const layer = state.theme.getMatchingLayer(tags) return layer?.getMostMatchingPreset(tags)?.description }) return new VariableUiElement(translation) } } class PresetTypeSelect extends SpecialVisualizationSvelte { funcName = "preset_type_select" docs = "An editable tag rendering which allows to change the type" args = [] constr( state: SpecialVisualizationState, tags: UIEventSource>, argument: string[], selectedElement: Feature, layer: LayerConfig, ): SvelteUIElement { const t = Translations.t.preset_type if(layer._basedOn !== layer.id){ console.warn("Trying to use the _original_ layer") layer = state.theme.layers.find(l => l.id === layer._basedOn) ?? layer } const question: QuestionableTagRenderingConfigJson = { id: layer.id + "-type", question: t.question.translations, mappings: layer.presets.map((pr) => ({ if: new And(pr.tags).asJson(), icon: "auto", then: (pr.description ? t.typeDescription : t.typeTitle).Subs({ title: pr.title, description: pr.description, }).translations, })), } if(question.mappings.length === 0){ console.error("No mappings for preset_type_select, something went wrong") return undefined } const config = new TagRenderingConfig(question) return new SvelteUIElement(TagRenderingEditable, { config, tags, selectedElement, state, layer, }) } } class AllTagsVis extends SpecialVisualizationSvelte { funcName = "all_tags" docs = "Prints all key-value pairs of the object - used for debugging" args = [] group = "data" constr( state, tags: UIEventSource>, _, __, layer: LayerConfig, ) { return new SvelteUIElement(AllTagsPanel, { tags, layer }) } } class TagsVis extends SpecialVisualization { funcName = "tags" docs = "Shows a (json of) tags in a human-readable way + links to the wiki" args = [ { name: "key", type: "key", defaultValue: "value", doc: "The key to look for the tags", }, ] constr( state: SpecialVisualizationState, tagSource: UIEventSource>, argument: string[], ): BaseUIElement { const key = argument[0] ?? "value" return new VariableUiElement( tagSource.map((tags) => { let value = tags[key] if (!value) { return new FixedUiElement("No tags found").SetClass("font-bold") } if (typeof value === "string" && value.startsWith("{")) { value = JSON.parse(value) } try { const parsed = TagUtils.Tag(value) return parsed.asHumanString(true, false, {}) } catch (e) { return new FixedUiElement( "Could not parse this tag: " + JSON.stringify(value) + " due to " + e, ).SetClass("alert") } }), ) } } export class DataVisualisations { public static initList(): SpecialVisualization[] { return [ new HistogramViz(), new StatisticsVis(), new DirectionAbsolute(), new DirectionIndicatorVis(), new OpeningHoursTableVis(), new OpeningHoursState(), new Canonical(), new LanguageElement(), new PresetDescription(), new PresetTypeSelect(), new AllTagsVis(), new TagsVis(), ] } }