Merge branch 'develop' into feature/doctor
This commit is contained in:
commit
ccb51e2175
30 changed files with 1625 additions and 241 deletions
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -2,7 +2,6 @@ dist/*
|
|||
node_modules
|
||||
.cache/*
|
||||
.idea/*
|
||||
.vscode/*
|
||||
scratch
|
||||
assets/editor-layer-index.json
|
||||
assets/generated/*
|
||||
|
@ -24,3 +23,16 @@ index_*.ts
|
|||
.~lock.*
|
||||
*.doctest.ts
|
||||
service-worker.js
|
||||
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
14
.gitpod.yml
Normal file
14
.gitpod.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
tasks:
|
||||
- init: npm run init
|
||||
command: npm run start
|
||||
|
||||
ports:
|
||||
- name: MapComplete Website
|
||||
port: 1234
|
||||
onOpen: open-browser
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- "esbenp.prettier-vscode"
|
||||
- "eamodio.gitlens",
|
||||
- "GitHub.vscode-pull-request-github"
|
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"eamodio.gitlens",
|
||||
"GitHub.vscode-pull-request-github"
|
||||
]
|
||||
}
|
21
.vscode/settings.json
vendored
Normal file
21
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"/assets/layers/*/*.json",
|
||||
"!/assets/layers/*/license_info.json"
|
||||
],
|
||||
"url": "./Docs/Schemas/LayerConfigJson.schema.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"/assets/themes/*/*.json",
|
||||
"!/assets/themes/*/license_info.json"
|
||||
],
|
||||
"url": "./Docs/Schemas/LayoutConfigJson.schema.json"
|
||||
}
|
||||
],
|
||||
"editor.tabSize": 2,
|
||||
"files.autoSave": "onFocusChange",
|
||||
"search.useIgnoreFiles": true
|
||||
}
|
14
.vscode/tasks.json
vendored
Normal file
14
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"path": "/",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"label": "MapComplete Dev",
|
||||
"detail": "Run MapComplete Dev Server"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -73,6 +73,11 @@ To use the WSL in Visual Studio Code:
|
|||
|
||||
To use WSL without Visual Studio Code you can replace steps 7 and 8 by opening up a WSL terminal
|
||||
|
||||
On mac
|
||||
------
|
||||
|
||||
Install the `Command line tools for XCode which you can find [here](https://developer.apple.com/download/all/). You might need an apple dev account for this.
|
||||
|
||||
Automatic deployment
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -39,6 +39,14 @@ export abstract class Conversion<TIn, TOut> {
|
|||
return DesugaringStep.strict(fixed)
|
||||
}
|
||||
|
||||
public convertJoin(json: TIn, context: string, errors: string[], warnings?: string[], information?: string[]): TOut {
|
||||
const fixed = this.convert(json, context)
|
||||
errors?.push(...(fixed.errors ?? []))
|
||||
warnings?.push(...(fixed.warnings ?? []))
|
||||
information?.push(...(fixed.information ?? []))
|
||||
return fixed.result
|
||||
}
|
||||
|
||||
public andThenF<X>(f: (tout:TOut) => X ): Conversion<TIn, X>{
|
||||
return new Pipe(
|
||||
this,
|
||||
|
|
|
@ -45,19 +45,67 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
|
|||
}
|
||||
}
|
||||
|
||||
export class DoesImageExist extends DesugaringStep<string> {
|
||||
|
||||
private readonly _knownImagePaths: Set<string>;
|
||||
private readonly doesPathExist: (path: string) => boolean = undefined;
|
||||
|
||||
constructor(knownImagePaths: Set<string>, checkExistsSync: (path: string) => boolean = undefined) {
|
||||
super("Checks if an image exists", [], "DoesImageExist");
|
||||
this._knownImagePaths = knownImagePaths;
|
||||
this.doesPathExist = checkExistsSync;
|
||||
}
|
||||
|
||||
convert(image: string, context: string): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
const information = []
|
||||
if (image.indexOf("{") >= 0) {
|
||||
information.push("Ignoring image with { in the path: " + image)
|
||||
return {result: image}
|
||||
}
|
||||
|
||||
if (image === "assets/SocialImage.png") {
|
||||
return {result: image}
|
||||
}
|
||||
if (image.match(/[a-z]*/)) {
|
||||
|
||||
if (Svg.All[image + ".svg"] !== undefined) {
|
||||
// This is a builtin img, e.g. 'checkmark' or 'crosshair'
|
||||
return {result: image};
|
||||
}
|
||||
}
|
||||
|
||||
if (this._knownImagePaths !== undefined && !this._knownImagePaths.has(image)) {
|
||||
if (this.doesPathExist === undefined) {
|
||||
errors.push(`Image with path ${image} not found or not attributed; it is used in ${context}`)
|
||||
} else if (!this.doesPathExist(image)) {
|
||||
errors.push(`Image with path ${image} does not exist; it is used in ${context}.\n Check for typo's and missing directories in the path.`)
|
||||
} else {
|
||||
errors.push(`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`)
|
||||
}
|
||||
}
|
||||
return {
|
||||
result: image,
|
||||
errors, warnings, information
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
||||
/**
|
||||
* The paths where this layer is originally saved. Triggers some extra checks
|
||||
* @private
|
||||
*/
|
||||
private readonly _path?: string;
|
||||
private readonly knownImagePaths: Set<string>;
|
||||
private readonly _isBuiltin: boolean;
|
||||
private _sharedTagRenderings: Map<string, any>;
|
||||
private readonly _validateImage: DesugaringStep<string>;
|
||||
|
||||
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
|
||||
constructor(doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
|
||||
super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme");
|
||||
this.knownImagePaths = knownImagePaths;
|
||||
this._validateImage = doesImageExist;
|
||||
this._path = path;
|
||||
this._isBuiltin = isBuiltin;
|
||||
this._sharedTagRenderings = sharedTagRenderings;
|
||||
|
@ -89,26 +137,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
errors.push("Found a remote image: " + remoteImage + " in theme " + json.id + ", please download it.")
|
||||
}
|
||||
for (const image of images) {
|
||||
if (image.indexOf("{") >= 0) {
|
||||
information.push("Ignoring image with { in the path: " + image)
|
||||
continue
|
||||
}
|
||||
|
||||
if (image === "assets/SocialImage.png") {
|
||||
continue
|
||||
}
|
||||
if (image.match(/[a-z]*/)) {
|
||||
|
||||
if (Svg.All[image + ".svg"] !== undefined) {
|
||||
// This is a builtin img, e.g. 'checkmark' or 'crosshair'
|
||||
continue;// =>
|
||||
}
|
||||
}
|
||||
|
||||
if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) {
|
||||
const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}`
|
||||
errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}`)
|
||||
}
|
||||
this._validateImage.convertJoin(image, context === undefined ? "" : ` in a layer defined in the theme ${context}`, errors, warnings, information)
|
||||
}
|
||||
|
||||
if (json.icon.endsWith(".svg")) {
|
||||
|
@ -150,9 +179,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
if (theme.id !== filename) {
|
||||
errors.push("Theme ids should be the same as the name.json, but we got id: " + theme.id + " and filename " + filename + " (" + this._path + ")")
|
||||
}
|
||||
if (!this.knownImagePaths.has(theme.icon)) {
|
||||
errors.push("The theme image " + theme.icon + " is not attributed or not saved locally")
|
||||
}
|
||||
this._validateImage.convertJoin(theme.icon, context + ".icon", errors, warnings, information);
|
||||
const dups = Utils.Dupiclates(json.layers.map(layer => layer["id"]))
|
||||
if (dups.length > 0) {
|
||||
errors.push(`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`)
|
||||
|
@ -166,16 +193,16 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
|
||||
// The first key in the the title-field must be english, otherwise the title in the loading page will be the different language
|
||||
const targetLanguage = theme.title.SupportedLanguages()[0]
|
||||
if(targetLanguage !== "en"){
|
||||
if (targetLanguage !== "en") {
|
||||
warnings.push(`TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`)
|
||||
}
|
||||
|
||||
|
||||
// Official, public themes must have a full english translation
|
||||
const checked = new ValidateLanguageCompleteness("en")
|
||||
.convert(theme, theme.id)
|
||||
errors.push(...checked.errors)
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
|
@ -192,10 +219,10 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
}
|
||||
|
||||
export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
|
||||
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
|
||||
constructor(doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
|
||||
super("Validates a theme and the contained layers",
|
||||
new ValidateTheme(knownImagePaths, path, isBuiltin, sharedTagRenderings),
|
||||
new On("layers", new Each(new ValidateLayer(undefined, false, knownImagePaths)))
|
||||
new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings),
|
||||
new On("layers", new Each(new ValidateLayer(undefined, false, doesImageExist)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +381,7 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender
|
|||
})
|
||||
for (let j = 0; j < i; j++) {
|
||||
const doesMatch = parsedConditions[j].matchesProperties(properties)
|
||||
if(doesMatch && json.mappings[j].hideInAnswer === true && json.mappings[i].hideInAnswer !== true){
|
||||
if (doesMatch && json.mappings[j].hideInAnswer === true && json.mappings[i].hideInAnswer !== true) {
|
||||
warnings.push(`At ${context}: 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) {
|
||||
// The current mapping is shadowed!
|
||||
|
@ -385,14 +412,15 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender
|
|||
}
|
||||
|
||||
export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJson> {
|
||||
private knownImagePaths: Set<string>;
|
||||
constructor(knownImagePaths: Set<string>) {
|
||||
private readonly _doesImageExist: DoesImageExist;
|
||||
|
||||
constructor(doesImageExist: DoesImageExist) {
|
||||
super("Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", [], "DetectMappingsWithImages");
|
||||
this.knownImagePaths = knownImagePaths;
|
||||
this._doesImageExist = doesImageExist;
|
||||
}
|
||||
|
||||
/**
|
||||
* const r = new DetectMappingsWithImages(new Set<string>()).convert({
|
||||
* const r = new DetectMappingsWithImages(new DoesImageExist(new Set<string>())).convert({
|
||||
* "mappings": [
|
||||
* {
|
||||
* "if": "bicycle_parking=stands",
|
||||
|
@ -412,9 +440,9 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
|
|||
* r.errors.some(msg => msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0) // => true
|
||||
*/
|
||||
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[], information?: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
const information = []
|
||||
const errors: string[] = []
|
||||
const warnings: string[] = []
|
||||
const information: string[] = []
|
||||
if (json.mappings === undefined || json.mappings.length === 0) {
|
||||
return {result: json}
|
||||
}
|
||||
|
@ -432,12 +460,10 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
|
|||
information.push(`${ctx}: Ignored image ${images.join(", ")} in 'then'-clause of a mapping as this check has been disabled`)
|
||||
|
||||
for (const image of images) {
|
||||
if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) {
|
||||
const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}`
|
||||
errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}`)
|
||||
}
|
||||
this._doesImageExist.convertJoin(image, ctx, errors, warnings, information);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} else if (ignore) {
|
||||
warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`)
|
||||
|
@ -454,10 +480,10 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
|
|||
}
|
||||
|
||||
export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
|
||||
constructor(layerConfig?: LayerConfigJson, knownImagePaths?: Set<string>) {
|
||||
constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) {
|
||||
super("Various validation on tagRenderingConfigs",
|
||||
new DetectShadowedMappings(layerConfig),
|
||||
new DetectMappingsWithImages(knownImagePaths)
|
||||
new DetectMappingsWithImages(doesImageExist)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -469,13 +495,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
*/
|
||||
private readonly _path?: string;
|
||||
private readonly _isBuiltin: boolean;
|
||||
private knownImagePaths: Set<string> | undefined;
|
||||
private readonly _doesImageExist: DoesImageExist;
|
||||
|
||||
constructor(path: string, isBuiltin: boolean, knownImagePaths: Set<string>) {
|
||||
constructor(path: string, isBuiltin: boolean, doesImageExist: DoesImageExist) {
|
||||
super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer");
|
||||
this._path = path;
|
||||
this._isBuiltin = isBuiltin;
|
||||
this.knownImagePaths = knownImagePaths
|
||||
this._doesImageExist = doesImageExist
|
||||
}
|
||||
|
||||
convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings?: string[], information?: string[] } {
|
||||
|
@ -563,7 +589,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
}
|
||||
if (json.tagRenderings !== undefined) {
|
||||
const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json, this.knownImagePaths))).convert(json, context)
|
||||
const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json, this._doesImageExist))).convert(json, context)
|
||||
warnings.push(...(r.warnings ?? []))
|
||||
errors.push(...(r.errors ?? []))
|
||||
information.push(...(r.information ?? []))
|
||||
|
|
|
@ -31,6 +31,7 @@ import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
|||
|
||||
export default class LayerConfig extends WithContextLoader {
|
||||
|
||||
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const;
|
||||
public readonly id: string;
|
||||
public readonly name: Translation;
|
||||
public readonly description: Translation;
|
||||
|
@ -44,10 +45,8 @@ export default class LayerConfig extends WithContextLoader {
|
|||
public readonly maxzoom: number;
|
||||
public readonly title?: TagRenderingConfig;
|
||||
public readonly titleIcons: TagRenderingConfig[];
|
||||
|
||||
public readonly mapRendering: PointRenderingConfig[]
|
||||
public readonly lineRendering: LineRenderingConfig[]
|
||||
|
||||
public readonly units: Unit[];
|
||||
public readonly deletion: DeleteConfig | null;
|
||||
public readonly allowMove: MoveConfig | null
|
||||
|
@ -57,15 +56,11 @@ export default class LayerConfig extends WithContextLoader {
|
|||
* In seconds
|
||||
*/
|
||||
public readonly maxAgeOfCache: number
|
||||
|
||||
public readonly presets: PresetConfig[];
|
||||
|
||||
public readonly tagRenderings: TagRenderingConfig[];
|
||||
public readonly filters: FilterConfig[];
|
||||
public readonly filterIsSameAs: string;
|
||||
public readonly forceLoad: boolean;
|
||||
|
||||
public static readonly syncSelectionAllowed = ["no" , "local" , "theme-only" , "global"] as const;
|
||||
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
|
||||
|
||||
constructor(
|
||||
|
@ -74,18 +69,24 @@ export default class LayerConfig extends WithContextLoader {
|
|||
official: boolean = true
|
||||
) {
|
||||
context = context + "." + json.id;
|
||||
const translationContext = "layers:"+json.id
|
||||
const translationContext = "layers:" + json.id
|
||||
super(json, context)
|
||||
this.id = json.id;
|
||||
|
||||
if (typeof json === "string") {
|
||||
throw `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed (at ${context})`
|
||||
}
|
||||
|
||||
|
||||
if (json.id === undefined) {
|
||||
throw "Not a valid layer: id is undefined: " + JSON.stringify(json)
|
||||
throw `Not a valid layer: id is undefined: ${JSON.stringify(json)} (At ${context})`
|
||||
}
|
||||
|
||||
if (json.source === undefined) {
|
||||
throw "Layer " + this.id + " does not define a source section (" + context + ")"
|
||||
}
|
||||
|
||||
|
||||
if (json.source.osmTags === undefined) {
|
||||
throw "Layer " + this.id + " does not define a osmTags in the source section - these should always be present, even for geojson layers (" + context + ")"
|
||||
}
|
||||
|
@ -98,8 +99,8 @@ export default class LayerConfig extends WithContextLoader {
|
|||
}
|
||||
|
||||
this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30
|
||||
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+"'"
|
||||
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 + "'"
|
||||
}
|
||||
this.syncSelection = json.syncSelection ?? "no";
|
||||
const osmTags = TagUtils.Tag(
|
||||
|
@ -107,10 +108,10 @@ export default class LayerConfig extends WithContextLoader {
|
|||
context + "source.osmTags"
|
||||
);
|
||||
|
||||
if(Constants.priviliged_layers.indexOf(this.id) < 0 && 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, {});
|
||||
if (Constants.priviliged_layers.indexOf(this.id) < 0 && 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, {});
|
||||
}
|
||||
|
||||
|
||||
if (json.source["geoJsonSource"] !== undefined) {
|
||||
throw context + "Use 'geoJson' instead of 'geoJsonSource'";
|
||||
}
|
||||
|
@ -118,7 +119,7 @@ export default class LayerConfig extends WithContextLoader {
|
|||
if (json.source["geojson"] !== undefined) {
|
||||
throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)";
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.source = new SourceConfig(
|
||||
{
|
||||
|
@ -138,8 +139,8 @@ export default class LayerConfig extends WithContextLoader {
|
|||
|
||||
this.allowSplit = json.allowSplit ?? false;
|
||||
this.name = Translations.T(json.name, translationContext + ".name");
|
||||
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"
|
||||
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"
|
||||
}
|
||||
this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`)))
|
||||
|
||||
|
@ -167,8 +168,8 @@ export default class LayerConfig extends WithContextLoader {
|
|||
const index = kv.indexOf("=");
|
||||
let key = kv.substring(0, index).trim();
|
||||
const r = "[a-z_][a-z0-9:]*"
|
||||
if(key.match(r) === null){
|
||||
throw "At "+context+" invalid key for calculated tag: "+key+"; it should match "+r
|
||||
if (key.match(r) === null) {
|
||||
throw "At " + context + " invalid key for calculated tag: " + key + "; it should match " + r
|
||||
}
|
||||
const isStrict = key.endsWith(':')
|
||||
if (isStrict) {
|
||||
|
@ -343,14 +344,14 @@ export default class LayerConfig extends WithContextLoader {
|
|||
}
|
||||
|
||||
public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy?: Map<string, string[]>, dependencies: {
|
||||
context?: string;
|
||||
reason: string;
|
||||
neededLayer: string;
|
||||
}[] = []
|
||||
, addedByDefault = false, canBeIncluded = true): BaseUIElement {
|
||||
context?: string;
|
||||
reason: string;
|
||||
neededLayer: string;
|
||||
}[] = []
|
||||
, addedByDefault = false, canBeIncluded = true): BaseUIElement {
|
||||
const extraProps = []
|
||||
|
||||
extraProps.push("This layer is shown at zoomlevel **"+this.minzoom+"** and higher")
|
||||
|
||||
extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
|
||||
|
||||
if (canBeIncluded) {
|
||||
if (addedByDefault) {
|
||||
|
@ -440,7 +441,7 @@ export default class LayerConfig extends WithContextLoader {
|
|||
let overpassLink: BaseUIElement = undefined;
|
||||
if (Constants.priviliged_layers.indexOf(this.id) < 0) {
|
||||
try {
|
||||
overpassLink = new Link("Execute on overpass", Overpass.AsOverpassTurboLink(<TagsFilter> new And(neededTags).optimize()))
|
||||
overpassLink = new Link("Execute on overpass", Overpass.AsOverpassTurboLink(<TagsFilter>new And(neededTags).optimize()))
|
||||
} catch (e) {
|
||||
console.error("Could not generate overpasslink for " + this.id)
|
||||
}
|
||||
|
|
|
@ -352,7 +352,7 @@ export default class TagRenderingConfig {
|
|||
}
|
||||
|
||||
if (hideInAnswer !== true && !(mp.ifnot?.isUsableAsAnswer() ?? true)) {
|
||||
throw `${context}.mapping[${i}].ifnot: This value cannot be used to answer a question, probably because it contains a regex or an OR. Either change it or set 'hideInAnswer'`
|
||||
throw `${context}.mapping[${i}].ifnot: This value cannot be used to answer a question, probably because it contains a regex or an OR. If a contributor were to pick this as an option, MapComplete wouldn't be able to determine which tags to add.\n Either change it or set 'hideInAnswer'`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
12
assets/layers/shelter/license_info.json
Normal file
12
assets/layers/shelter/license_info.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"path": "shelter.svg",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
"Diemen Design"
|
||||
],
|
||||
"sources": [
|
||||
"https://icon-icons.com/icon/map-shelter/158301"
|
||||
]
|
||||
}
|
||||
]
|
88
assets/layers/shelter/shelter.json
Normal file
88
assets/layers/shelter/shelter.json
Normal file
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"id": "shelter",
|
||||
"name": {
|
||||
"en": "Shelter"
|
||||
},
|
||||
"description": {
|
||||
"en": "Layer showing shelter structures"
|
||||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"amenity=shelter"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minzoom": 13,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Shelter"
|
||||
}
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"location": [
|
||||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"icon": "./assets/layers/shelter/shelter.svg"
|
||||
}
|
||||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "shelter-type",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "shelter_type=public_transport",
|
||||
"then": {
|
||||
"en": "This is a shelter at a public transport stop."
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter_type=picnic_shelter",
|
||||
"then": {
|
||||
"en": "This is a shelter protecting from rain at a picnic site."
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter_type=gazebo",
|
||||
"then": {
|
||||
"en": "This is a gazebo."
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter_type=weather_shelter",
|
||||
"then": {
|
||||
"en": "This is a small shelter, primarily intended for short breaks. Usually found in the mountains or alongside roads."
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter_type=lean_to",
|
||||
"then": {
|
||||
"en": "This is a shed with 3 walls, primarily intended for camping."
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter_type=pavilion",
|
||||
"then": {
|
||||
"en": "This is a pavilion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter_type=basic_hut",
|
||||
"then": "This is a basic hut, providing basic shelter and sleeping facilities."
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "What kind of shelter is this?"
|
||||
},
|
||||
"render": {
|
||||
"en": "Shelter type: {shelter_type}"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "shelter_type",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
1
assets/layers/shelter/shelter.svg
Normal file
1
assets/layers/shelter/shelter.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="M 4.4352679,0.99999571 3.578125,2.7143027 l 0.4017857,0 0.8973214,-1.71430699 -0.4419642,0 z m 3.4285714,0 -0.8571429,1.71430699 0.4017857,0 0.8973215,-1.71430699 -0.4419643,0 z m 3.4285707,0 -0.857143,1.71430699 0.401786,0 0.897322,-1.71430699 -0.441965,0 z M 1.8571429,2.7143027 1,4.4286098 l 0.4017857,0 0.8989955,-1.7143071 -0.4436383,0 z m 3.4352678,0 -0.8571428,1.7143071 0.4017857,0 0.8973214,-1.7143071 -0.4419643,0 z m 3.4285714,0 -0.8571428,1.7143071 0.4017857,0 0.8973214,-1.7143071 -0.4419643,0 z m 3.4285709,0 -0.857143,1.7143071 0.401786,0 0.897322,-1.7143071 -0.441965,0 z m -9.0066959,1.7143071 -0.8571428,1.714307 0.4017857,0 0.8989955,-1.714307 -0.4436384,0 z m 6.842076,0 -0.8571429,1.714307 0.4017857,0 0.8989951,-1.714307 -0.4436379,0 z M 7,5.2857633 l -6,3.428614 1.7142857,0 0.8571429,-0.3431962 0,4.1853199 0,0.01507 a 0.42857143,0.42857675 0 0 0 0.2059152,0.366634 0.42857143,0.42857675 0 0 0 0.4319196,0.0067 0.42857143,0.42857675 0 0 0 0.219308,-0.373322 l 0,-4.5435832 L 7,7.0000703 l 2.5714286,1.0279145 0,4.5435832 a 0.42857143,0.42857675 0 0 0 0.2059148,0.366634 0.42857143,0.42857675 0 0 0 0.4319206,0.0067 0.42857143,0.42857675 0 0 0 0.219307,-0.37332 l 0,-4.2003869 0.857143,0.3431962 1.714286,0 -6,-3.428614 z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
141
assets/layers/transit_routes/transit_routes.json
Normal file
141
assets/layers/transit_routes/transit_routes.json
Normal file
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"id": "transit_routes",
|
||||
"name": {
|
||||
"en": "Bus lines"
|
||||
},
|
||||
"description": {
|
||||
"en": "Layer showing bus lines"
|
||||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"type=route",
|
||||
"route=bus"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minzoom": 15,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Bus line"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "name~*",
|
||||
"then": {
|
||||
"en": "{name}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"color": {
|
||||
"render": {
|
||||
"en": "#ff0000"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "colour~*",
|
||||
"then": "{colour}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "name",
|
||||
"freeform": {
|
||||
"key": "name",
|
||||
"type": "string",
|
||||
"placeholder": "Bus XX: From => Via => To"
|
||||
},
|
||||
"render": "{name}",
|
||||
"question": {
|
||||
"en": "What is the name for this bus line? (i.e. Bus XX: From => Via => To)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "from",
|
||||
"freeform": {
|
||||
"key": "from",
|
||||
"type": "string",
|
||||
"placeholder": "City, Stop Name"
|
||||
},
|
||||
"render": {
|
||||
"en": "This bus line begins at {from}"
|
||||
},
|
||||
"question": {
|
||||
"en": "What is the starting point for this bus line?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "via",
|
||||
"freeform": {
|
||||
"key": "via",
|
||||
"type": "string",
|
||||
"placeholder": "City, Stop Name"
|
||||
},
|
||||
"render": {
|
||||
"en": "This bus line goes via {via}"
|
||||
},
|
||||
"question": {
|
||||
"en": "What is the via point for this bus line?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "to",
|
||||
"freeform": {
|
||||
"key": "to",
|
||||
"type": "string",
|
||||
"placeholder": "City, Stop Name"
|
||||
},
|
||||
"render": {
|
||||
"en": "This bus line ends at {to}"
|
||||
},
|
||||
"question": {
|
||||
"en": "What is the ending point for this bus line?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "colour",
|
||||
"freeform": {
|
||||
"key": "colour",
|
||||
"type": "color"
|
||||
},
|
||||
"render": {
|
||||
"en": "This bus line has the color {colour}"
|
||||
},
|
||||
"question": {
|
||||
"en": "What is the colour for this bus line?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "network",
|
||||
"freeform": {
|
||||
"key": "network",
|
||||
"type": "string"
|
||||
},
|
||||
"render": {
|
||||
"en": "This bus line is part of the {network} network"
|
||||
},
|
||||
"question": {
|
||||
"en": "What network does this bus line belong to?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "operator",
|
||||
"freeform": {
|
||||
"key": "operator",
|
||||
"type": "string"
|
||||
},
|
||||
"render": {
|
||||
"en": "This bus line is operated by {operator}"
|
||||
},
|
||||
"question": {
|
||||
"en": "What company operates this bus line?"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
55
assets/layers/transit_stops/bus_stop.svg
Normal file
55
assets/layers/transit_stops/bus_stop.svg
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="500"
|
||||
height="500"
|
||||
viewBox="0 0 500 500"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="bus_stop.svg"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="25.986174"
|
||||
inkscape:cx="-2.629287"
|
||||
inkscape:cy="10.208516"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="M 100,0 50,50 v 400 h 32.8125 c 0,0 5.057445,50 40.6375,50 34.45648,0 39.05,-50 39.05,-50 h 175 c 0,0 5.7171,50 39.05,50 40.82343,0 40.6375,-50 40.6375,-50 H 450 V 50 L 400,0 Z m 50,50 h 200 v 50 H 150 Z M 100,150 H 400 V 300 H 100 Z m 0,200 h 50 v 50 h -50 z m 250,0 h 50 v 50 h -50 z"
|
||||
id="path2"
|
||||
style="stroke-width:50" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
15
assets/layers/transit_stops/license_info.json
Normal file
15
assets/layers/transit_stops/license_info.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
"path": "bus_stop.svg",
|
||||
"license": "CC0",
|
||||
"authors": [
|
||||
"Andy Allan",
|
||||
"Michael Glanznig",
|
||||
"Paul Norman",
|
||||
"Paul Dicker"
|
||||
],
|
||||
"sources": [
|
||||
"https://github.com/gravitystorm/openstreetmap-carto/blob/master/symbols/highway/bus_stop.svg"
|
||||
]
|
||||
}
|
||||
]
|
260
assets/layers/transit_stops/transit_stops.json
Normal file
260
assets/layers/transit_stops/transit_stops.json
Normal file
|
@ -0,0 +1,260 @@
|
|||
{
|
||||
"id": "transit_stops",
|
||||
"name": {
|
||||
"en": "Transit Stops"
|
||||
},
|
||||
"description": {
|
||||
"en": "Layer showing different types of transit stops."
|
||||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"or": [
|
||||
"highway=bus_stop"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minzoom": 15,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "Transit Stop"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "name~*",
|
||||
"then": {
|
||||
"en": "Stop {name}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"location": [
|
||||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"icon": {
|
||||
"render": "./assets/layers/transit_stops/bus_stop.svg",
|
||||
"mappings": []
|
||||
},
|
||||
"label": "<div style=\"background: white; display: block\">{name}</div>"
|
||||
}
|
||||
],
|
||||
"calculatedTags": [
|
||||
"_routes=feat.memberships()",
|
||||
"_contained_routes_properties=feat.memberships().map(p => {return {id: p.relation.id, name: p.relation.properties.name} }).filter((v,i,a)=>a.findIndex(t=>(JSON.stringify(t) === JSON.stringify(v)))===i)",
|
||||
"_contained_route_ids=JSON.parse(feat.properties._contained_routes_properties ?? '[]').map(p => p.id)",
|
||||
"_contained_routes=JSON.parse(feat.properties._contained_routes_properties ?? '[]').map(p => `<li><a href='#relation/${p.id}'>${p.name ?? 'bus route'}</a></li>`).join('')",
|
||||
"_contained_routes_count=JSON.parse(feat.properties._contained_routes_properties ?? '[]').length"
|
||||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "stop_name",
|
||||
"render": {
|
||||
"en": "This stop is called <b>{name}</b>"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "name",
|
||||
"type": "string",
|
||||
"addExtraTags": [
|
||||
"noname="
|
||||
],
|
||||
"placeholder": {
|
||||
"en": "Name of the stop"
|
||||
}
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"noname=yes",
|
||||
"name="
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"en": "This stop has no name"
|
||||
}
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "What is the name of this stop?"
|
||||
},
|
||||
"placeholder": "Name of the stop"
|
||||
},
|
||||
"images",
|
||||
{
|
||||
"id": "shelter",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "shelter=yes",
|
||||
"then": {
|
||||
"en": "This stop has a shelter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter=no",
|
||||
"then": {
|
||||
"en": "This stop does not have a shelter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "shelter=separate",
|
||||
"then": {
|
||||
"en": "This stop has a shelter, that's separately mapped"
|
||||
},
|
||||
"hideInAnswer": true
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Does this stop have a shelter?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bench",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "bench=yes",
|
||||
"then": {
|
||||
"en": "This stop has a bench"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "bench=no",
|
||||
"then": {
|
||||
"en": "This stop does not have a bench"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "bench=separate",
|
||||
"then": {
|
||||
"en": "This stop has a bench, that's separately mapped"
|
||||
},
|
||||
"hideInAnswer": true
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Does this stop have a bench?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bin",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "bin=yes",
|
||||
"then": {
|
||||
"en": "This stop has a bin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "bin=no",
|
||||
"then": {
|
||||
"en": "This stop does not have a bin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "bin=separate",
|
||||
"then": {
|
||||
"en": "This stop has a bin, that's separately mapped"
|
||||
},
|
||||
"hideInAnswer": true
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Does this stop have a bin?"
|
||||
}
|
||||
},
|
||||
"wheelchair-access",
|
||||
{
|
||||
"id": "tactile_paving",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "tactile_paving=yes",
|
||||
"then": {
|
||||
"en": "This stop has tactile paving"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "tactile_paving=no",
|
||||
"then": {
|
||||
"en": "This stop does not have tactile paving"
|
||||
}
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Does this stop have tactile paving?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lit",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "lit=yes",
|
||||
"then": {
|
||||
"en": "This stop is lit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "lit=no",
|
||||
"then": {
|
||||
"en": "This stop is not lit"
|
||||
}
|
||||
}
|
||||
],
|
||||
"question": {
|
||||
"en": "Is this stop lit?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "departures_board",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "departures_board=yes",
|
||||
"then": {
|
||||
"en": "This stop has a departures board of unknown type"
|
||||
},
|
||||
"hideInAnswer": true
|
||||
},
|
||||
{
|
||||
"if": "departures_board=realtime",
|
||||
"then": {
|
||||
"en": "This stop has a board showing realtime departure information"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "passenger_information_display=yes",
|
||||
"then": {
|
||||
"en": "This stop has a board showing realtime departure information"
|
||||
},
|
||||
"hideInAnswer": true
|
||||
},
|
||||
{
|
||||
"if": "departures_board=timetable",
|
||||
"then": {
|
||||
"en": "This stop has a timetable showing regular departures"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "departures_board=interval",
|
||||
"then": {
|
||||
"en": "This stop has a timetable containing just the interval between departures"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "departures_board=no",
|
||||
"then": {
|
||||
"en": "This stop does not have a departures board"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"render": {
|
||||
"en": "<h3>{_contained_routes_count} routes stop at this stop</h3> <ul>{_contained_routes}</ul>"
|
||||
},
|
||||
"condition": "_contained_routes~*",
|
||||
"id": "contained_routes"
|
||||
}
|
||||
],
|
||||
"filter": [],
|
||||
"allowMove": false
|
||||
}
|
|
@ -311,6 +311,10 @@
|
|||
"if": "theme=toilets",
|
||||
"then": "./assets/themes/toilets/toilets.svg"
|
||||
},
|
||||
{
|
||||
"if": "theme=transit",
|
||||
"then": "./assets/layers/transit_stops/bus_stop.svg"
|
||||
},
|
||||
{
|
||||
"if": "theme=trees",
|
||||
"then": "./assets/themes/trees/logo.svg"
|
||||
|
|
51
assets/themes/transit/transit.json
Normal file
51
assets/themes/transit/transit.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"id": "transit",
|
||||
"maintainer": "Robin van der Linde",
|
||||
"version": "20220406",
|
||||
"title": {
|
||||
"en": "Bus routes"
|
||||
},
|
||||
"description": {
|
||||
"en": "Plan your trip with the help of the public transport system."
|
||||
},
|
||||
"icon": "./assets/layers/transit_stops/bus_stop.svg",
|
||||
"startZoom": 20,
|
||||
"startLat": 53.21333,
|
||||
"startLon": 6.56963,
|
||||
"layers": [
|
||||
"transit_stops",
|
||||
"transit_routes",
|
||||
{
|
||||
"builtin": "bike_parking",
|
||||
"override": {
|
||||
"minzoom": 19,
|
||||
"minzoomVisible": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"builtin": "parking",
|
||||
"override": {
|
||||
"minzoom": 19,
|
||||
"minzoomVisible": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"builtin": "shelter",
|
||||
"override": {
|
||||
"minzoom": 19,
|
||||
"minzoomVisible": 19,
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"amenity=shelter",
|
||||
"shelter_type=public_transport"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideTagRenderingsWithLabels": [
|
||||
"shelter-type"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -3568,6 +3568,11 @@
|
|||
"0": {
|
||||
"explanation": "{title()} has closed down permanently"
|
||||
}
|
||||
},
|
||||
"nonDeleteMappings": {
|
||||
"0": {
|
||||
"then": "This is actually a pub"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "A layer showing restaurants and fast-food amenities (with a special rendering for friteries)",
|
||||
|
@ -5232,6 +5237,39 @@
|
|||
"render": "School <i>{name}</i>"
|
||||
}
|
||||
},
|
||||
"shelter": {
|
||||
"description": "Layer showing shelter structures",
|
||||
"name": "Shelter",
|
||||
"tagRenderings": {
|
||||
"shelter-type": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This is a shelter at a public transport stop."
|
||||
},
|
||||
"1": {
|
||||
"then": "This is a shelter protecting from rain at a picnic site."
|
||||
},
|
||||
"2": {
|
||||
"then": "This is a gazebo."
|
||||
},
|
||||
"3": {
|
||||
"then": "This is a small shelter, primarily intended for short breaks. Usually found in the mountains or alongside roads."
|
||||
},
|
||||
"4": {
|
||||
"then": "This is a shed with 3 walls, primarily intended for camping."
|
||||
},
|
||||
"5": {
|
||||
"then": "This is a pavilion"
|
||||
}
|
||||
},
|
||||
"question": "What kind of shelter is this?",
|
||||
"render": "Shelter type: {shelter_type}"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Shelter"
|
||||
}
|
||||
},
|
||||
"shops": {
|
||||
"deletion": {
|
||||
"extraDeleteReasons": {
|
||||
|
@ -5279,11 +5317,13 @@
|
|||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"2": {
|
||||
"override": {
|
||||
"question": "What kind of shop is this?"
|
||||
}
|
||||
},
|
||||
"shops-name": {
|
||||
"question": "What is the name of this shop?"
|
||||
},
|
||||
"shops-type-from-id": {
|
||||
"question": "What kind of shop is this?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
|
@ -5962,6 +6002,169 @@
|
|||
"render": "Trail"
|
||||
}
|
||||
},
|
||||
"transit_routes": {
|
||||
"description": "Layer showing bus lines",
|
||||
"mapRendering": {
|
||||
"0": {
|
||||
"color": {
|
||||
"render": "#ff0000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Bus lines",
|
||||
"tagRenderings": {
|
||||
"colour": {
|
||||
"question": "What is the colour for this bus line?",
|
||||
"render": "This bus line has the color {colour}"
|
||||
},
|
||||
"from": {
|
||||
"question": "What is the starting point for this bus line?",
|
||||
"render": "This bus line begins at {from}"
|
||||
},
|
||||
"name": {
|
||||
"question": "What is the name for this bus line? (i.e. Bus XX: From => Via => To)"
|
||||
},
|
||||
"network": {
|
||||
"question": "What network does this bus line belong to?",
|
||||
"render": "This bus line is part of the {network} network"
|
||||
},
|
||||
"operator": {
|
||||
"question": "What company operates this bus line?",
|
||||
"render": "This bus line is operated by {operator}"
|
||||
},
|
||||
"to": {
|
||||
"question": "What is the ending point for this bus line?",
|
||||
"render": "This bus line ends at {to}"
|
||||
},
|
||||
"via": {
|
||||
"question": "What is the via point for this bus line?",
|
||||
"render": "This bus line goes via {via}"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "{name}"
|
||||
}
|
||||
},
|
||||
"render": "Bus line"
|
||||
}
|
||||
},
|
||||
"transit_stops": {
|
||||
"description": "Layer showing different types of transit stops.",
|
||||
"name": "Transit Stops",
|
||||
"tagRenderings": {
|
||||
"bench": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This stop has a bench"
|
||||
},
|
||||
"1": {
|
||||
"then": "This stop does not have a bench"
|
||||
},
|
||||
"2": {
|
||||
"then": "This stop has a bench, that's separately mapped"
|
||||
}
|
||||
},
|
||||
"question": "Does this stop have a bench?"
|
||||
},
|
||||
"bin": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This stop has a bin"
|
||||
},
|
||||
"1": {
|
||||
"then": "This stop does not have a bin"
|
||||
},
|
||||
"2": {
|
||||
"then": "This stop has a bin, that's separately mapped"
|
||||
}
|
||||
},
|
||||
"question": "Does this stop have a bin?"
|
||||
},
|
||||
"contained_routes": {
|
||||
"render": "<h3>{_contained_routes_count} routes stop at this stop</h3> <ul>{_contained_routes}</ul>"
|
||||
},
|
||||
"departures_board": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This stop has a departures board of unknown type"
|
||||
},
|
||||
"1": {
|
||||
"then": "This stop has a board showing realtime departure information"
|
||||
},
|
||||
"2": {
|
||||
"then": "This stop has a board showing realtime departure information"
|
||||
},
|
||||
"3": {
|
||||
"then": "This stop has a timetable showing regular departures"
|
||||
},
|
||||
"4": {
|
||||
"then": "This stop has a timetable containing just the interval between departures"
|
||||
},
|
||||
"5": {
|
||||
"then": "This stop does not have a departures board"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lit": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This stop is lit"
|
||||
},
|
||||
"1": {
|
||||
"then": "This stop is not lit"
|
||||
}
|
||||
},
|
||||
"question": "Is this stop lit?"
|
||||
},
|
||||
"shelter": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This stop has a shelter"
|
||||
},
|
||||
"1": {
|
||||
"then": "This stop does not have a shelter"
|
||||
},
|
||||
"2": {
|
||||
"then": "This stop has a shelter, that's separately mapped"
|
||||
}
|
||||
},
|
||||
"question": "Does this stop have a shelter?"
|
||||
},
|
||||
"stop_name": {
|
||||
"freeform": {
|
||||
"placeholder": "Name of the stop"
|
||||
},
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This stop has no name"
|
||||
}
|
||||
},
|
||||
"question": "What is the name of this stop?",
|
||||
"render": "This stop is called <b>{name}</b>"
|
||||
},
|
||||
"tactile_paving": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "This stop has tactile paving"
|
||||
},
|
||||
"1": {
|
||||
"then": "This stop does not have tactile paving"
|
||||
}
|
||||
},
|
||||
"question": "Does this stop have tactile paving?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Stop {name}"
|
||||
}
|
||||
},
|
||||
"render": "Transit Stop"
|
||||
}
|
||||
},
|
||||
"tree_node": {
|
||||
"description": "A layer showing trees",
|
||||
"name": "Tree",
|
||||
|
|
|
@ -1756,6 +1756,218 @@
|
|||
"render": "Microbiblioteca"
|
||||
}
|
||||
},
|
||||
"recycling": {
|
||||
"description": "Un livello con i contenitori e centri per la raccolta rifiuti riciclabili",
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Aperto ora"
|
||||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Tutti i tipi di rifiuti"
|
||||
},
|
||||
"1": {
|
||||
"question": "Riciclo di batterie"
|
||||
},
|
||||
"2": {
|
||||
"question": "Riciclo di confezioni per bevande"
|
||||
},
|
||||
"3": {
|
||||
"question": "Riciclo di lattine"
|
||||
},
|
||||
"4": {
|
||||
"question": "Riciclo di abiti"
|
||||
},
|
||||
"5": {
|
||||
"question": "Riciclo di olio da cucina"
|
||||
},
|
||||
"6": {
|
||||
"question": "Riciclo di olio da motore"
|
||||
},
|
||||
"7": {
|
||||
"question": "Riciclo di umido"
|
||||
},
|
||||
"8": {
|
||||
"question": "Riciclo di bottiglie di vetro"
|
||||
},
|
||||
"9": {
|
||||
"question": "Riciclo di vetro"
|
||||
},
|
||||
"10": {
|
||||
"question": "Riciclo di giornali"
|
||||
},
|
||||
"11": {
|
||||
"question": "Riciclo di carta"
|
||||
},
|
||||
"12": {
|
||||
"question": "Riciclo di bottiglie di plastica"
|
||||
},
|
||||
"13": {
|
||||
"question": "Riciclo di confezioni di plastica"
|
||||
},
|
||||
"14": {
|
||||
"question": "Riciclo di plastica"
|
||||
},
|
||||
"15": {
|
||||
"question": "Riciclo di rottami metallici"
|
||||
},
|
||||
"16": {
|
||||
"question": "Riciclo di piccoli elettrodomestici"
|
||||
},
|
||||
"17": {
|
||||
"question": "Riciclo di secco"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Riciclo",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "un contenitore per il riciclo"
|
||||
},
|
||||
"1": {
|
||||
"title": "un centro di riciclo"
|
||||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"container-location": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "E' un contenitore sotterraneo"
|
||||
},
|
||||
"1": {
|
||||
"then": "Questo contenitore è al chiuso"
|
||||
},
|
||||
"2": {
|
||||
"then": "Questo contenitore è all'aperto"
|
||||
}
|
||||
},
|
||||
"question": "Dove si trova questo contenitore?"
|
||||
},
|
||||
"opening_hours": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "24/7"
|
||||
}
|
||||
},
|
||||
"question": "Quali sono gli orari di apertura di questo impianto di raccolta e riciclo?"
|
||||
},
|
||||
"operator": {
|
||||
"question": "Quale azienda gestisce questo impianto di raccolta e riciclo?",
|
||||
"render": "Questa struttura di raccola e riciclo è gestita da {operator}"
|
||||
},
|
||||
"recycling-accepts": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Batterie"
|
||||
},
|
||||
"1": {
|
||||
"then": "Cartoni per bevande"
|
||||
},
|
||||
"2": {
|
||||
"then": "Lattine"
|
||||
},
|
||||
"3": {
|
||||
"then": "Abiti"
|
||||
},
|
||||
"4": {
|
||||
"then": "Olio da cucina"
|
||||
},
|
||||
"5": {
|
||||
"then": "Olio di motore"
|
||||
},
|
||||
"6": {
|
||||
"then": "Verde"
|
||||
},
|
||||
"7": {
|
||||
"then": "Umido"
|
||||
},
|
||||
"8": {
|
||||
"then": "Bottiglie di vetro"
|
||||
},
|
||||
"9": {
|
||||
"then": "Vetro"
|
||||
},
|
||||
"10": {
|
||||
"then": "Giornali"
|
||||
},
|
||||
"11": {
|
||||
"then": "Carta"
|
||||
},
|
||||
"12": {
|
||||
"then": "Bottiglie di platica"
|
||||
},
|
||||
"13": {
|
||||
"then": "Confezioni di plastica"
|
||||
},
|
||||
"14": {
|
||||
"then": "Plastica"
|
||||
},
|
||||
"15": {
|
||||
"then": "Rottami metallici"
|
||||
},
|
||||
"16": {
|
||||
"then": "Scarpe"
|
||||
},
|
||||
"17": {
|
||||
"then": "Piccoli elettrodomestici"
|
||||
},
|
||||
"18": {
|
||||
"then": "Piccoli elettrodomestici"
|
||||
},
|
||||
"19": {
|
||||
"then": "Aghi e oggetti appuntiti"
|
||||
},
|
||||
"20": {
|
||||
"then": "Secco"
|
||||
}
|
||||
},
|
||||
"question": "Cosa si può riciclare qui?"
|
||||
},
|
||||
"recycling-centre-name": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Questo centro raccolta e riciclo rifiuti non ha un nome specifico"
|
||||
}
|
||||
},
|
||||
"question": "Come si chiama questo centro raccolta e riciclo rifiuti?",
|
||||
"render": "Questo centro raccolta e riciclo rifiuti si chiama <b>{name}</b>"
|
||||
},
|
||||
"recycling-type": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Questo è un contenitore per il riciclo di rifiuti"
|
||||
},
|
||||
"1": {
|
||||
"then": "Questo è un centro per la raccola e riciclo di rifiuti"
|
||||
},
|
||||
"2": {
|
||||
"then": "Contenitore per lo smaltimento del secco"
|
||||
}
|
||||
},
|
||||
"question": "Che tipo di raccolta è questo?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Centro di riciclo rifiuti"
|
||||
},
|
||||
"1": {
|
||||
"then": "Centro di riciclo rifiuti"
|
||||
},
|
||||
"2": {
|
||||
"then": "Contenitore per il riciclo"
|
||||
}
|
||||
},
|
||||
"render": "Impianti di riciclo"
|
||||
}
|
||||
},
|
||||
"slow_roads": {
|
||||
"tagRenderings": {
|
||||
"slow_roads-surface": {
|
||||
|
@ -2259,6 +2471,158 @@
|
|||
"render": "Punto panoramico"
|
||||
}
|
||||
},
|
||||
"waste_basket": {
|
||||
"description": "Questo è un cestino dei rifiuti pubblico, un bidone della spazzatura, dove puoi buttare via la tua spazzatura",
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Tutti i tipi"
|
||||
},
|
||||
"1": {
|
||||
"question": "Cestino per sigarette"
|
||||
},
|
||||
"2": {
|
||||
"question": "Cestino per medicinali"
|
||||
},
|
||||
"3": {
|
||||
"question": "Cestino per escrementi dei cani"
|
||||
},
|
||||
"4": {
|
||||
"question": "Cestino per la spazzatura"
|
||||
},
|
||||
"5": {
|
||||
"question": "Cestino dei rifiuti per oggetti taglienti"
|
||||
},
|
||||
"6": {
|
||||
"question": "Cestino per la plastica"
|
||||
}
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Cestino per rifiuti con dispenser per sacchetti per escrementi dei cani"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapRendering": {
|
||||
"0": {
|
||||
"iconSize": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Cestino dei rifiuti"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Cestino dei rifiuti",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "un cestino dei rifiuti"
|
||||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"dispensing_dog_bags": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Questo cestino ha un distributore di sacchetti per escrementi dei cani"
|
||||
},
|
||||
"1": {
|
||||
"then": "Questo cestino <b>non</b> ha un distributore di sacchetti per escrementi dei cani"
|
||||
},
|
||||
"2": {
|
||||
"then": "Questo cestino <b>non</b> ha un distributore di sacchetti per escrementi dei cani"
|
||||
}
|
||||
},
|
||||
"question": "Questo cestino ha un distributore di sacchetti per escrementi dei cani?"
|
||||
},
|
||||
"waste-basket-waste-types": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Un cestino rifiuti per uso generico"
|
||||
},
|
||||
"1": {
|
||||
"then": "Un cestino rifiuti per uso generico"
|
||||
},
|
||||
"2": {
|
||||
"then": "Un cestino rifiuti per escrementi di cani"
|
||||
},
|
||||
"3": {
|
||||
"then": "Un cestino rifiuti per sigarette"
|
||||
},
|
||||
"4": {
|
||||
"then": "Un cestino rifiuti per medicinali"
|
||||
},
|
||||
"5": {
|
||||
"then": "Un cestino rifiuti per aghi e altri oggetti appuntiti"
|
||||
},
|
||||
"6": {
|
||||
"then": "Un cestino rifiuti per la plastica"
|
||||
}
|
||||
},
|
||||
"question": "Che tipo di cestino dei rifiuti è questo?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Cestino dei rifiuti"
|
||||
}
|
||||
},
|
||||
"waste_disposal": {
|
||||
"description": "Cestino per lo smaltimento dei rifiuti, contenitore di dimensioni medio grandi per lo smaltimento dei rifiuti (domestici)",
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Solo accesso pubblico"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Contenitori per la raccolta differenziata",
|
||||
"presets": {
|
||||
"0": {
|
||||
"description": "Cestino di dimensioni medio-grandi per lo smaltimento dei rifiuti (domestici)",
|
||||
"title": "un raccoglitore per lo smaltimento rifiuti"
|
||||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"access": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Questo cestino può essere usato da chiunque"
|
||||
},
|
||||
"1": {
|
||||
"then": "Questo cestino è privato"
|
||||
},
|
||||
"2": {
|
||||
"then": "Questo cestino è solo per residenti"
|
||||
}
|
||||
},
|
||||
"question": "Chi può utilizzare questo cestino per lo smaltimento dei rifiuti?",
|
||||
"render": "Accesso: {access}"
|
||||
},
|
||||
"disposal-location": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Questo è un contenitore sotterraneo"
|
||||
},
|
||||
"1": {
|
||||
"then": "Questo contenitore è al chiuso"
|
||||
},
|
||||
"2": {
|
||||
"then": "Questo contenitore è all'aperto"
|
||||
}
|
||||
},
|
||||
"question": "Dove si trova questo contenitore?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Smaltimento rifiuti"
|
||||
}
|
||||
},
|
||||
"windturbine": {
|
||||
"name": "pala eolica",
|
||||
"presets": {
|
||||
|
|
|
@ -5183,11 +5183,13 @@
|
|||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"2": {
|
||||
"override": {
|
||||
"question": "Wat voor soort winkel is dit?"
|
||||
}
|
||||
},
|
||||
"shops-name": {
|
||||
"question": "Wat is de naam van deze winkel?"
|
||||
},
|
||||
"shops-type-from-id": {
|
||||
"question": "Wat voor soort winkel is dit?"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
|
|
|
@ -953,6 +953,10 @@
|
|||
"description": "A map of public toilets",
|
||||
"title": "Open Toilet Map"
|
||||
},
|
||||
"transit": {
|
||||
"description": "Plan your trip with the help of the public transport system.",
|
||||
"title": "Bus routes"
|
||||
},
|
||||
"trees": {
|
||||
"description": "Map all the trees!",
|
||||
"shortDescription": "Map all the trees",
|
||||
|
|
|
@ -577,6 +577,10 @@
|
|||
"shortDescription": "Mappa tutti gli alberi",
|
||||
"title": "Alberi"
|
||||
},
|
||||
"waste": {
|
||||
"description": "Mappa dei cestini per i rifiuti e i centri di raccolta e riciclo rifiuti.",
|
||||
"title": "Rifiuti"
|
||||
},
|
||||
"waste_basket": {
|
||||
"description": "In questa cartina troverai i cestini dei rifiuti nei tuoi paraggi. Se manca un cestino, puoi inserirlo tu stesso",
|
||||
"shortDescription": "Una cartina dei cestini dei rifiuti",
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"generate:cache:speelplekken": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56",
|
||||
"generate:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre",
|
||||
"generate:cache:natuurpunt:mini": "ts-node scripts/generateCache.ts natuurpunt 12 ../../git/MapComplete-data/natuurpunt_cache_mini/ 51.00792239979105 4.497699737548828 51.0353492224462554 4.539070129394531 --generate-point-overview nature_reserve,visitor_information_centre",
|
||||
"generate:layeroverview": "ts-node scripts/generateLayerOverview.ts --no-fail",
|
||||
"generate:layeroverview": "ts-node scripts/generateLayerOverview.ts",
|
||||
"generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail",
|
||||
"query:licenses": "ts-node scripts/generateLicenseInfo.ts --query",
|
||||
"generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"generate:schemas": "ts2json-schema -p Models/ThemeConfig/Json/ -o Docs/Schemas/ -t tsconfig.json -R . -m \".*ConfigJson\" && ts-node scripts/fixSchemas.ts ",
|
||||
"generate:service-worker": "tsc service-worker.ts && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" service-worker.js",
|
||||
"optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
|
||||
"reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json",
|
||||
"reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json && rm ./asssets/generated/layers/* && rm ./assets/generated/themes/*",
|
||||
"generate": "mkdir -p ./assets/generated; npm run reset:layeroverview; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run generate:licenses; npm run generate:layeroverview; npm run generate:service-worker",
|
||||
"generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -",
|
||||
"prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh",
|
||||
|
|
|
@ -45,13 +45,103 @@ export default class ScriptUtils {
|
|||
|
||||
})
|
||||
}
|
||||
|
||||
private static async DownloadJSON(url: string, headers?: any): Promise<any>{
|
||||
|
||||
public static erasableLog(...text) {
|
||||
process.stdout.write("\r " + text.join(" ") + " \r")
|
||||
}
|
||||
|
||||
public static sleep(ms) {
|
||||
if (ms <= 0) {
|
||||
process.stdout.write("\r \r")
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
process.stdout.write("\r Sleeping for " + (ms / 1000) + "s \r")
|
||||
setTimeout(resolve, 1000);
|
||||
}).then(() => ScriptUtils.sleep(ms - 1000));
|
||||
}
|
||||
|
||||
public static getLayerPaths(): string[] {
|
||||
return ScriptUtils.readDirRecSync("./assets/layers")
|
||||
.filter(path => path.indexOf(".json") > 0)
|
||||
.filter(path => path.indexOf(".proto.json") < 0)
|
||||
.filter(path => path.indexOf("license_info.json") < 0)
|
||||
}
|
||||
|
||||
public static getLayerFiles(): { parsed: LayerConfigJson, path: string }[] {
|
||||
return ScriptUtils.readDirRecSync("./assets/layers")
|
||||
.filter(path => path.indexOf(".json") > 0)
|
||||
.filter(path => path.indexOf(".proto.json") < 0)
|
||||
.filter(path => path.indexOf("license_info.json") < 0)
|
||||
.map(path => {
|
||||
try {
|
||||
const contents = readFileSync(path, "UTF8")
|
||||
if (contents === "") {
|
||||
throw "The file " + path + " is empty, did you properly save?"
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(contents);
|
||||
return {parsed, path}
|
||||
} catch (e) {
|
||||
console.error("Could not parse file ", "./assets/layers/" + path, "due to ", e)
|
||||
throw e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public static getThemePaths(): string[] {
|
||||
return ScriptUtils.readDirRecSync("./assets/themes")
|
||||
.filter(path => path.endsWith(".json") && !path.endsWith(".proto.json"))
|
||||
.filter(path => path.indexOf("license_info.json") < 0)
|
||||
}
|
||||
|
||||
public static getThemeFiles(): { parsed: LayoutConfigJson, path: string }[] {
|
||||
return this.getThemePaths()
|
||||
.map(path => {
|
||||
try {
|
||||
const contents = readFileSync(path, "UTF8");
|
||||
if (contents === "") {
|
||||
throw "The file " + path + " is empty, did you properly save?"
|
||||
}
|
||||
const parsed = JSON.parse(contents);
|
||||
return {parsed: parsed, path: path}
|
||||
} catch (e) {
|
||||
console.error("Could not read file ", path, "due to ", e)
|
||||
throw e
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static TagInfoHistogram(key: string): Promise<{
|
||||
data: { count: number, value: string, fraction: number }[]
|
||||
}> {
|
||||
const url = `https://taginfo.openstreetmap.org/api/4/key/values?key=${key}&filter=all&lang=en&sortname=count&sortorder=desc&page=1&rp=17&qtype=value`
|
||||
return ScriptUtils.DownloadJSON(url)
|
||||
}
|
||||
|
||||
public static async ReadSvg(path: string): Promise<any> {
|
||||
if (!existsSync(path)) {
|
||||
throw "File not found: " + path
|
||||
}
|
||||
const root = await xml2js.parseStringPromise(readFileSync(path, "UTF8"))
|
||||
return root.svg
|
||||
}
|
||||
|
||||
public static async ReadSvgSync(path: string, callback: ((svg: any) => void)): Promise<any> {
|
||||
xml2js.parseString(readFileSync(path, "UTF8"), {async: false}, (err, root) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
callback(root["svg"]);
|
||||
})
|
||||
}
|
||||
|
||||
private static async DownloadJSON(url: string, headers?: any): Promise<any> {
|
||||
const data = await ScriptUtils.Download(url, headers);
|
||||
return JSON.parse(data.content)
|
||||
}
|
||||
|
||||
private static Download(url, headers?: any): Promise<{content: string}> {
|
||||
private static Download(url, headers?: any): Promise<{ content: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
headers = headers ?? {}
|
||||
|
@ -83,84 +173,4 @@ export default class ScriptUtils {
|
|||
|
||||
}
|
||||
|
||||
public static erasableLog(...text) {
|
||||
process.stdout.write("\r " + text.join(" ") + " \r")
|
||||
}
|
||||
|
||||
public static sleep(ms) {
|
||||
if (ms <= 0) {
|
||||
process.stdout.write("\r \r")
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
process.stdout.write("\r Sleeping for " + (ms / 1000) + "s \r")
|
||||
setTimeout(resolve, 1000);
|
||||
}).then(() => ScriptUtils.sleep(ms - 1000));
|
||||
}
|
||||
|
||||
public static getLayerFiles(): { parsed: LayerConfigJson, path: string }[] {
|
||||
return ScriptUtils.readDirRecSync("./assets/layers")
|
||||
.filter(path => path.indexOf(".json") > 0)
|
||||
.filter(path => path.indexOf(".proto.json") < 0)
|
||||
.filter(path => path.indexOf("license_info.json") < 0)
|
||||
.map(path => {
|
||||
try {
|
||||
const contents = readFileSync(path, "UTF8")
|
||||
if (contents === "") {
|
||||
throw "The file " + path + " is empty, did you properly save?"
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(contents);
|
||||
return {parsed, path}
|
||||
} catch (e) {
|
||||
console.error("Could not parse file ", "./assets/layers/" + path, "due to ", e)
|
||||
throw e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public static getThemeFiles(): { parsed: LayoutConfigJson, path: string }[] {
|
||||
return ScriptUtils.readDirRecSync("./assets/themes")
|
||||
.filter(path => path.endsWith(".json") && !path.endsWith(".proto.json"))
|
||||
.filter(path => path.indexOf("license_info.json") < 0)
|
||||
.map(path => {
|
||||
try {
|
||||
const contents = readFileSync(path, "UTF8");
|
||||
if (contents === "") {
|
||||
throw "The file " + path + " is empty, did you properly save?"
|
||||
}
|
||||
const parsed = JSON.parse(contents);
|
||||
return {parsed: parsed, path: path}
|
||||
} catch (e) {
|
||||
console.error("Could not read file ", path, "due to ", e)
|
||||
throw e
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static TagInfoHistogram(key: string): Promise<{
|
||||
data: { count: number, value: string, fraction: number }[]
|
||||
}> {
|
||||
const url = `https://taginfo.openstreetmap.org/api/4/key/values?key=${key}&filter=all&lang=en&sortname=count&sortorder=desc&page=1&rp=17&qtype=value`
|
||||
return ScriptUtils.DownloadJSON(url)
|
||||
}
|
||||
|
||||
public static async ReadSvg(path: string): Promise<any>{
|
||||
if(!existsSync(path)){
|
||||
throw "File not found: "+path
|
||||
}
|
||||
const root = await xml2js.parseStringPromise(readFileSync(path, "UTF8"))
|
||||
return root.svg
|
||||
}
|
||||
|
||||
public static async ReadSvgSync(path: string, callback: ((svg: any) => void)): Promise<any>{
|
||||
xml2js.parseString(readFileSync(path, "UTF8"),{async: false} , (err, root) => {
|
||||
if(err){
|
||||
throw err
|
||||
}
|
||||
callback(root["svg"]);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,9 +10,10 @@ mkdir dist 2> /dev/null
|
|||
mkdir dist/assets 2> /dev/null
|
||||
|
||||
# This script ends every line with '&&' to chain everything. A failure will thus stop the build
|
||||
npm run generate:editor-layer-index
|
||||
npm run generate:editor-layer-index &&
|
||||
npm run reset:layeroverview
|
||||
npm run generate &&
|
||||
npm run generate:layeroverview && # generate:layeroverview has to be run twice: the personal theme won't pick up all the layers otherwise
|
||||
npm run generate:layeroverview --force && # generate:layeroverview has to be run twice: the personal theme won't pick up all the layers otherwise; first time happens in 'npm run generate'
|
||||
npm run test &&
|
||||
npm run generate:layouts
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import ScriptUtils from "./ScriptUtils";
|
||||
import {existsSync, mkdirSync, readFileSync, writeFileSync} from "fs";
|
||||
import {existsSync, mkdirSync, readFileSync, statSync, writeFileSync} from "fs";
|
||||
import * as licenses from "../assets/generated/license_info.json"
|
||||
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
|
||||
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
|
||||
import Constants from "../Models/Constants";
|
||||
import {
|
||||
DoesImageExist,
|
||||
PrevalidateTheme,
|
||||
ValidateLayer,
|
||||
ValidateTagRenderings,
|
||||
|
@ -25,6 +26,51 @@ import {Utils} from "../Utils";
|
|||
|
||||
class LayerOverviewUtils {
|
||||
|
||||
public static readonly layerPath = "./assets/generated/layers/"
|
||||
public static readonly themePath = "./assets/generated/themes/"
|
||||
|
||||
private static publicLayerIdsFrom(themefiles: LayoutConfigJson[]): Set<string> {
|
||||
const publicThemes = [].concat(...themefiles
|
||||
.filter(th => !th.hideFromOverview))
|
||||
|
||||
return new Set([].concat(...publicThemes.map(th => this.extractLayerIdsFrom(th))))
|
||||
}
|
||||
|
||||
private static extractLayerIdsFrom(themeFile: LayoutConfigJson, includeInlineLayers = true): string[] {
|
||||
const publicLayerIds = []
|
||||
for (const publicLayer of themeFile.layers) {
|
||||
if (typeof publicLayer === "string") {
|
||||
publicLayerIds.push(publicLayer)
|
||||
continue
|
||||
}
|
||||
if (publicLayer["builtin"] !== undefined) {
|
||||
const bi = publicLayer["builtin"]
|
||||
if (typeof bi === "string") {
|
||||
publicLayerIds.push(bi)
|
||||
continue
|
||||
}
|
||||
bi.forEach(id => publicLayerIds.push(id))
|
||||
continue
|
||||
}
|
||||
if (includeInlineLayers) {
|
||||
publicLayerIds.push(publicLayer["id"])
|
||||
}
|
||||
}
|
||||
return publicLayerIds
|
||||
}
|
||||
|
||||
shouldBeUpdated(sourcefile: string | string[], targetfile: string): boolean {
|
||||
if (!existsSync(targetfile)) {
|
||||
return true;
|
||||
}
|
||||
const targetModified = statSync(targetfile).mtime
|
||||
if (typeof sourcefile === "string") {
|
||||
sourcefile = [sourcefile]
|
||||
}
|
||||
|
||||
return sourcefile.some(sourcefile => statSync(sourcefile).mtime > targetModified)
|
||||
}
|
||||
|
||||
writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean, mustHaveLanguage: boolean, layers: (LayerConfigJson | string | { builtin })[] }[]) {
|
||||
const perId = new Map<string, any>();
|
||||
for (const theme of themes) {
|
||||
|
@ -69,23 +115,23 @@ class LayerOverviewUtils {
|
|||
}
|
||||
|
||||
writeTheme(theme: LayoutConfigJson) {
|
||||
if (!existsSync("./assets/generated/themes")) {
|
||||
mkdirSync("./assets/generated/themes");
|
||||
if (!existsSync(LayerOverviewUtils.themePath)) {
|
||||
mkdirSync(LayerOverviewUtils.themePath);
|
||||
}
|
||||
writeFileSync(`./assets/generated/themes/${theme.id}.json`, JSON.stringify(theme, null, " "), "UTF8");
|
||||
writeFileSync(`${LayerOverviewUtils.themePath}${theme.id}.json`, JSON.stringify(theme, null, " "), "UTF8");
|
||||
}
|
||||
|
||||
writeLayer(layer: LayerConfigJson) {
|
||||
if (!existsSync("./assets/generated/layers")) {
|
||||
mkdirSync("./assets/generated/layers");
|
||||
if (!existsSync(LayerOverviewUtils.layerPath)) {
|
||||
mkdirSync(LayerOverviewUtils.layerPath);
|
||||
}
|
||||
writeFileSync(`./assets/generated/layers/${layer.id}.json`, JSON.stringify(layer, null, " "), "UTF8");
|
||||
writeFileSync(`${LayerOverviewUtils.layerPath}${layer.id}.json`, JSON.stringify(layer, null, " "), "UTF8");
|
||||
}
|
||||
|
||||
getSharedTagRenderings(knownImagePaths: Set<string>): Map<string, TagRenderingConfigJson> {
|
||||
getSharedTagRenderings(doesImageExist: DoesImageExist): Map<string, TagRenderingConfigJson> {
|
||||
const dict = new Map<string, TagRenderingConfigJson>();
|
||||
|
||||
const validator = new ValidateTagRenderings(undefined, knownImagePaths);
|
||||
|
||||
const validator = new ValidateTagRenderings(undefined, doesImageExist);
|
||||
for (const key in questions["default"]) {
|
||||
if (key === "id") {
|
||||
continue
|
||||
|
@ -93,7 +139,7 @@ class LayerOverviewUtils {
|
|||
questions[key].id = key;
|
||||
questions[key]["source"] = "shared-questions"
|
||||
const config = <TagRenderingConfigJson>questions[key]
|
||||
validator.convertStrict(config, "generate-layer-overview:tagRenderings/questions.json:"+key)
|
||||
validator.convertStrict(config, "generate-layer-overview:tagRenderings/questions.json:" + key)
|
||||
dict.set(key, config)
|
||||
}
|
||||
for (const key in icons["default"]) {
|
||||
|
@ -104,9 +150,9 @@ class LayerOverviewUtils {
|
|||
continue
|
||||
}
|
||||
icons[key].id = key;
|
||||
const config = <TagRenderingConfigJson>icons[key]
|
||||
validator.convertStrict(config, "generate-layer-overview:tagRenderings/icons.json:"+key)
|
||||
dict.set(key,config)
|
||||
const config = <TagRenderingConfigJson>icons[key]
|
||||
validator.convertStrict(config, "generate-layer-overview:tagRenderings/icons.json:" + key)
|
||||
dict.set(key, config)
|
||||
}
|
||||
|
||||
dict.forEach((value, key) => {
|
||||
|
@ -149,16 +195,18 @@ class LayerOverviewUtils {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
main(_: string[]) {
|
||||
main(args: string[]) {
|
||||
|
||||
const forceReload = args.some(a => a == "--force")
|
||||
|
||||
const licensePaths = new Set<string>()
|
||||
for (const i in licenses) {
|
||||
licensePaths.add(licenses[i].path)
|
||||
}
|
||||
|
||||
const sharedLayers = this.buildLayerIndex(licensePaths);
|
||||
const sharedThemes = this.buildThemeIndex(licensePaths, sharedLayers)
|
||||
const doesImageExist = new DoesImageExist(licensePaths, existsSync)
|
||||
const sharedLayers = this.buildLayerIndex(doesImageExist, forceReload);
|
||||
const recompiledThemes : string[] = []
|
||||
const sharedThemes = this.buildThemeIndex(doesImageExist, sharedLayers, recompiledThemes, forceReload)
|
||||
|
||||
writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({
|
||||
"layers": Array.from(sharedLayers.values()),
|
||||
|
@ -168,7 +216,7 @@ class LayerOverviewUtils {
|
|||
writeFileSync("./assets/generated/known_layers.json", JSON.stringify({layers: Array.from(sharedLayers.values())}))
|
||||
|
||||
|
||||
{
|
||||
if(recompiledThemes.length > 0) {
|
||||
// mapcomplete-changes shows an icon for each corresponding mapcomplete-theme
|
||||
const iconsPerTheme =
|
||||
Array.from(sharedThemes.values()).map(th => ({
|
||||
|
@ -188,28 +236,42 @@ class LayerOverviewUtils {
|
|||
console.log(green("All done!"))
|
||||
}
|
||||
|
||||
private buildLayerIndex(knownImagePaths: Set<string>): Map<string, LayerConfigJson> {
|
||||
private buildLayerIndex(doesImageExist: DoesImageExist, forceReload: boolean): Map<string, LayerConfigJson> {
|
||||
// First, we expand and validate all builtin layers. These are written to assets/generated/layers
|
||||
// At the same time, an index of available layers is built.
|
||||
console.log(" ---------- VALIDATING BUILTIN LAYERS ---------")
|
||||
|
||||
const sharedTagRenderings = this.getSharedTagRenderings(knownImagePaths);
|
||||
const layerFiles = ScriptUtils.getLayerFiles();
|
||||
const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist);
|
||||
const sharedLayers = new Map<string, LayerConfigJson>()
|
||||
const state: DesugaringContext = {
|
||||
tagRenderings: sharedTagRenderings,
|
||||
sharedLayers
|
||||
}
|
||||
const prepLayer = new PrepareLayer(state);
|
||||
for (const sharedLayerJson of layerFiles) {
|
||||
const context = "While building builtin layer " + sharedLayerJson.path
|
||||
const fixed = prepLayer.convertStrict(sharedLayerJson.parsed, context)
|
||||
const skippedLayers: string[] = []
|
||||
const recompiledLayers: string[] = []
|
||||
for (const sharedLayerPath of ScriptUtils.getLayerPaths()) {
|
||||
|
||||
{
|
||||
const targetPath = LayerOverviewUtils.layerPath + sharedLayerPath.substring(sharedLayerPath.lastIndexOf("/"))
|
||||
if (!forceReload && !this.shouldBeUpdated(sharedLayerPath, targetPath)) {
|
||||
const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8"))
|
||||
sharedLayers.set(sharedLayer.id, sharedLayer)
|
||||
skippedLayers.push(sharedLayer.id)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(fixed.source.osmTags["and"] === undefined){
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(readFileSync(sharedLayerPath, "utf8"))
|
||||
const context = "While building builtin layer " + sharedLayerPath
|
||||
const fixed = prepLayer.convertStrict(parsed, context)
|
||||
|
||||
if (fixed.source.osmTags["and"] === undefined) {
|
||||
fixed.source.osmTags = {"and": [fixed.source.osmTags]}
|
||||
}
|
||||
|
||||
const validator = new ValidateLayer(sharedLayerJson.path, true, knownImagePaths);
|
||||
|
||||
const validator = new ValidateLayer(sharedLayerPath, true, doesImageExist);
|
||||
validator.convertStrict(fixed, context)
|
||||
|
||||
if (sharedLayers.has(fixed.id)) {
|
||||
|
@ -217,39 +279,18 @@ class LayerOverviewUtils {
|
|||
}
|
||||
|
||||
sharedLayers.set(fixed.id, fixed)
|
||||
recompiledLayers.push(fixed.id)
|
||||
|
||||
this.writeLayer(fixed)
|
||||
|
||||
}
|
||||
|
||||
console.log("Recompiled layers " + recompiledLayers.join(", ") + " and skipped " + skippedLayers.length + " layers")
|
||||
|
||||
return sharedLayers;
|
||||
}
|
||||
|
||||
private static publicLayerIdsFrom(themefiles: LayoutConfigJson[]): Set<string> {
|
||||
const publicLayers = [].concat(...themefiles
|
||||
.filter(th => !th.hideFromOverview)
|
||||
.map(th => th.layers))
|
||||
|
||||
const publicLayerIds = new Set<string>()
|
||||
for (const publicLayer of publicLayers) {
|
||||
if (typeof publicLayer === "string") {
|
||||
publicLayerIds.add(publicLayer)
|
||||
continue
|
||||
}
|
||||
if (publicLayer["builtin"] !== undefined) {
|
||||
const bi = publicLayer["builtin"]
|
||||
if (typeof bi === "string") {
|
||||
publicLayerIds.add(bi)
|
||||
continue
|
||||
}
|
||||
bi.forEach(id => publicLayerIds.add(id))
|
||||
continue
|
||||
}
|
||||
publicLayerIds.add(publicLayer.id)
|
||||
}
|
||||
return publicLayerIds
|
||||
}
|
||||
|
||||
private buildThemeIndex(knownImagePaths: Set<string>, sharedLayers: Map<string, LayerConfigJson>): Map<string, LayoutConfigJson> {
|
||||
private buildThemeIndex(doesImageExist: DoesImageExist, sharedLayers: Map<string, LayerConfigJson>, recompiledThemes: string[], forceReload: boolean): Map<string, LayoutConfigJson> {
|
||||
console.log(" ---------- VALIDATING BUILTIN THEMES ---------")
|
||||
const themeFiles = ScriptUtils.getThemeFiles();
|
||||
const fixed = new Map<string, LayoutConfigJson>();
|
||||
|
@ -258,23 +299,33 @@ class LayerOverviewUtils {
|
|||
|
||||
const convertState: DesugaringContext = {
|
||||
sharedLayers,
|
||||
tagRenderings: this.getSharedTagRenderings(knownImagePaths),
|
||||
tagRenderings: this.getSharedTagRenderings(doesImageExist),
|
||||
publicLayers
|
||||
}
|
||||
const nonDefaultLanguages : {theme: string, language: string}[] = []
|
||||
const skippedThemes: string[] = []
|
||||
for (const themeInfo of themeFiles) {
|
||||
|
||||
const themePath = themeInfo.path;
|
||||
let themeFile = themeInfo.parsed
|
||||
const themePath = themeInfo.path
|
||||
|
||||
{
|
||||
const targetPath = LayerOverviewUtils.themePath + "/" + themePath.substring(themePath.lastIndexOf("/"))
|
||||
const usedLayers = Array.from(LayerOverviewUtils.extractLayerIdsFrom(themeFile, false))
|
||||
.map(id => LayerOverviewUtils.layerPath + id + ".json")
|
||||
if (!forceReload && !this.shouldBeUpdated([themePath, ...usedLayers], targetPath)) {
|
||||
fixed.set(themeFile.id, JSON.parse(readFileSync(LayerOverviewUtils.themePath+themeFile.id+".json", 'utf8')))
|
||||
skippedThemes.push(themeFile.id)
|
||||
continue;
|
||||
}
|
||||
recompiledThemes.push(themeFile.id)
|
||||
}
|
||||
|
||||
new PrevalidateTheme().convertStrict(themeFile, themePath)
|
||||
try {
|
||||
|
||||
themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath)
|
||||
|
||||
if (knownImagePaths === undefined) {
|
||||
throw "Could not load known images/licenses"
|
||||
}
|
||||
new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings)
|
||||
new ValidateThemeAndLayers(doesImageExist, themePath, true, convertState.tagRenderings)
|
||||
.convertStrict(themeFile, themePath)
|
||||
|
||||
this.writeTheme(themeFile)
|
||||
|
@ -293,6 +344,9 @@ class LayerOverviewUtils {
|
|||
mustHaveLanguage: t.mustHaveLanguage?.length > 0,
|
||||
}
|
||||
}));
|
||||
|
||||
console.log("Recompiled themes " + recompiledThemes.join(", ") + " and skipped " + skippedThemes.length + " themes")
|
||||
|
||||
return fixed;
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
|
|||
import TagRenderingQuestion from "../../../UI/Popup/TagRenderingQuestion";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import { expect } from 'chai';
|
||||
import Locale from "../../../UI/i18n/Locale";
|
||||
|
||||
describe("TagRenderingQuestion", () => {
|
||||
|
||||
|
@ -27,6 +28,7 @@ describe("TagRenderingQuestion", () => {
|
|||
|
||||
it("should have a freeform text field with a type explanation", () => {
|
||||
|
||||
Locale.language.setData("en")
|
||||
const configJson = <TagRenderingConfigJson>{
|
||||
id: "test-tag-rendering",
|
||||
question: "Question?",
|
||||
|
|
|
@ -28,23 +28,28 @@ function initDownloads(query: string){
|
|||
describe("GenerateCache", () => {
|
||||
|
||||
it("should generate a cached file for the Natuurpunt-theme", async () => {
|
||||
if (existsSync("/tmp/np-cache")) {
|
||||
ScriptUtils.readDirRecSync("/tmp/np-cache").forEach(p => unlinkSync(p))
|
||||
rmdirSync("/tmp/np-cache")
|
||||
// We use /var/tmp instead of /tmp, as more OS's (such as MAC) have this
|
||||
const dir = "/var/tmp/"
|
||||
if(!existsSync(dir)){
|
||||
console.log("Not executing caching test: no temp directory found")
|
||||
}
|
||||
mkdirSync("/tmp/np-cache")
|
||||
if (existsSync(dir+"/np-cache")) {
|
||||
ScriptUtils.readDirRecSync(dir+"np-cache").forEach(p => unlinkSync(p))
|
||||
rmdirSync(dir+"np-cache")
|
||||
}
|
||||
mkdirSync(dir+"np-cache")
|
||||
initDownloads(
|
||||
"(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22man_made%22%3D%22watermill%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*foot.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*hiking.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*bycicle.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*horse.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B"
|
||||
);
|
||||
await main([
|
||||
"natuurpunt",
|
||||
"12",
|
||||
"/tmp/np-cache",
|
||||
dir+"np-cache",
|
||||
"51.15423567022531", "3.250579833984375", "51.162821593316934", "3.262810707092285",
|
||||
"--generate-point-overview", "nature_reserve,visitor_information_centre"
|
||||
])
|
||||
await ScriptUtils.sleep(500)
|
||||
const birdhides = JSON.parse(readFileSync("/tmp/np-cache/natuurpunt_birdhide_12_2085_1368.geojson", "UTF8"))
|
||||
const birdhides = JSON.parse(readFileSync(dir+"np-cache/natuurpunt_birdhide_12_2085_1368.geojson", "UTF8"))
|
||||
expect(birdhides.features.length).deep.equal(5)
|
||||
expect(birdhides.features.some(f => f.properties.id === "node/5158056232"), "Didn't find birdhide node/5158056232 ").true
|
||||
|
||||
|
|
Loading…
Reference in a new issue