2025-07-28 01:00:07 +02:00
import { SpecialVisualization , SpecialVisualizationState , SpecialVisualizationSvelte } from "../SpecialVisualization"
2025-06-26 05:20:12 +02:00
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"
2025-07-28 01:00:07 +02:00
import CollectionTimes from "../CollectionTimes/CollectionTimes.svelte"
2025-06-26 05:20:12 +02:00
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"
2025-06-26 05:20:12 +02:00
group = "data"
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
argument : string [ ] ,
2025-07-28 01:00:07 +02:00
feature : Feature ,
2025-06-26 05:20:12 +02:00
) : 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'"
2025-06-26 05:20:12 +02:00
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 > > ,
2025-07-28 01:00:07 +02:00
args : string [ ] ,
2025-06-26 05:20:12 +02:00
) : 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 (
2025-07-28 01:00:07 +02:00
GeoOperations . parseBearing ( value ) + offset ,
2025-06-26 05:20:12 +02:00
)
console . log ( "Human dir" , dir )
return Translations . t . general . visualFeedback . directionsAbsolute [ dir ]
2025-07-28 01:00:07 +02:00
} ) ,
2025-06-26 05:20:12 +02:00
)
}
}
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'."
2025-06-26 05:20:12 +02:00
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"
2025-07-06 02:29:48 +02:00
needsUrls = [ Constants . countryCoderInfo ]
2025-06-26 05:20:12 +02:00
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 > > ,
2025-07-28 01:00:07 +02:00
args : string [ ] ,
2025-06-26 05:20:12 +02:00
) : 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. "
2025-06-26 05:20:12 +02:00
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 (
2025-07-28 01:00:07 +02:00
. . . ( state ? . theme ? . layers ? . map ( ( lyr ) = > lyr . units ) ? ? [ ] ) ,
2025-06-26 05:20:12 +02:00
)
2025-07-10 18:26:31 +02:00
const unit = allUnits . filter ( ( unit ) = > unit . isApplicableToKey ( key ) ) [ 0 ]
2025-06-26 05:20:12 +02:00
if ( unit === undefined ) {
return value
}
const getCountry = ( ) = > tagSource . data . _country
return unit . asHumanLongValue ( value , getCountry )
2025-07-28 01:00:07 +02:00
} ) ,
2025-06-26 05:20:12 +02:00
)
}
}
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"
2025-06-26 05:20:12 +02:00
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"
2025-06-26 05:20:12 +02:00
args = [ ]
constr (
state : SpecialVisualizationState ,
2025-07-28 01:00:07 +02:00
tagSource : UIEventSource < Record < string , string > > ,
2025-06-26 05:20:12 +02:00
) : 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 ,
2025-07-28 01:00:07 +02:00
layer : LayerConfig ,
2025-06-26 05:20:12 +02:00
) : SvelteUIElement {
const t = Translations . t . preset_type
2025-07-10 18:26:31 +02:00
if ( layer . _basedOn !== layer . id ) {
2025-07-03 17:32:22 +02:00
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
2025-07-03 17:32:22 +02:00
}
2025-06-26 05:20:12 +02:00
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 ) {
2025-07-03 17:32:22 +02:00
console . error ( "No mappings for preset_type_select, something went wrong" )
return undefined
}
2025-06-26 05:20:12 +02:00
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 ) {
2025-06-26 05:20:12 +02:00
return new SvelteUIElement ( AllTagsPanel , { tags , layer } )
}
}
2025-07-28 01:00:07 +02:00
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 } ) ,
) )
}
}
2025-06-26 05:20:12 +02:00
export class DataVisualisations {
public static initList ( ) : SpecialVisualization [ ] {
return [
new HistogramViz ( ) ,
new StatisticsVis ( ) ,
new DirectionAbsolute ( ) ,
new DirectionIndicatorVis ( ) ,
new OpeningHoursTableVis ( ) ,
new OpeningHoursState ( ) ,
2025-07-28 01:00:07 +02:00
new PointsInTimeVis ( ) ,
2025-06-26 05:20:12 +02:00
new Canonical ( ) ,
new LanguageElement ( ) ,
new PresetDescription ( ) ,
new PresetTypeSelect ( ) ,
new AllTagsVis ( ) ,
]
}
}