MapComplete/src/UI/Popup/DataVisualisations.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

335 lines
12 KiB
TypeScript
Raw Normal View History

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 CollectionTimes from "../CollectionTimes/CollectionTimes.svelte"
class DirectionIndicatorVis extends SpecialVisualization {
funcName = "direction_indicator"
args = []
2025-07-10 18:26:31 +02:00
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"
2025-07-10 18:26:31 +02:00
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"
2025-07-10 18:26:31 +02:00
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.countryCoderInfo]
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"
2025-07-10 18:26:31 +02:00
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) ?? []),
)
2025-07-10 18:26:31 +02:00
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"
2025-07-10 18:26:31 +02:00
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"
2025-07-10 18:26:31 +02:00
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
2025-07-10 18:26:31 +02:00
if (layer._basedOn !== layer.id) {
console.warn("Trying to use the _original_ layer")
2025-07-10 18:26:31 +02:00
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,
})),
}
2025-07-10 18:26:31 +02:00
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"
2025-07-10 18:26:31 +02:00
constr(state, tags: UIEventSource<Record<string, string>>, _, __, layer: LayerConfig) {
return new SvelteUIElement(AllTagsPanel, { tags, layer })
}
}
class PointsInTimeVis extends SpecialVisualization {
docs = "Creates a visualisation for 'points in time', e.g. collection times of a postbox"
group = "data"
funcName = "points_in_time"
args = [
{
name: "key",
required: true,
doc: "The key out of which the points_in_time will be parsed",
},
]
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, args: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
const key = args[0]
const points_in_time = tagSource.map(tags => tags[key])
const times = points_in_time.map(times =>
OH.createOhObject(<any>tagSource.data, times, tagSource.data["_country"], 1), [tagSource])
return new VariableUiElement(times.map(times =>
new SvelteUIElement(CollectionTimes, { times }),
))
}
}
export class DataVisualisations {
public static initList(): SpecialVisualization[] {
return [
new HistogramViz(),
new StatisticsVis(),
new DirectionAbsolute(),
new DirectionIndicatorVis(),
new OpeningHoursTableVis(),
new OpeningHoursState(),
new PointsInTimeVis(),
new Canonical(),
new LanguageElement(),
new PresetDescription(),
new PresetTypeSelect(),
new AllTagsVis(),
]
}
}