import * as known_themes from "../assets/generated/known_layers_and_themes.json" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import BaseUIElement from "../UI/BaseUIElement" import Combine from "../UI/Base/Combine" import Title from "../UI/Base/Title" import List from "../UI/Base/List" import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator" import Constants from "../Models/Constants" import { Utils } from "../Utils" import Link from "../UI/Base/Link" import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson" import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" export class AllKnownLayouts { public static allKnownLayouts: Map = AllKnownLayouts.AllLayouts() public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList( AllKnownLayouts.allKnownLayouts ) // Must be below the list... private static sharedLayers: Map = AllKnownLayouts.getSharedLayers() public static AllPublicLayers(options?: { includeInlineLayers: true | boolean }): LayerConfig[] { const allLayers: LayerConfig[] = [] const seendIds = new Set() AllKnownLayouts.sharedLayers.forEach((layer, key) => { seendIds.add(key) allLayers.push(layer) }) if (options?.includeInlineLayers ?? true) { const publicLayouts = AllKnownLayouts.layoutsList.filter((l) => !l.hideFromOverview) for (const layout of publicLayouts) { if (layout.hideFromOverview) { continue } for (const layer of layout.layers) { if (seendIds.has(layer.id)) { continue } seendIds.add(layer.id) allLayers.push(layer) } } } return allLayers } /** * Returns all themes which use the given layer, reverse sorted by minzoom. This sort maximizes the chances that the layer is prominently featured on the first theme */ public static themesUsingLayer(id: string, publicOnly = true): LayoutConfig[] { const themes = AllKnownLayouts.layoutsList .filter((l) => !(publicOnly && l.hideFromOverview) && l.id !== "personal") .map((theme) => ({ theme, minzoom: theme.layers.find((layer) => layer.id === id)?.minzoom, })) .filter((obj) => obj.minzoom !== undefined) themes.sort((th0, th1) => th1.minzoom - th0.minzoom) return themes.map((th) => th.theme) } /** * Generates documentation for the layers. * Inline layers are included (if the theme is public) * @param callback * @constructor */ public static GenOverviewsForSingleLayer( callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void ): void { const allLayers: LayerConfig[] = Array.from(AllKnownLayouts.sharedLayers.values()).filter( (layer) => Constants.priviliged_layers.indexOf(layer.id) < 0 ) const builtinLayerIds: Set = new Set() allLayers.forEach((l) => builtinLayerIds.add(l.id)) const inlineLayers = new Map() for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) { if (layout.hideFromOverview) { continue } for (const layer of layout.layers) { if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { continue } if (builtinLayerIds.has(layer.id)) { continue } if (layer.source.geojsonSource !== undefined) { // Not an OSM-source continue } allLayers.push(layer) builtinLayerIds.add(layer.id) inlineLayers.set(layer.id, layout.id) } } const themesPerLayer = new Map() for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) { if (layout.hideFromOverview) { continue } for (const layer of layout.layers) { if (!builtinLayerIds.has(layer.id)) { // This is an inline layer continue } if (!themesPerLayer.has(layer.id)) { themesPerLayer.set(layer.id, []) } themesPerLayer.get(layer.id).push(layout.id) } } // Determine the cross-dependencies const layerIsNeededBy: Map = new Map() for (const layer of allLayers) { for (const dep of DependencyCalculator.getLayerDependencies(layer)) { const dependency = dep.neededLayer if (!layerIsNeededBy.has(dependency)) { layerIsNeededBy.set(dependency, []) } layerIsNeededBy.get(dependency).push(layer.id) } } allLayers.forEach((layer) => { const element = layer.GenerateDocumentation( themesPerLayer.get(layer.id), layerIsNeededBy, DependencyCalculator.getLayerDependencies(layer) ) callback(layer, element, inlineLayers.get(layer.id)) }) } /** * Generates the documentation for the layers overview page * @constructor */ public static GenLayerOverviewText(): BaseUIElement { for (const id of Constants.priviliged_layers) { if (!AllKnownLayouts.sharedLayers.has(id)) { throw "Priviliged layer definition not found: " + id } } const allLayers: LayerConfig[] = Array.from(AllKnownLayouts.sharedLayers.values()).filter( (layer) => Constants.priviliged_layers.indexOf(layer.id) < 0 ) const builtinLayerIds: Set = new Set() allLayers.forEach((l) => builtinLayerIds.add(l.id)) const themesPerLayer = new Map() for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) { for (const layer of layout.layers) { if (!builtinLayerIds.has(layer.id)) { continue } if (!themesPerLayer.has(layer.id)) { themesPerLayer.set(layer.id, []) } themesPerLayer.get(layer.id).push(layout.id) } } // Determine the cross-dependencies const layerIsNeededBy: Map = new Map() for (const layer of allLayers) { for (const dep of DependencyCalculator.getLayerDependencies(layer)) { const dependency = dep.neededLayer if (!layerIsNeededBy.has(dependency)) { layerIsNeededBy.set(dependency, []) } layerIsNeededBy.get(dependency).push(layer.id) } } return new Combine([ new Title("Special and other useful layers", 1), "MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.", new Title("Priviliged layers", 1), new List(Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")")), ...Constants.priviliged_layers .map((id) => AllKnownLayouts.sharedLayers.get(id)) .map((l) => l.GenerateDocumentation( themesPerLayer.get(l.id), layerIsNeededBy, DependencyCalculator.getLayerDependencies(l), Constants.added_by_default.indexOf(l.id) >= 0, Constants.no_include.indexOf(l.id) < 0 ) ), new Title("Normal layers", 1), "The following layers are included in MapComplete:", new List( Array.from(AllKnownLayouts.sharedLayers.keys()).map( (id) => new Link(id, "./Layers/" + id + ".md") ) ), ]) } public static GenerateDocumentationForTheme(theme: LayoutConfig): BaseUIElement { return new Combine([ new Title(new Combine([theme.title, "(", theme.id + ")"]), 2), theme.description, "This theme contains the following layers:", new List( theme.layers .filter((l) => !l.id.startsWith("note_import_")) .map((l) => new Link(l.id, "../Layers/" + l.id + ".md")) ), "Available languages:", new List(theme.language.filter((ln) => ln !== "_context")), ]).SetClass("flex flex-col") } public static getSharedLayers(): Map { const sharedLayers = new Map() for (const layer of known_themes["layers"]) { try { // @ts-ignore const parsed = new LayerConfig(layer, "shared_layers") sharedLayers.set(layer.id, parsed) } catch (e) { if (!Utils.runningFromConsole) { console.error( "CRITICAL: Could not parse a layer configuration!", layer.id, " due to", e ) } } } return sharedLayers } public static getSharedLayersConfigs(): Map { const sharedLayers = new Map() for (const layer of known_themes["layers"]) { // @ts-ignore sharedLayers.set(layer.id, layer) } return sharedLayers } private static GenerateOrderedList(allKnownLayouts: Map): LayoutConfig[] { const list = [] allKnownLayouts.forEach((layout) => { list.push(layout) }) return list } private static AllLayouts(): Map { const dict: Map = new Map() for (const layoutConfigJson of known_themes["themes"]) { const layout = new LayoutConfig(layoutConfigJson, true) dict.set(layout.id, layout) for (let i = 0; i < layout.layers.length; i++) { let layer = layout.layers[i] if (typeof layer === "string") { layer = AllKnownLayouts.sharedLayers.get(layer) layout.layers[i] = layer if (layer === undefined) { console.log("Defined layers are ", AllKnownLayouts.sharedLayers.keys()) throw `Layer ${layer} was not found or defined - probably a type was made` } } } } return dict } }