forked from MapComplete/MapComplete
Generate layer overview now only recompiles files that need to be recompiled
This commit is contained in:
parent
063d7e4637
commit
8c036e159f
4 changed files with 204 additions and 137 deletions
|
@ -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",
|
||||
|
|
|
@ -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"]);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ 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 &&
|
||||
npm run generate:layeroverview && # generate:layeroverview has to be run twice: the personal theme won't pick up all the layers otherwise
|
||||
ts-node ./scripts/generateLayeroverview --force && # generate:layeroverview has to be run twice: the personal theme won't pick up all the layers otherwise
|
||||
npm run test &&
|
||||
npm run generate:layouts
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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";
|
||||
|
@ -26,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) {
|
||||
|
@ -70,22 +115,22 @@ 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(doesImageExist: DoesImageExist): Map<string, TagRenderingConfigJson> {
|
||||
const dict = new Map<string, TagRenderingConfigJson>();
|
||||
|
||||
|
||||
const validator = new ValidateTagRenderings(undefined, doesImageExist);
|
||||
for (const key in questions["default"]) {
|
||||
if (key === "id") {
|
||||
|
@ -94,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"]) {
|
||||
|
@ -105,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) => {
|
||||
|
@ -150,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 doesImageExist = new DoesImageExist(licensePaths, existsSync)
|
||||
const sharedLayers = this.buildLayerIndex(doesImageExist);
|
||||
const sharedThemes = this.buildThemeIndex(doesImageExist, sharedLayers)
|
||||
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()),
|
||||
|
@ -169,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 => ({
|
||||
|
@ -189,28 +236,42 @@ class LayerOverviewUtils {
|
|||
console.log(green("All done!"))
|
||||
}
|
||||
|
||||
private buildLayerIndex(doesImageExist: DoesImageExist): 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(doesImageExist);
|
||||
const layerFiles = ScriptUtils.getLayerFiles();
|
||||
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, doesImageExist);
|
||||
|
||||
const validator = new ValidateLayer(sharedLayerPath, true, doesImageExist);
|
||||
validator.convertStrict(fixed, context)
|
||||
|
||||
if (sharedLayers.has(fixed.id)) {
|
||||
|
@ -218,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(doesImageExist: DoesImageExist, 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>();
|
||||
|
@ -262,9 +302,23 @@ class LayerOverviewUtils {
|
|||
tagRenderings: this.getSharedTagRenderings(doesImageExist),
|
||||
publicLayers
|
||||
}
|
||||
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, themeFile)
|
||||
skippedThemes.push(themeFile.id)
|
||||
continue;
|
||||
}
|
||||
recompiledThemes.push(themeFile.id)
|
||||
}
|
||||
|
||||
new PrevalidateTheme().convertStrict(themeFile, themePath)
|
||||
try {
|
||||
|
@ -290,6 +344,9 @@ class LayerOverviewUtils {
|
|||
mustHaveLanguage: t.mustHaveLanguage?.length > 0,
|
||||
}
|
||||
}));
|
||||
|
||||
console.log("Recompiled themes " + recompiledThemes.join(", ") + " and skipped " + skippedThemes.length + " themes")
|
||||
|
||||
return fixed;
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue