2021-07-23 15:56:22 +02:00
import { Translation } from "../../UI/i18n/Translation"
2021-03-20 23:45:52 +01:00
import SourceConfig from "./SourceConfig"
2021-08-07 23:11:34 +02:00
import TagRenderingConfig from "./TagRenderingConfig"
2021-12-04 21:49:17 +01:00
import PresetConfig , { PreciseInput } from "./PresetConfig"
2021-08-07 23:11:34 +02:00
import { LayerConfigJson } from "./Json/LayerConfigJson"
import Translations from "../../UI/i18n/Translations"
import { TagUtils } from "../../Logic/Tags/TagUtils"
2021-07-22 11:29:09 +02:00
import FilterConfig from "./FilterConfig"
2021-08-07 23:11:34 +02:00
import { Unit } from "../Unit"
import DeleteConfig from "./DeleteConfig"
2021-10-14 03:46:09 +02:00
import MoveConfig from "./MoveConfig"
2021-10-19 02:31:32 +02:00
import PointRenderingConfig from "./PointRenderingConfig"
import WithContextLoader from "./WithContextLoader"
2021-10-20 02:01:27 +02:00
import LineRenderingConfig from "./LineRenderingConfig"
import PointRenderingConfigJson from "./Json/PointRenderingConfigJson"
import LineRenderingConfigJson from "./Json/LineRenderingConfigJson"
2021-10-22 18:53:07 +02:00
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
2021-10-31 02:08:39 +01:00
import BaseUIElement from "../../UI/BaseUIElement"
2021-11-08 02:36:01 +01:00
import Combine from "../../UI/Base/Combine"
import Title from "../../UI/Base/Title"
import List from "../../UI/Base/List"
import Link from "../../UI/Base/Link"
2021-11-08 03:00:58 +01:00
import { Utils } from "../../Utils"
2022-01-14 19:34:00 +01:00
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import Table from "../../UI/Base/Table"
2022-02-01 04:14:54 +01:00
import FilterConfigJson from "./Json/FilterConfigJson"
2022-03-24 19:59:46 +01:00
import { And } from "../../Logic/Tags/And"
import { Overpass } from "../../Logic/Osm/Overpass"
2022-04-30 00:28:51 +02:00
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
2022-07-16 03:57:13 +02:00
import Svg from "../../Svg"
2023-03-24 19:21:15 +01:00
import { ImmutableStore } from "../../Logic/UIEventSource"
2022-07-16 03:57:13 +02:00
import { OsmTags } from "../OsmFeature"
2023-04-02 02:59:20 +02:00
import Constants from "../Constants"
2021-04-10 23:53:13 +02:00
2021-10-22 18:53:07 +02:00
export default class LayerConfig extends WithContextLoader {
2022-07-06 17:11:17 +02:00
public static readonly syncSelectionAllowed = [ "no" , "local" , "theme-only" , "global" ] as const
2021-12-04 21:49:17 +01:00
public readonly id : string
public readonly name : Translation
public readonly description : Translation
2023-03-24 19:21:15 +01:00
/ * *
* Only 'null' for special , privileged layers
* /
public readonly source : SourceConfig | null
2021-12-12 02:59:24 +01:00
public readonly calculatedTags : [ string , string , boolean ] [ ]
2021-12-04 21:49:17 +01:00
public readonly doNotDownload : boolean
2022-01-14 19:34:00 +01:00
public readonly passAllFeatures : boolean
2022-07-18 02:00:32 +02:00
public readonly isShown : TagsFilter
2021-12-12 17:21:32 +01:00
public minzoom : number
public minzoomVisible : number
2022-01-14 19:34:00 +01:00
public readonly maxzoom : number
2021-12-04 21:49:17 +01:00
public readonly title? : TagRenderingConfig
public readonly titleIcons : TagRenderingConfig [ ]
2021-10-19 02:31:32 +02:00
public readonly mapRendering : PointRenderingConfig [ ]
2021-10-20 02:01:27 +02:00
public readonly lineRendering : LineRenderingConfig [ ]
2021-07-23 15:56:22 +02:00
public readonly units : Unit [ ]
public readonly deletion : DeleteConfig | null
2021-10-14 03:46:09 +02:00
public readonly allowMove : MoveConfig | null
2021-07-15 20:47:28 +02:00
public readonly allowSplit : boolean
2021-12-03 02:29:25 +01:00
public readonly shownByDefault : boolean
2021-10-25 20:38:57 +02:00
/ * *
* In seconds
* /
public readonly maxAgeOfCache : number
2021-12-04 21:49:17 +01:00
public readonly presets : PresetConfig [ ]
public readonly tagRenderings : TagRenderingConfig [ ]
public readonly filters : FilterConfig [ ]
2022-02-01 04:14:54 +01:00
public readonly filterIsSameAs : string
2022-02-07 01:59:07 +01:00
public readonly forceLoad : boolean
2023-06-14 20:44:01 +02:00
public readonly syncSelection : ( typeof LayerConfig . syncSelectionAllowed ) [ number ] // this is a trick to conver a constant array of strings into a type union of these values
2022-09-08 21:40:48 +02:00
2023-06-01 02:52:21 +02:00
public readonly _needsFullNodeDatabase : boolean
2023-05-05 01:00:15 +02:00
public readonly popupInFloatover
2023-03-26 05:58:28 +02:00
2021-07-23 15:56:22 +02:00
constructor ( json : LayerConfigJson , context? : string , official : boolean = true ) {
context = context + "." + json . id
2022-07-06 17:11:17 +02:00
const translationContext = "layers:" + json . id
2021-10-19 02:31:32 +02:00
super ( json , context )
2021-07-23 15:56:22 +02:00
this . id = json . id
2022-01-26 21:40:38 +01:00
2022-07-06 17:11:17 +02:00
if ( typeof json === "string" ) {
throw ` Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed (at ${ context } ) `
}
2022-01-26 21:40:38 +01:00
if ( json . id === undefined ) {
2022-07-06 17:11:17 +02:00
throw ` Not a valid layer: id is undefined: ${ JSON . stringify ( json ) } (At ${ context } ) `
2022-01-17 21:33:03 +01:00
}
2021-10-29 01:41:37 +02:00
if ( json . source === undefined ) {
2021-10-31 02:08:39 +01:00
throw "Layer " + this . id + " does not define a source section (" + context + ")"
2021-03-20 23:45:52 +01:00
}
2021-10-25 20:38:57 +02:00
2023-03-25 02:48:24 +01:00
if ( json . source === "special" || json . source === "special:library" ) {
2023-03-24 19:21:15 +01:00
this . source = null
2023-04-14 02:42:57 +02:00
} else if ( json . source [ "osmTags" ] === undefined ) {
2021-10-31 02:08:39 +01:00
throw (
"Layer " +
this . id +
" does not define a osmTags in the source section - these should always be present, even for geojson layers (" +
context +
")"
2022-09-08 21:40:48 +02:00
)
2021-12-30 22:36:34 +01:00
}
2021-07-23 15:56:22 +02:00
2022-01-14 19:34:00 +01:00
if ( json . id . toLowerCase ( ) !== json . id ) {
2021-12-30 22:36:34 +01:00
throw ` ${ context } : The id of a layer should be lowercase: ${ json . id } `
}
2022-01-14 19:34:00 +01:00
if ( json . id . match ( /[a-z0-9-_]/ ) == null ) {
2021-12-30 22:36:34 +01:00
throw ` ${ context } : The id of a layer should match [a-z0-9-_]*: ${ json . id } `
2021-10-29 01:41:37 +02:00
}
2021-07-23 15:56:22 +02:00
2022-07-06 17:11:17 +02:00
if (
json . syncSelection !== undefined &&
LayerConfig . syncSelectionAllowed . indexOf ( json . syncSelection ) < 0
) {
throw (
context +
" Invalid sync-selection: must be one of " +
LayerConfig . syncSelectionAllowed . map ( ( v ) = > ` ' ${ v } ' ` ) . join ( ", " ) +
" but got '" +
json . syncSelection +
"'"
2022-09-08 21:40:48 +02:00
)
2022-06-13 03:13:42 +02:00
}
this . syncSelection = json . syncSelection ? ? "no"
2023-03-25 02:48:24 +01:00
if ( typeof json . source !== "string" ) {
2023-04-14 02:42:57 +02:00
this . maxAgeOfCache = json . source [ "maxCacheAge" ] ? ? 24 * 60 * 60 * 30
const osmTags = TagUtils . Tag ( json . source [ "osmTags" ] , context + "source.osmTags" )
2023-03-25 02:48:24 +01:00
if ( osmTags . isNegative ( ) ) {
throw (
context +
"The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" +
osmTags . asHumanString ( false , false , { } )
)
}
2021-09-18 02:32:40 +02:00
2023-03-25 02:48:24 +01:00
this . source = new SourceConfig (
{
osmTags : osmTags ,
geojsonSource : json.source [ "geoJson" ] ,
geojsonSourceLevel : json.source [ "geoJsonZoomLevel" ] ,
overpassScript : json.source [ "overpassScript" ] ,
isOsmCache : json.source [ "isOsmCache" ] ,
mercatorCrs : json.source [ "mercatorCrs" ] ,
idKey : json.source [ "idKey" ] ,
} ,
json . id
2022-09-08 21:40:48 +02:00
)
2022-03-31 02:44:23 +02:00
}
2022-07-06 17:11:17 +02:00
2021-10-29 01:41:37 +02:00
if ( json . source [ "geoJsonSource" ] !== undefined ) {
throw context + "Use 'geoJson' instead of 'geoJsonSource'"
}
if ( json . source [ "geojson" ] !== undefined ) {
throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)"
2021-03-24 01:25:57 +01:00
}
2022-07-06 17:11:17 +02:00
2021-10-19 02:31:32 +02:00
this . allowSplit = json . allowSplit ? ? false
2022-04-01 12:51:55 +02:00
this . name = Translations . T ( json . name , translationContext + ".name" )
2022-07-06 17:11:17 +02:00
if ( json . units !== undefined && ! Array . isArray ( json . units ) ) {
throw (
"At " +
context +
".units: the 'units'-section should be a list; you probably have an object there"
2022-09-08 21:40:48 +02:00
)
2022-04-22 03:17:40 +02:00
}
2021-10-19 02:31:32 +02:00
this . units = ( json . units ? ? [ ] ) . map ( ( unitJson , i ) = >
Unit . fromJson ( unitJson , ` ${ context } .unit[ ${ i } ] ` )
2022-09-08 21:40:48 +02:00
)
2021-10-19 02:31:32 +02:00
if ( json . description !== undefined ) {
if ( Object . keys ( json . description ) . length === 0 ) {
json . description = undefined
}
}
2022-04-01 12:51:55 +02:00
this . description = Translations . T ( json . description , translationContext + ".description" )
2021-10-19 02:31:32 +02:00
2021-07-23 15:56:22 +02:00
this . calculatedTags = undefined
if ( json . calculatedTags !== undefined ) {
if ( ! official ) {
console . warn (
` Unofficial theme ${ this . id } with custom javascript! This is a security risk `
)
2021-01-08 03:57:18 +01:00
}
2021-07-23 15:56:22 +02:00
this . calculatedTags = [ ]
for ( const kv of json . calculatedTags ) {
const index = kv . indexOf ( "=" )
2022-04-23 15:20:54 +02:00
let key = kv . substring ( 0 , index ) . trim ( )
const r = "[a-z_][a-z0-9:]*"
2022-07-06 17:11:17 +02:00
if ( key . match ( r ) === null ) {
throw (
"At " +
context +
" invalid key for calculated tag: " +
key +
"; it should match " +
r
2022-09-08 21:40:48 +02:00
)
2022-04-23 15:20:54 +02:00
}
2021-12-12 02:59:24 +01:00
const isStrict = key . endsWith ( ":" )
2022-01-14 19:34:00 +01:00
if ( isStrict ) {
2021-12-12 02:59:24 +01:00
key = key . substr ( 0 , key . length - 1 )
}
2021-07-23 15:56:22 +02:00
const code = kv . substring ( index + 1 )
2021-01-08 03:57:18 +01:00
2021-10-14 03:46:09 +02:00
try {
new Function ( "feat" , "return " + code + ";" )
} catch ( e ) {
2022-04-30 22:58:36 +02:00
throw ` Invalid function definition: the custom javascript is invalid: ${ e } (at ${ context } ). The offending javascript code is: \ n ${ code } `
2021-09-18 02:32:40 +02:00
}
2021-12-12 02:59:24 +01:00
this . calculatedTags . push ( [ key , code , isStrict ] )
2020-10-27 01:01:34 +01:00
}
2021-07-23 15:56:22 +02:00
}
2021-01-08 03:57:18 +01:00
2021-07-23 15:56:22 +02:00
this . doNotDownload = json . doNotDownload ? ? false
this . passAllFeatures = json . passAllFeatures ? ? false
this . minzoom = json . minzoom ? ? 0
2023-06-01 02:52:21 +02:00
this . _needsFullNodeDatabase = json . fullNodeDatabase ? ? false
2022-07-16 03:57:13 +02:00
if ( json [ "minZoom" ] !== undefined ) {
throw "At " + context + ": minzoom is written all lowercase"
2022-07-12 10:23:45 +02:00
}
2021-07-27 19:39:57 +02:00
this . minzoomVisible = json . minzoomVisible ? ? this . minzoom
2021-12-03 02:29:25 +01:00
this . shownByDefault = json . shownByDefault ? ? true
2022-02-07 01:59:07 +01:00
this . forceLoad = json . forceLoad ? ? false
2022-09-12 10:32:19 +02:00
if ( json . presets === null ) json . presets = undefined
2021-10-14 03:46:09 +02:00
if ( json . presets !== undefined && json . presets ? . map === undefined ) {
throw "Presets should be a list of items (at " + context + ")"
2021-09-22 20:44:53 +02:00
}
2021-07-14 00:17:15 +02:00
this . presets = ( json . presets ? ? [ ] ) . map ( ( pr , i ) = > {
2021-12-04 21:49:17 +01:00
let preciseInput : PreciseInput = {
2021-10-16 00:43:53 +02:00
preferredBackground : [ "photo" ] ,
snapToLayers : undefined ,
maxSnapDistance : undefined ,
2021-10-15 19:58:02 +02:00
}
2023-06-20 01:52:15 +02:00
if ( pr [ "preciseInput" ] !== undefined ) {
throw "Layer " + this . id + " still uses the old 'preciseInput'-field"
}
if ( pr . snapToLayer !== undefined ) {
2021-08-07 21:19:01 +02:00
let snapToLayers : string [ ]
2023-06-20 01:52:15 +02:00
if ( typeof pr . snapToLayer === "string" ) {
snapToLayers = [ pr . snapToLayer ]
2021-08-07 21:19:01 +02:00
} else {
2023-06-20 01:52:15 +02:00
snapToLayers = pr . snapToLayer
2021-08-07 21:19:01 +02:00
}
2021-08-07 23:11:34 +02:00
2021-08-07 21:19:01 +02:00
preciseInput = {
2021-12-04 21:49:17 +01:00
snapToLayers ,
2023-06-20 01:52:15 +02:00
maxSnapDistance : pr.maxSnapDistance ? ? 10 ,
2021-07-14 00:17:15 +02:00
}
2023-06-20 01:52:15 +02:00
} else if ( pr . maxSnapDistance !== undefined ) {
throw (
"Layer " +
this . id +
" defines a maxSnapDistance, but does not include a `snapToLayer`"
)
2021-07-14 00:17:15 +02:00
}
2021-08-07 23:11:34 +02:00
const config : PresetConfig = {
2022-04-01 12:51:55 +02:00
title : Translations.T ( pr . title , ` ${ translationContext } .presets. ${ i } .title ` ) ,
2021-08-07 23:11:34 +02:00
tags : pr.tags.map ( ( t ) = > TagUtils . SimpleTag ( t ) ) ,
2022-04-01 12:51:55 +02:00
description : Translations.T (
pr . description ,
` ${ translationContext } .presets. ${ i } .description `
) ,
2021-08-07 21:19:01 +02:00
preciseInput : preciseInput ,
2022-02-09 03:38:40 +01:00
exampleImages : pr.exampleImages ,
2021-07-24 02:32:33 +02:00
}
2021-08-07 21:19:01 +02:00
return config
2021-07-24 02:32:33 +02:00
} )
2021-07-23 15:56:22 +02:00
2021-10-22 18:53:07 +02:00
if ( json . mapRendering === undefined ) {
throw "MapRendering is undefined in " + context
2021-10-21 01:26:20 +02:00
}
2021-09-09 20:26:12 +02:00
2021-11-09 18:22:05 +01:00
if ( json . mapRendering === null ) {
this . mapRendering = [ ]
this . lineRendering = [ ]
} else {
2021-11-09 19:45:26 +01:00
this . mapRendering = Utils . NoNull ( json . mapRendering )
2021-11-09 18:22:05 +01:00
. filter ( ( r ) = > r [ "location" ] !== undefined )
. map (
( r , i ) = >
new PointRenderingConfig (
< PointRenderingConfigJson > r ,
context + ".mapRendering[" + i + "]"
)
2022-09-08 21:40:48 +02:00
)
2021-10-20 02:01:27 +02:00
2021-11-09 19:45:26 +01:00
this . lineRendering = Utils . NoNull ( json . mapRendering )
2021-11-09 18:22:05 +01:00
. filter ( ( r ) = > r [ "location" ] === undefined )
. map (
( r , i ) = >
new LineRenderingConfig (
< LineRenderingConfigJson > r ,
context + ".mapRendering[" + i + "]"
)
2022-09-08 21:40:48 +02:00
)
2021-11-09 18:22:05 +01:00
const hasCenterRendering = this . mapRendering . some (
( r ) = >
2022-12-16 13:45:07 +01:00
r . location . has ( "centroid" ) ||
r . location . has ( "projected_centerpoint" ) ||
r . location . has ( "start" ) ||
r . location . has ( "end" )
2022-09-08 21:40:48 +02:00
)
2021-11-09 18:22:05 +01:00
if ( this . lineRendering . length === 0 && this . mapRendering . length === 0 ) {
throw (
"The layer " +
this . id +
" does not have any maprenderings defined and will thus not show up on the map at all. If this is intentional, set maprenderings to 'null' instead of '[]'"
)
2022-01-21 03:57:49 +01:00
} else if (
! hasCenterRendering &&
this . lineRendering . length === 0 &&
2023-04-02 02:59:20 +02:00
Constants . priviliged_layers . indexOf ( < any > this . id ) < 0 &&
! this . source ? . geojsonSource ? . startsWith (
2022-01-21 03:57:49 +01:00
"https://api.openstreetmap.org/api/0.6/notes.json"
2022-09-08 21:40:48 +02:00
)
2022-01-21 03:57:49 +01:00
) {
2022-03-24 19:59:46 +01:00
throw (
"The layer " +
this . id +
" might not render ways. This might result in dropped information (at " +
context +
")"
2022-09-08 21:40:48 +02:00
)
2021-11-09 18:22:05 +01:00
}
}
2021-10-20 02:01:27 +02:00
2022-01-18 18:12:24 +01:00
const missingIds =
Utils . NoNull ( json . tagRenderings ) ? . filter (
( tr ) = >
typeof tr !== "string" &&
tr [ "builtin" ] === undefined &&
tr [ "id" ] === undefined &&
tr [ "rewrite" ] === undefined
) ? ? [ ]
2021-11-08 03:00:58 +01:00
if ( missingIds ? . length > 0 && official ) {
2021-10-14 03:46:09 +02:00
console . error ( "Some tagRenderings of" , this . id , "are missing an id:" , missingIds )
throw "Missing ids in tagrenderings"
}
2022-01-18 18:12:24 +01:00
this . tagRenderings = ( Utils . NoNull ( json . tagRenderings ) ? ? [ ] ) . map (
( tr , i ) = >
new TagRenderingConfig (
< TagRenderingConfigJson > tr ,
this . id + ".tagRenderings[" + i + "]"
)
2022-09-08 21:40:48 +02:00
)
2021-11-08 03:00:58 +01:00
2022-03-24 19:59:46 +01:00
if (
json . filter !== undefined &&
json . filter !== null &&
json . filter [ "sameAs" ] !== undefined
) {
2022-02-01 04:14:54 +01:00
this . filterIsSameAs = json . filter [ "sameAs" ]
this . filters = [ ]
2022-03-24 19:59:46 +01:00
} else {
2022-02-01 04:14:54 +01:00
this . filters = ( < FilterConfigJson [ ] > json . filter ? ? [ ] ) . map ( ( option , i ) = > {
2022-04-01 12:51:55 +02:00
return new FilterConfig ( option , ` layers: ${ this . id } .filter. ${ i } ` )
2022-02-01 04:14:54 +01:00
} )
}
2021-10-14 03:46:09 +02:00
2021-11-08 03:00:58 +01:00
{
2021-12-21 18:35:31 +01:00
const duplicateIds = Utils . Dupiclates ( this . filters . map ( ( f ) = > f . id ) )
2021-11-08 03:00:58 +01:00
if ( duplicateIds . length > 0 ) {
throw ` Some filters have a duplicate id: ${ duplicateIds } (at ${ context } .filters) `
}
}
2021-10-14 03:46:09 +02:00
if ( json [ "filters" ] !== undefined ) {
throw "Error in " + context + ": use 'filter' instead of 'filters'"
}
2021-07-23 15:56:22 +02:00
2022-09-27 18:53:53 +02:00
this . titleIcons = this . ParseTagRenderings ( < TagRenderingConfigJson [ ] > json . titleIcons ? ? [ ] , {
2021-11-11 17:14:03 +01:00
readOnlyMode : true ,
2022-09-08 21:40:48 +02:00
} )
2020-11-16 01:59:30 +01:00
2023-05-30 23:45:30 +02:00
this . title = this . tr ( "title" , undefined , translationContext )
2022-07-18 02:00:32 +02:00
this . isShown = TagUtils . TagD ( json . isShown , context + ".isShown" )
2021-10-22 18:53:07 +02:00
2021-07-23 15:56:22 +02:00
this . deletion = null
if ( json . deletion === true ) {
json . deletion = { }
}
if ( json . deletion !== undefined && json . deletion !== false ) {
this . deletion = new DeleteConfig ( json . deletion , ` ${ context } .deletion ` )
}
2021-03-15 16:23:04 +01:00
2021-10-14 03:46:09 +02:00
this . allowMove = null
if ( json . allowMove === false ) {
this . allowMove = null
} else if ( json . allowMove === true ) {
this . allowMove = new MoveConfig ( { } , context + ".allowMove" )
2022-12-23 16:39:01 +01:00
} else if ( json . allowMove !== undefined ) {
2021-10-14 03:46:09 +02:00
this . allowMove = new MoveConfig ( json . allowMove , context + ".allowMove" )
}
2021-07-23 15:56:22 +02:00
if ( json [ "showIf" ] !== undefined ) {
throw (
"Invalid key on layerconfig " +
this . id +
": showIf. Did you mean 'isShown' instead?"
)
}
2023-05-05 01:00:15 +02:00
this . popupInFloatover = json . popupInFloatover ? ? false
2021-01-08 03:57:18 +01:00
}
2021-11-07 16:34:51 +01:00
public defaultIcon ( ) : BaseUIElement | undefined {
2021-11-11 17:14:03 +01:00
if ( this . mapRendering === undefined || this . mapRendering === null ) {
2021-11-09 19:45:26 +01:00
return undefined
}
2021-10-31 02:08:39 +01:00
const mapRendering = this . mapRendering . filter ( ( r ) = > r . location . has ( "point" ) ) [ 0 ]
if ( mapRendering === undefined ) {
return undefined
}
2023-04-19 03:20:49 +02:00
return mapRendering . GetBaseIcon ( this . GetBaseTags ( ) , { noFullWidth : true } )
2022-02-09 22:37:21 +01:00
}
2022-03-24 19:59:46 +01:00
2023-04-02 02:59:20 +02:00
public GetBaseTags ( ) : Record < string , string > {
return TagUtils . changeAsProperties (
this . source ? . osmTags ? . asChange ( { id : "node/-1" } ) ? ? [ { k : "id" , v : "node/-1" } ]
)
2021-10-31 02:08:39 +01:00
}
2022-04-28 00:32:43 +02:00
public GenerateDocumentation (
usedInThemes : string [ ] ,
layerIsNeededBy? : Map < string , string [ ] > ,
dependencies : {
2022-07-06 17:11:17 +02:00
context? : string
reason : string
neededLayer : string
} [ ] = [ ] ,
addedByDefault = false ,
canBeIncluded = true
) : BaseUIElement {
2022-07-16 03:57:13 +02:00
const extraProps : ( string | BaseUIElement ) [ ] = [ ]
2022-07-06 17:11:17 +02:00
extraProps . push ( "This layer is shown at zoomlevel **" + this . minzoom + "** and higher" )
2021-11-08 02:36:01 +01:00
if ( canBeIncluded ) {
2021-11-09 18:22:05 +01:00
if ( addedByDefault ) {
extraProps . push (
"**This layer is included automatically in every theme. This layer might contain no points**"
)
2021-11-08 14:18:45 +01:00
}
2022-01-14 19:34:00 +01:00
if ( this . shownByDefault === false ) {
2021-12-07 18:18:24 +01:00
extraProps . push (
"This layer is not visible by default and must be enabled in the filter by the user. "
)
}
2021-11-08 02:36:01 +01:00
if ( this . title === undefined ) {
2022-07-20 14:39:19 +02:00
extraProps . push (
"Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable."
)
2021-12-07 18:18:24 +01:00
}
2022-07-20 14:39:19 +02:00
if ( this . name === undefined && this . shownByDefault === false ) {
2021-12-07 18:18:24 +01:00
extraProps . push (
"This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true"
)
2021-11-08 02:36:01 +01:00
}
if ( this . name === undefined ) {
extraProps . push (
"Not visible in the layer selection by default. If you want to make this layer toggable, override `name`"
)
}
if ( this . mapRendering . length === 0 ) {
extraProps . push (
"Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`"
)
}
2022-01-14 19:34:00 +01:00
2023-04-15 03:15:17 +02:00
if ( this . source ? . geojsonSource !== undefined ) {
2022-07-16 03:57:13 +02:00
extraProps . push (
new Combine ( [
Utils . runningFromConsole
? "<img src='../warning.svg' height='1rem'/>"
: undefined ,
"This layer is loaded from an external source, namely " ,
new FixedUiElement ( this . source . geojsonSource ) . SetClass ( "code" ) ,
] )
2022-09-08 21:40:48 +02:00
)
2022-01-14 19:34:00 +01:00
}
2021-11-08 02:36:01 +01:00
} else {
extraProps . push (
"This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data."
)
}
let usingLayer : BaseUIElement [ ] = [ ]
if ( usedInThemes ? . length > 0 && ! addedByDefault ) {
usingLayer = [
new Title ( "Themes using this layer" , 4 ) ,
new List (
( usedInThemes ? ? [ ] ) . map (
( id ) = > new Link ( id , "https://mapcomplete.osm.be/" + id )
2022-09-08 21:40:48 +02:00
)
) ,
2021-11-08 02:36:01 +01:00
]
}
2021-12-05 02:06:14 +01:00
for ( const dep of dependencies ) {
2022-01-14 19:34:00 +01:00
extraProps . push (
new Combine ( [
"This layer will automatically load " ,
new Link ( dep . neededLayer , "./" + dep . neededLayer + ".md" ) ,
" into the layout as it depends on it: " ,
dep . reason ,
"(" + dep . context + ")" ,
] )
2022-09-08 21:40:48 +02:00
)
2021-12-05 02:06:14 +01:00
}
2022-01-14 19:34:00 +01:00
2022-03-24 19:59:46 +01:00
for ( const revDep of Utils . Dedup ( layerIsNeededBy ? . get ( this . id ) ? ? [ ] ) ) {
2022-01-14 19:34:00 +01:00
extraProps . push (
new Combine ( [
"This layer is needed as dependency for layer" ,
new Link ( revDep , "#" + revDep ) ,
] )
2022-09-08 21:40:48 +02:00
)
2022-01-14 19:34:00 +01:00
}
2023-04-15 03:15:17 +02:00
let neededTags : TagsFilter [ ] = Utils . NoNull ( [ this . source ? . osmTags ] )
if ( this . source ? . osmTags [ "and" ] !== undefined ) {
2022-01-14 19:34:00 +01:00
neededTags = this . source . osmTags [ "and" ]
2021-12-05 02:06:14 +01:00
}
2022-01-14 19:34:00 +01:00
let tableRows = Utils . NoNull (
this . tagRenderings
. map ( ( tr ) = > tr . FreeformValues ( ) )
. map ( ( values ) = > {
if ( values == undefined ) {
return undefined
}
2022-07-16 03:57:13 +02:00
const embedded : ( Link | string ) [ ] = values . values ? . map ( ( v ) = >
Link . OsmWiki ( values . key , v , true ) . SetClass ( "mr-2" )
) ? ? [ "_no preset options defined, or no values in them_" ]
2022-01-14 19:34:00 +01:00
return [
new Combine ( [
new Link (
2022-07-16 03:57:13 +02:00
Utils . runningFromConsole
? "<img src='https://mapcomplete.osm.be/assets/svg/statistics.svg' height='18px'>"
: Svg . statistics_svg ( ) . SetClass ( "w-4 h-4 mr-2" ) ,
"https://taginfo.openstreetmap.org/keys/" + values . key + "#values" ,
true
2022-01-26 21:40:38 +01:00
) ,
Link . OsmWiki ( values . key ) ,
2022-07-16 03:57:13 +02:00
] ) . SetClass ( "flex" ) ,
2022-01-14 19:34:00 +01:00
values . type === undefined
? "Multiple choice"
: new Link ( values . type , "../SpecialInputElements.md#" + values . type ) ,
2022-07-16 03:57:13 +02:00
new Combine ( embedded ) . SetClass ( "flex" ) ,
2022-01-14 19:34:00 +01:00
]
} )
2022-09-08 21:40:48 +02:00
)
2022-01-14 19:34:00 +01:00
let quickOverview : BaseUIElement = undefined
if ( tableRows . length > 0 ) {
quickOverview = new Combine ( [
2022-04-30 00:28:51 +02:00
new FixedUiElement ( "Warning: " ) . SetClass ( "bold" ) ,
"this quick overview is incomplete" ,
2022-07-16 03:57:13 +02:00
new Table (
[ "attribute" , "type" , "values which are supported by this layer" ] ,
tableRows
) . SetClass ( "zebra-table" ) ,
2022-01-14 19:34:00 +01:00
] ) . SetClass ( "flex-col flex" )
2021-12-04 21:49:17 +01:00
}
2022-07-16 03:57:13 +02:00
let iconImg : BaseUIElement = new FixedUiElement ( "" )
if ( Utils . runningFromConsole ) {
const icon = this . mapRendering
. filter ( ( mr ) = > mr . location . has ( "point" ) )
. map ( ( mr ) = > mr . icon ? . render ? . txt )
. find ( ( i ) = > i !== undefined )
// This is for the documentation in a markdown-file, so we have to use raw HTML
if ( icon !== undefined ) {
iconImg = new FixedUiElement (
` <img src='https://mapcomplete.osm.be/ ${ icon } ' height="100px"> `
)
}
} else {
iconImg = this . mapRendering
. filter ( ( mr ) = > mr . location . has ( "point" ) )
. map (
( mr ) = >
2023-03-24 19:21:15 +01:00
mr . RenderIcon ( new ImmutableStore < OsmTags > ( { id : "node/-1" } ) , false , {
includeBadges : false ,
} ) . html
2022-09-08 21:40:48 +02:00
)
2022-07-16 03:57:13 +02:00
. find ( ( i ) = > i !== undefined )
2022-02-09 22:37:21 +01:00
}
2022-09-08 21:40:48 +02:00
2022-03-24 19:59:46 +01:00
let overpassLink : BaseUIElement = undefined
2023-03-25 02:48:24 +01:00
if ( this . source !== undefined ) {
2022-03-24 19:59:46 +01:00
try {
2022-07-06 17:11:17 +02:00
overpassLink = new Link (
"Execute on overpass" ,
Overpass . AsOverpassTurboLink ( < TagsFilter > new And ( neededTags ) . optimize ( ) )
2022-09-08 21:40:48 +02:00
)
2022-03-24 19:59:46 +01:00
} catch ( e ) {
console . error ( "Could not generate overpasslink for " + this . id )
}
}
2022-12-16 13:45:07 +01:00
const filterDocs : ( string | BaseUIElement ) [ ] = [ ]
if ( this . filters . length > 0 ) {
2022-12-06 03:41:54 +01:00
filterDocs . push ( new Title ( "Filters" , 4 ) )
2022-12-16 13:45:07 +01:00
filterDocs . push ( . . . this . filters . map ( ( filter ) = > filter . GenerateDocs ( ) ) )
2022-12-06 03:41:54 +01:00
}
2023-04-20 18:58:31 +02:00
const tagsDescription = [ ]
if ( this . source === null ) {
tagsDescription . push (
new Title ( "Basic tags for this layer" , 2 ) ,
"Elements must have the all of following tags to be shown on this layer:" ,
new List ( neededTags . map ( ( t ) = > t . asHumanString ( true , false , { } ) ) ) ,
overpassLink
)
} else {
tagsDescription . push ( "This is a special layer - data is not sourced from OpenStreetMap" )
}
2021-11-08 02:36:01 +01:00
return new Combine ( [
2022-01-14 19:34:00 +01:00
new Combine ( [ new Title ( this . id , 1 ) , iconImg , this . description , "\n" ] ) . SetClass (
"flex flex-col"
) ,
new List ( extraProps ) ,
. . . usingLayer ,
2023-04-20 18:58:31 +02:00
. . . tagsDescription ,
2022-01-14 19:34:00 +01:00
new Title ( "Supported attributes" , 2 ) ,
quickOverview ,
. . . this . tagRenderings . map ( ( tr ) = > tr . GenerateDocumentation ( ) ) ,
2022-12-16 13:45:07 +01:00
. . . filterDocs ,
2022-07-16 03:57:13 +02:00
] )
. SetClass ( "flex-col" )
. SetClass ( "link-underline" )
2021-11-08 02:36:01 +01:00
}
2021-07-23 15:56:22 +02:00
public CustomCodeSnippets ( ) : string [ ] {
if ( this . calculatedTags === undefined ) {
return [ ]
}
return this . calculatedTags . map ( ( code ) = > code [ 1 ] )
2021-07-22 11:29:09 +02:00
}
2022-01-14 19:34:00 +01:00
AllTagRenderings ( ) : TagRenderingConfig [ ] {
2022-07-18 02:00:32 +02:00
return Utils . NoNull ( [ . . . this . tagRenderings , . . . this . titleIcons , this . title ] )
2022-01-14 19:34:00 +01:00
}
2021-01-08 03:57:18 +01:00
2021-10-22 18:53:07 +02:00
public isLeftRightSensitive ( ) : boolean {
2021-10-22 14:01:40 +02:00
return this . lineRendering . some ( ( lr ) = > lr . leftRightSensitive )
}
2021-08-07 23:11:34 +02:00
}