forked from MapComplete/MapComplete
353 lines
12 KiB
TypeScript
353 lines
12 KiB
TypeScript
|
|
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<Record<string, string>>,
|
||
|
|
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<Record<string, string>>,
|
||
|
|
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<any>, args) {
|
||
|
|
const [key, prefix, postfix] = args
|
||
|
|
const openingHoursStore: Store<opening_hours | "error" | undefined> =
|
||
|
|
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<Record<string, string>>,
|
||
|
|
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<Record<string, string>>,
|
||
|
|
): 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<Record<string, string>>,
|
||
|
|
argument: string[],
|
||
|
|
selectedElement: Feature,
|
||
|
|
layer: LayerConfig,
|
||
|
|
): SvelteUIElement {
|
||
|
|
const t = Translations.t.preset_type
|
||
|
|
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,
|
||
|
|
})),
|
||
|
|
}
|
||
|
|
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<Record<string, string>>,
|
||
|
|
_,
|
||
|
|
__,
|
||
|
|
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<Record<string, string>>,
|
||
|
|
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(),
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|