This commit is contained in:
Pieter Vander Vennet 2024-10-13 21:37:04 +02:00
parent 4794032e49
commit ca17d3da1b
7 changed files with 136 additions and 91 deletions

View file

@ -13,10 +13,13 @@ class DownloadEli extends Script {
const url = "https://osmlab.github.io/editor-layer-index/imagery.geojson" const url = "https://osmlab.github.io/editor-layer-index/imagery.geojson"
// Target should use '.json' instead of '.geojson', as the latter cannot be imported by the build systems // Target should use '.json' instead of '.geojson', as the latter cannot be imported by the build systems
const target = args[0] ?? "public/assets/data/editor-layer-index.json" const target = args[0] ?? "public/assets/data/editor-layer-index.json"
const targetGlobal = args[1] ?? "src/assets/generated/editor-layer-index-global.json"
const targetBing = args[0] ?? "src/assets/bing.json" const targetBing = args[0] ?? "src/assets/bing.json"
const eli: Eli = await Utils.downloadJson(url) const eli: Eli = await Utils.downloadJson(url)
const keptLayers: EliEntry[] = [] const keptLayers: EliEntry[] = []
const keptGlobalLayers: EliEntry[] = []
console.log("Got", eli.features.length, "ELI-entries") console.log("Got", eli.features.length, "ELI-entries")
for (let layer of eli.features) { for (let layer of eli.features) {
const props = layer.properties const props = layer.properties
@ -95,18 +98,26 @@ class DownloadEli extends Script {
} }
layer = { properties: layer.properties, type: layer.type, geometry: layer.geometry } layer = { properties: layer.properties, type: layer.type, geometry: layer.geometry }
if(layer.geometry === null){
keptGlobalLayers.push(layer)
}else{
keptLayers.push(layer) keptLayers.push(layer)
} }
}
const contents = const contents =
'{"type":"FeatureCollection",\n "features": [\n' + '{"type":"FeatureCollection",\n "features": [\n' +
keptLayers keptLayers
.filter((l) => l.properties.id !== "Bing")
.map((l) => JSON.stringify(l)) .map((l) => JSON.stringify(l))
.join(",\n") + .join(",\n") +
"\n]}" "\n]}"
const bing = keptLayers.find((l) => l.properties.id === "Bing") const contentsGlobal =
keptGlobalLayers
.filter((l) => l.properties.id !== "Bing")
.map(l => l.properties)
const bing = keptGlobalLayers.find((l) => l.properties.id === "Bing")
if (bing) { if (bing) {
fs.writeFileSync(targetBing, JSON.stringify(bing), { encoding: "utf8" }) fs.writeFileSync(targetBing, JSON.stringify(bing), { encoding: "utf8" })
console.log("Written", targetBing) console.log("Written", targetBing)
@ -115,6 +126,9 @@ class DownloadEli extends Script {
} }
fs.writeFileSync(target, contents, { encoding: "utf8" }) fs.writeFileSync(target, contents, { encoding: "utf8" })
console.log("Written", keptLayers.length + ", entries to the ELI") console.log("Written", keptLayers.length + ", entries to the ELI")
fs.writeFileSync(targetGlobal, JSON.stringify(contentsGlobal,null, " "), { encoding: "utf8" })
console.log("Written", keptGlobalLayers.length + ", entries to the global ELI")
} }
} }

View file

@ -1,5 +1,7 @@
import { Feature, Polygon } from "geojson" import { Feature, Polygon } from "geojson"
import * as globallayers from "../assets/global-raster-layers.json" import * as globallayers from "../assets/global-raster-layers.json"
import * as globallayersEli from "../assets/generated/editor-layer-index-global.json"
import * as bingJson from "../assets/bing.json" import * as bingJson from "../assets/bing.json"
import { BBox } from "../Logic/BBox" import { BBox } from "../Logic/BBox"
@ -21,26 +23,37 @@ export class AvailableRasterLayers {
} }
console.debug("Downloading ELI") console.debug("Downloading ELI")
const eli = await Utils.downloadJson<{ features: EditorLayerIndex }>( const eli = await Utils.downloadJson<{ features: EditorLayerIndex }>(
"./assets/data/editor-layer-index.json" "./assets/data/editor-layer-index.json",
) )
this._editorLayerIndex = eli.features?.filter((l) => l.properties.id !== "Bing") ?? [] this._editorLayerIndex = eli.features?.filter((l) => l.properties.id !== "Bing") ?? []
this._editorLayerIndexStore.set(this._editorLayerIndex) this._editorLayerIndexStore.set(this._editorLayerIndex)
return this._editorLayerIndex return this._editorLayerIndex
} }
public static globalLayers: ReadonlyArray<RasterLayerPolygon> = globallayers.layers public static readonly globalLayers: ReadonlyArray<RasterLayerPolygon> = AvailableRasterLayers.initGlobalLayers()
private static initGlobalLayers(): RasterLayerPolygon[] {
const gl: RasterLayerProperties[] = (globallayers["default"] ?? globallayers ).layers
.filter( .filter(
(properties) => (properties) =>
properties.id !== "osm.carto" && properties.id !== "Bing" /*Added separately*/ properties.id !== "osm.carto" && properties.id !== "Bing", /*Added separately*/
) )
.map( const glEli: RasterLayerProperties[] = globallayersEli["default"] ?? globallayersEli
const joined = gl.concat(glEli)
if (joined.some(j => !j.id)) {
console.log("Invalid layers:", JSON.stringify(joined .filter(l => !l.id)))
throw "Detected invalid global layer with invalid id"
}
return joined.map(
(properties) => (properties) =>
<RasterLayerPolygon>{ <RasterLayerPolygon>{
type: "Feature", type: "Feature",
properties, properties,
geometry: BBox.global.asGeometry(), geometry: BBox.global.asGeometry(),
} },
) )
}
public static bing = <RasterLayerPolygon>bingJson public static bing = <RasterLayerPolygon>bingJson
public static readonly osmCartoProperties: RasterLayerProperties = { public static readonly osmCartoProperties: RasterLayerProperties = {
id: "osm", id: "osm",
@ -72,18 +85,18 @@ export class AvailableRasterLayers {
public static layersAvailableAt( public static layersAvailableAt(
location: Store<{ lon: number; lat: number }>, location: Store<{ lon: number; lat: number }>,
enableBing?: Store<boolean> enableBing?: Store<boolean>,
): { store: Store<RasterLayerPolygon[]> } { ): { store: Store<RasterLayerPolygon[]> } {
const store = { store: undefined } const store = { store: undefined }
Utils.AddLazyProperty(store, "store", () => Utils.AddLazyProperty(store, "store", () =>
AvailableRasterLayers._layersAvailableAt(location, enableBing) AvailableRasterLayers._layersAvailableAt(location, enableBing),
) )
return store return store
} }
private static _layersAvailableAt( private static _layersAvailableAt(
location: Store<{ lon: number; lat: number }>, location: Store<{ lon: number; lat: number }>,
enableBing?: Store<boolean> enableBing?: Store<boolean>,
): Store<RasterLayerPolygon[]> { ): Store<RasterLayerPolygon[]> {
this.editorLayerIndex() // start the download this.editorLayerIndex() // start the download
const availableLayersBboxes = Stores.ListStabilized( const availableLayersBboxes = Stores.ListStabilized(
@ -96,8 +109,8 @@ export class AvailableRasterLayers {
const lonlat: [number, number] = [loc.lon, loc.lat] const lonlat: [number, number] = [loc.lon, loc.lat]
return eli.filter((eliPolygon) => BBox.get(eliPolygon).contains(lonlat)) return eli.filter((eliPolygon) => BBox.get(eliPolygon).contains(lonlat))
}, },
[AvailableRasterLayers._editorLayerIndexStore] [AvailableRasterLayers._editorLayerIndexStore],
) ),
) )
return Stores.ListStabilized( return Stores.ListStabilized(
availableLayersBboxes.map( availableLayersBboxes.map(
@ -119,15 +132,15 @@ export class AvailableRasterLayers {
if ( if (
!matching.some( !matching.some(
(l) => (l) =>
l.id === AvailableRasterLayers.defaultBackgroundLayer.properties.id l.id === AvailableRasterLayers.defaultBackgroundLayer.properties.id,
) )
) { ) {
matching.push(AvailableRasterLayers.defaultBackgroundLayer) matching.push(AvailableRasterLayers.defaultBackgroundLayer)
} }
return matching return matching
}, },
[enableBing] [enableBing],
) ),
) )
} }
} }
@ -146,7 +159,7 @@ export class RasterLayerUtils {
available: RasterLayerPolygon[], available: RasterLayerPolygon[],
preferredCategory: string, preferredCategory: string,
ignoreLayer?: RasterLayerPolygon, ignoreLayer?: RasterLayerPolygon,
skipLayers: number = 0 skipLayers: number = 0,
): RasterLayerPolygon { ): RasterLayerPolygon {
const inCategory = available.filter((l) => l.properties.category === preferredCategory) const inCategory = available.filter((l) => l.properties.category === preferredCategory)
const best: RasterLayerPolygon[] = inCategory.filter((l) => l.properties.best) const best: RasterLayerPolygon[] = inCategory.filter((l) => l.properties.best)
@ -154,7 +167,7 @@ export class RasterLayerUtils {
let all = best.concat(others) let all = best.concat(others)
console.log( console.log(
"Selected layers are:", "Selected layers are:",
all.map((l) => l.properties.id) all.map((l) => l.properties.id),
) )
if (others.length > skipLayers) { if (others.length > skipLayers) {
all = all.slice(skipLayers) all = all.slice(skipLayers)

View file

@ -120,11 +120,11 @@ export class ConversionContext {
return new ConversionContext(this.messages, this.path, [...this.operation, key]) return new ConversionContext(this.messages, this.path, [...this.operation, key])
} }
warn(message: string) { warn(...message: (string | number)[]) {
this.messages.push({ context: this, level: "warning", message }) this.messages.push({ context: this, level: "warning", message: message.join(" ") })
} }
err(...message: string[]) { err(...message: (string | number)[]) {
this._hasErrors = true this._hasErrors = true
this.messages.push({ context: this, level: "error", message: message.join(" ") }) this.messages.push({ context: this, level: "error", message: message.join(" ") })
} }

View file

@ -20,6 +20,8 @@ import { Translatable } from "../Json/Translatable"
import { ConversionContext } from "./ConversionContext" import { ConversionContext } from "./ConversionContext"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
import { PrevalidateLayer } from "./PrevalidateLayer" import { PrevalidateLayer } from "./PrevalidateLayer"
import { AvailableRasterLayers } from "../../RasterLayers"
import { eliCategory } from "../../RasterLayerProperties"
export class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> { export class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
private readonly _languages: string[] private readonly _languages: string[]
@ -28,7 +30,7 @@ export class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
super( super(
"Checks that the given object is fully translated in the specified languages", "Checks that the given object is fully translated in the specified languages",
[], [],
"ValidateLanguageCompleteness" "ValidateLanguageCompleteness",
) )
this._languages = languages ?? ["en"] this._languages = languages ?? ["en"]
} }
@ -42,7 +44,7 @@ export class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
.filter( .filter(
(t) => (t) =>
t.tr.translations[neededLanguage] === undefined && t.tr.translations[neededLanguage] === undefined &&
t.tr.translations["*"] === undefined t.tr.translations["*"] === undefined,
) )
.forEach((missing) => { .forEach((missing) => {
context context
@ -53,7 +55,7 @@ export class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
", but it lacks a translation for " + ", but it lacks a translation for " +
missing.context + missing.context +
".\n\tThe known translation is " + ".\n\tThe known translation is " +
missing.tr.textFor("en") missing.tr.textFor("en"),
) )
}) })
} }
@ -70,7 +72,7 @@ export class DoesImageExist extends DesugaringStep<string> {
constructor( constructor(
knownImagePaths: Set<string>, knownImagePaths: Set<string>,
checkExistsSync: (path: string) => boolean = undefined, checkExistsSync: (path: string) => boolean = undefined,
ignore?: Set<string> ignore?: Set<string>,
) { ) {
super("Checks if an image exists", [], "DoesImageExist") super("Checks if an image exists", [], "DoesImageExist")
this._ignore = ignore this._ignore = ignore
@ -103,22 +105,22 @@ export class DoesImageExist extends DesugaringStep<string> {
return image return image
} }
if(Utils.isEmoji(image)){ if (Utils.isEmoji(image)) {
return image return image
} }
if (!this._knownImagePaths.has(image)) { if (!this._knownImagePaths.has(image)) {
if (this.doesPathExist === undefined) { if (this.doesPathExist === undefined) {
context.err( context.err(
`Image with path ${image} not found or not attributed; it is used in ${context}` `Image with path ${image} not found or not attributed; it is used in ${context}`,
) )
} else if (!this.doesPathExist(image)) { } else if (!this.doesPathExist(image)) {
context.err( context.err(
`Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.` `Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.`,
) )
} else { } else {
context.err( context.err(
`Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info` `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`,
) )
} }
} }
@ -131,7 +133,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
super( super(
"Checks that an 'overrideAll' does not override a single override", "Checks that an 'overrideAll' does not override a single override",
[], [],
"OverrideShadowingCheck" "OverrideShadowingCheck",
) )
} }
@ -181,7 +183,7 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> {
context context
.enter("layers") .enter("layers")
.err( .err(
"The 'layers'-field should be an array, but it is not. Did you pase a layer identifier and forget to add the '[' and ']'?" "The 'layers'-field should be an array, but it is not. Did you pase a layer identifier and forget to add the '[' and ']'?",
) )
} }
if (json.socialImage === "") { if (json.socialImage === "") {
@ -215,9 +217,25 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> {
context context
.enter("overideAll") .enter("overideAll")
.err( .err(
"'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them." "'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them.",
) )
} }
if (json.defaultBackgroundId
&& ![AvailableRasterLayers.osmCartoProperties.id, ...eliCategory ]
.find(l => l === json.defaultBackgroundId) ) {
const background = json.defaultBackgroundId
const match = AvailableRasterLayers.globalLayers.find(l => l.properties.id === background)
if (!match) {
const suggestions = Utils.sortedByLevenshteinDistance(background,
AvailableRasterLayers.globalLayers, l => l.properties.id)
context.enter("defaultBackgroundId")
.warn("The default background layer with id", background, "does not exist or is not a global layer. Perhaps you meant one of:",
suggestions.slice(0, 5).map(l => l.properties.id).join(", "),
"If you want to use a certain category of background image, use", AvailableRasterLayers.globalLayers.join(", ")
)
}
}
return json return json
} }
} }
@ -227,7 +245,7 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
super( super(
"Various consistency checks on the raw JSON", "Various consistency checks on the raw JSON",
new MiscThemeChecks(), new MiscThemeChecks(),
new OverrideShadowingCheck() new OverrideShadowingCheck(),
) )
} }
} }
@ -237,7 +255,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
super( super(
"The `if`-part in a mapping might set some keys. Those keys are not allowed to be set in the `addExtraTags`, as this might result in conflicting values", "The `if`-part in a mapping might set some keys. Those keys are not allowed to be set in the `addExtraTags`, as this might result in conflicting values",
[], [],
"DetectConflictingAddExtraTags" "DetectConflictingAddExtraTags",
) )
} }
@ -264,7 +282,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
.enters("mappings", i) .enters("mappings", i)
.err( .err(
"AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + "AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " +
duplicateKeys.join(", ") duplicateKeys.join(", "),
) )
} }
} }
@ -282,13 +300,13 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
super( super(
"A tagRendering might set a freeform key (e.g. `name` and have an option that _should_ erase this name, e.g. `noname=yes`). Under normal circumstances, every mapping/freeform should affect all touched keys", "A tagRendering might set a freeform key (e.g. `name` and have an option that _should_ erase this name, e.g. `noname=yes`). Under normal circumstances, every mapping/freeform should affect all touched keys",
[], [],
"DetectNonErasedKeysInMappings" "DetectNonErasedKeysInMappings",
) )
} }
convert( convert(
json: QuestionableTagRenderingConfigJson, json: QuestionableTagRenderingConfigJson,
context: ConversionContext context: ConversionContext,
): QuestionableTagRenderingConfigJson { ): QuestionableTagRenderingConfigJson {
if (json.multiAnswer) { if (json.multiAnswer) {
// No need to check this here, this has its own validation // No need to check this here, this has its own validation
@ -343,7 +361,7 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
.warn( .warn(
"The freeform block does not modify the key `" + "The freeform block does not modify the key `" +
neededKey + neededKey +
"` which is set in a mapping. Use `addExtraTags` to overwrite it" "` which is set in a mapping. Use `addExtraTags` to overwrite it",
) )
} }
} }
@ -362,7 +380,7 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
.warn( .warn(
"This mapping does not modify the key `" + "This mapping does not modify the key `" +
neededKey + neededKey +
"` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it" "` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it",
) )
} }
} }
@ -379,7 +397,7 @@ export class DetectMappingsShadowedByCondition extends DesugaringStep<TagRenderi
super( super(
"Checks that, if the tagrendering has a condition, that a mapping is not contradictory to it, i.e. that there are no dead mappings", "Checks that, if the tagrendering has a condition, that a mapping is not contradictory to it, i.e. that there are no dead mappings",
[], [],
"DetectMappingsShadowedByCondition" "DetectMappingsShadowedByCondition",
) )
this._forceError = forceError this._forceError = forceError
} }
@ -451,7 +469,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
* DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"] * DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"]
*/ */
private static extractCalculatedTagNames( private static extractCalculatedTagNames(
layerConfig?: LayerConfigJson | { calculatedTags: string[] } layerConfig?: LayerConfigJson | { calculatedTags: string[] },
) { ) {
return ( return (
layerConfig?.calculatedTags?.map((ct) => { layerConfig?.calculatedTags?.map((ct) => {
@ -537,7 +555,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
json.mappings[i]["hideInAnswer"] !== true json.mappings[i]["hideInAnswer"] !== true
) { ) {
context.warn( context.warn(
`Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.` `Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`,
) )
} else if (doesMatch) { } else if (doesMatch) {
// The current mapping is shadowed! // The current mapping is shadowed!
@ -545,7 +563,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
The mapping ${parsedConditions[i].asHumanString( The mapping ${parsedConditions[i].asHumanString(
false, false,
false, false,
{} {},
)} is fully matched by a previous mapping (namely ${j}), which matches: )} is fully matched by a previous mapping (namely ${j}), which matches:
${parsedConditions[j].asHumanString(false, false, {})}. ${parsedConditions[j].asHumanString(false, false, {})}.
@ -571,7 +589,7 @@ export class ValidatePossibleLinks extends DesugaringStep<string | Record<string
super( super(
"Given a possible set of translations, validates that <a href=... target='_blank'> does have `rel='noopener'` set", "Given a possible set of translations, validates that <a href=... target='_blank'> does have `rel='noopener'` set",
[], [],
"ValidatePossibleLinks" "ValidatePossibleLinks",
) )
} }
@ -601,21 +619,21 @@ export class ValidatePossibleLinks extends DesugaringStep<string | Record<string
convert( convert(
json: string | Record<string, string>, json: string | Record<string, string>,
context: ConversionContext context: ConversionContext,
): string | Record<string, string> { ): string | Record<string, string> {
if (typeof json === "string") { if (typeof json === "string") {
if (this.isTabnabbingProne(json)) { if (this.isTabnabbingProne(json)) {
context.err( context.err(
"The string " + "The string " +
json + json +
" has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping" " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping",
) )
} }
} else { } else {
for (const k in json) { for (const k in json) {
if (this.isTabnabbingProne(json[k])) { if (this.isTabnabbingProne(json[k])) {
context.err( context.err(
`The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping` `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`,
) )
} }
} }
@ -633,7 +651,7 @@ export class CheckTranslation extends DesugaringStep<Translatable> {
super( super(
"Checks that a translation is valid and internally consistent", "Checks that a translation is valid and internally consistent",
["*"], ["*"],
"CheckTranslation" "CheckTranslation",
) )
this._allowUndefined = allowUndefined this._allowUndefined = allowUndefined
} }
@ -680,7 +698,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
isBuiltin: boolean, isBuiltin: boolean,
doesImageExist: DoesImageExist, doesImageExist: DoesImageExist,
studioValidations: boolean = false, studioValidations: boolean = false,
skipDefaultLayers: boolean = false skipDefaultLayers: boolean = false,
) { ) {
super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig") super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig")
this.validator = new ValidateLayer( this.validator = new ValidateLayer(
@ -688,7 +706,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
isBuiltin, isBuiltin,
doesImageExist, doesImageExist,
studioValidations, studioValidations,
skipDefaultLayers skipDefaultLayers,
) )
} }
@ -716,7 +734,7 @@ export class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJ
context context
.enter("markers") .enter("markers")
.err( .err(
`Detected a field 'markerS' in pointRendering. It is written as a singular case` `Detected a field 'markerS' in pointRendering. It is written as a singular case`,
) )
} }
if (json.marker && !Array.isArray(json.marker)) { if (json.marker && !Array.isArray(json.marker)) {
@ -726,7 +744,7 @@ export class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJ
context context
.enter("location") .enter("location")
.err( .err(
"A pointRendering should have at least one 'location' to defined where it should be rendered. " "A pointRendering should have at least one 'location' to defined where it should be rendered. ",
) )
} }
return json return json
@ -745,26 +763,26 @@ export class ValidateLayer extends Conversion<
isBuiltin: boolean, isBuiltin: boolean,
doesImageExist: DoesImageExist, doesImageExist: DoesImageExist,
studioValidations: boolean = false, studioValidations: boolean = false,
skipDefaultLayers: boolean = false skipDefaultLayers: boolean = false,
) { ) {
super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer")
this._prevalidation = new PrevalidateLayer( this._prevalidation = new PrevalidateLayer(
path, path,
isBuiltin, isBuiltin,
doesImageExist, doesImageExist,
studioValidations studioValidations,
) )
this._skipDefaultLayers = skipDefaultLayers this._skipDefaultLayers = skipDefaultLayers
} }
convert( convert(
json: LayerConfigJson, json: LayerConfigJson,
context: ConversionContext context: ConversionContext,
): { parsed: LayerConfig; raw: LayerConfigJson } { ): { parsed: LayerConfig; raw: LayerConfigJson } {
context = context.inOperation(this.name) context = context.inOperation(this.name)
if (typeof json === "string") { if (typeof json === "string") {
context.err( context.err(
`Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed` `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`,
) )
return undefined return undefined
} }
@ -796,7 +814,7 @@ export class ValidateLayer extends Conversion<
context context
.enters("calculatedTags", i) .enters("calculatedTags", i)
.err( .err(
`Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}` `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}`,
) )
} }
} }
@ -844,7 +862,7 @@ export class ValidateLayer extends Conversion<
context context
.enters("allowMove", "enableAccuracy") .enters("allowMove", "enableAccuracy")
.err( .err(
"`enableAccuracy` is written with two C in the first occurrence and only one in the last" "`enableAccuracy` is written with two C in the first occurrence and only one in the last",
) )
} }
@ -875,8 +893,8 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
.enters("fields", i) .enters("fields", i)
.err( .err(
`Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from( `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from(
Validators.availableTypes Validators.availableTypes,
).join(",")}` ).join(",")}`,
) )
} }
} }
@ -893,13 +911,13 @@ export class DetectDuplicateFilters extends DesugaringStep<{
super( super(
"Tries to detect layers where a shared filter can be used (or where similar filters occur)", "Tries to detect layers where a shared filter can be used (or where similar filters occur)",
[], [],
"DetectDuplicateFilters" "DetectDuplicateFilters",
) )
} }
convert( convert(
json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }, json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] },
context: ConversionContext context: ConversionContext,
): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } { ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } {
const { layers, themes } = json const { layers, themes } = json
const perOsmTag = new Map< const perOsmTag = new Map<
@ -963,7 +981,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{
filter: FilterConfigJson filter: FilterConfigJson
}[] }[]
>, >,
layout?: LayoutConfigJson | undefined layout?: LayoutConfigJson | undefined,
): void { ): void {
if (layer.filter === undefined || layer.filter === null) { if (layer.filter === undefined || layer.filter === null) {
return return
@ -1003,7 +1021,7 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
super( super(
"Detects mappings which have identical (english) names or identical mappings.", "Detects mappings which have identical (english) names or identical mappings.",
["presets"], ["presets"],
"DetectDuplicatePresets" "DetectDuplicatePresets",
) )
} }
@ -1014,13 +1032,13 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
if (new Set(enNames).size != enNames.length) { if (new Set(enNames).size != enNames.length) {
const dups = Utils.Duplicates(enNames) const dups = Utils.Duplicates(enNames)
const layersWithDup = json.layers.filter((l) => const layersWithDup = json.layers.filter((l) =>
l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0) l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0),
) )
const layerIds = layersWithDup.map((l) => l.id) const layerIds = layersWithDup.map((l) => l.id)
context.err( context.err(
`This theme has multiple presets which are named:${dups}, namely layers ${layerIds.join( `This theme has multiple presets which are named:${dups}, namely layers ${layerIds.join(
", " ", ",
)} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets` )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`,
) )
} }
@ -1035,17 +1053,17 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
Utils.SameObject(presetATags, presetBTags) && Utils.SameObject(presetATags, presetBTags) &&
Utils.sameList( Utils.sameList(
presetA.preciseInput.snapToLayers, presetA.preciseInput.snapToLayers,
presetB.preciseInput.snapToLayers presetB.preciseInput.snapToLayers,
) )
) { ) {
context.err( context.err(
`This theme has multiple presets with the same tags: ${presetATags.asHumanString( `This theme has multiple presets with the same tags: ${presetATags.asHumanString(
false, false,
false, false,
{} {},
)}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[ )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[
j j
].title.textFor("en")}'` ].title.textFor("en")}'`,
) )
} }
} }
@ -1070,13 +1088,13 @@ export class ValidateThemeEnsemble extends Conversion<
super( super(
"Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes", "Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes",
[], [],
"ValidateThemeEnsemble" "ValidateThemeEnsemble",
) )
} }
convert( convert(
json: LayoutConfig[], json: LayoutConfig[],
context: ConversionContext context: ConversionContext,
): Map< ): Map<
string, string,
{ {
@ -1131,7 +1149,7 @@ export class ValidateThemeEnsemble extends Conversion<
"' is found in multiple themes with different tag definitions:", "' is found in multiple themes with different tag definitions:",
"\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}), "\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}),
"\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}), "\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}),
].join("\n") ].join("\n"),
) )
} }
} }

View file

@ -62,7 +62,7 @@
return return
} }
await state?.imageUploadManager.uploadImageAndApply(file, tags, targetKey) await state?.imageUploadManager?.uploadImageAndApply(file, tags, targetKey)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
state.reportError(e, "Could not upload image") state.reportError(e, "Could not upload image")

View file

@ -1 +1 @@
{"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t3.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14634&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null} {"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t3.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14738&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null}