forked from MapComplete/MapComplete
		
	Scripts: better output of taginfo project files: add link to docs, add some icons, merge entries to be a bit less spammy
This commit is contained in:
		
							parent
							
								
									fd0e6fc535
								
							
						
					
					
						commit
						3ecbf1163c
					
				
					 1 changed files with 125 additions and 79 deletions
				
			
		|  | @ -9,49 +9,57 @@ import { Utils } from "../src/Utils" | ||||||
| /** | /** | ||||||
|  * Generates all the files in "Docs/TagInfo". These are picked up by the taginfo project, showing a link to the mapcomplete theme if the key is used |  * Generates all the files in "Docs/TagInfo". These are picked up by the taginfo project, showing a link to the mapcomplete theme if the key is used | ||||||
|  */ |  */ | ||||||
|  | interface TagInfoEntry { | ||||||
|  |     key: string | ||||||
|  |     description: string | ||||||
|  |     value?: string, | ||||||
|  |     icon_url?: string, | ||||||
|  |     doc_url?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface TagInfoProjectFile { | ||||||
|  |     // data format version, currently always 1, will get updated if there are incompatible changes to the format (required)
 | ||||||
|  |     data_format: 1, | ||||||
|  |     // timestamp when project file was updated is not given as it pollutes the github history
 | ||||||
|  |     project: { | ||||||
|  |         name: string, // name of the project (required)
 | ||||||
|  |         description: string, // short description of the project (required)
 | ||||||
|  |         project_url: string, // home page of the project with general information (required)
 | ||||||
|  |         doc_url: string // documentation of the project and especially the tags used (optional)
 | ||||||
|  |         icon_url: string, // project logo, should work in 16x16 pixels on white and light gray backgrounds (optional)
 | ||||||
|  |         contact_name: string, // contact name, needed for taginfo maintainer (required)
 | ||||||
|  |         contact_email: string // contact email, needed for taginfo maintainer (required)
 | ||||||
|  |     }, | ||||||
|  |     tags: TagInfoEntry[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface TagInfoPrototype { | ||||||
|  |     key: string, | ||||||
|  |     value?: string, | ||||||
|  |     shownText: string, | ||||||
|  |     layerName: string, | ||||||
|  |     layer: LayerConfig, | ||||||
|  |     icon?: string | ||||||
|  |     emoji?: string | ||||||
|  |     trid?: string | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| const outputDirectory = "Docs/TagInfo" | const outputDirectory = "Docs/TagInfo" | ||||||
| 
 | 
 | ||||||
| function generateTagOverview( | function generateLayerUsage(layer: LayerConfig): TagInfoPrototype[] { | ||||||
|     kv: { k: string; v: string }, |  | ||||||
|     description: string |  | ||||||
| ): { |  | ||||||
|     key: string |  | ||||||
|     description: string |  | ||||||
|     value?: string |  | ||||||
| } { |  | ||||||
|     const overview = { |  | ||||||
|         // OSM tag key (required)
 |  | ||||||
|         key: kv.k, |  | ||||||
|         description: description, |  | ||||||
|         value: undefined, |  | ||||||
|     } |  | ||||||
|     if (kv.v !== undefined) { |  | ||||||
|         // OSM tag value (optional, if not supplied it means "all values")
 |  | ||||||
|         overview.value = kv.v |  | ||||||
|     } |  | ||||||
|     return overview |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function generateLayerUsage(layer: LayerConfig, layout: ThemeConfig): any[] { |  | ||||||
|     if (layer.name === undefined) { |     if (layer.name === undefined) { | ||||||
|         return [] // Probably a duplicate or irrelevant layer
 |         return [] // Probably a duplicate or irrelevant layer
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const usedTags = layer.source.osmTags.asChange({}) |     const usedTags = layer.source.osmTags.asChange({}) | ||||||
|     const result: { |     const result: TagInfoPrototype[] = [] | ||||||
|         key: string |     const layerName = layer.name.txt | ||||||
|         description: string |  | ||||||
|         value?: string |  | ||||||
|     }[] = [] |  | ||||||
|     for (const kv of usedTags) { |     for (const kv of usedTags) { | ||||||
|         const description = |         result.push({ | ||||||
|             "The MapComplete theme " + |             key: kv.k, value: kv.v, layerName, | ||||||
|             layout.title.txt + |             shownText: "Features with this tag are displayed", | ||||||
|             " has a layer " + |             layer | ||||||
|             layer.name.txt + |         }) | ||||||
|             " showing features with this tag" |  | ||||||
|         result.push(generateTagOverview(kv, description)) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (const tr of layer.tagRenderings) { |     for (const tr of layer.tagRenderings) { | ||||||
|  | @ -66,55 +74,47 @@ function generateLayerUsage(layer: LayerConfig, layout: ThemeConfig): any[] { | ||||||
|             const usesImageUpload = (tr.render?.txt?.indexOf("image_upload") ?? -2) > 0 |             const usesImageUpload = (tr.render?.txt?.indexOf("image_upload") ?? -2) > 0 | ||||||
| 
 | 
 | ||||||
|             if (usesImageCarousel || usesImageUpload) { |             if (usesImageCarousel || usesImageUpload) { | ||||||
|                 const descrNoUpload = `The layer '${layer.name.txt} shows images based on the keys image, image:0, image:1,..., panoramax, panoramax:0, panoramx:1, ... ,  wikidata, wikipedia, wikimedia_commons and mapillary` |                 const descrNoUpload = `Images  are displayed based on the keys image, image:0, image:1,..., panoramax, panoramax:0, panoramx:1, ... ,  wikidata, wikipedia, wikimedia_commons and mapillary` | ||||||
|                 const descrUpload = `The layer '${layer.name.txt} allows to upload images and adds them under the 'panoramax'-tag (and panoramax:0, panoramax:1, ... for multiple images). Furthermore, this layer shows images based on the keys panoramax, image, wikidata, wikipedia, wikimedia_commons and mapillary` |                 const descrUpload = `${descrNoUpload} Furthermore, this layer shows images based on the keys panoramax, image, wikidata, wikipedia, wikimedia_commons and mapillary` | ||||||
| 
 | 
 | ||||||
|                 const descr = (usesImageUpload ? descrUpload : descrNoUpload) + condition |                 const shownText = (usesImageUpload ? descrUpload : descrNoUpload) + condition | ||||||
| 
 |                 const keys = ["image", "panoramax", "mapillary", "wikidata", "wikipedia"] | ||||||
|                 result.push(generateTagOverview({ k: "image", v: undefined }, descr)) |                 for (const key of keys) { | ||||||
|                 result.push(generateTagOverview({ k: "panoramax", v: undefined }, descr)) |                     result.push({ | ||||||
| 
 |                         key, shownText, layerName, layer, emoji: "📷", trid: "images" | ||||||
|                 result.push(generateTagOverview({ k: "mapillary", v: undefined }, descr)) |                     }) | ||||||
|                 result.push(generateTagOverview({ k: "wikidata", v: undefined }, descr)) |                 } | ||||||
|                 result.push(generateTagOverview({ k: "wikipedia", v: undefined }, descr)) |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const q = tr.question?.txt |         const q = tr.question?.txt | ||||||
|         const key = tr.freeform?.key |         const key = tr.freeform?.key | ||||||
|         if (key != undefined) { |         if (key != undefined) { | ||||||
|             let descr = `Layer '${layer.name.txt}'` |             let descr = "Values of `" + key + "` are shown with \"" + tr.render.txt + "\"" | ||||||
|             if (q == undefined) { |             if (q != undefined) { | ||||||
|                 descr += " shows values with" |                 descr += " and can be updated. The question is \"" + q + "\"" | ||||||
|             } else { |  | ||||||
|                 descr += " shows and asks freeform values for" |  | ||||||
|             } |             } | ||||||
|             descr += ` key '${key}' (in the mapcomplete.org theme '${layout.title.txt}')` |             result.push(({ | ||||||
|             result.push(generateTagOverview({ k: key, v: undefined }, descr + condition)) |                 key, layerName, shownText: descr, | ||||||
|  |                 layer, | ||||||
|  |                 icon: !Utils.isEmoji(tr.renderIcon) ? tr.renderIcon : undefined, | ||||||
|  |                 emoji: Utils.isEmoji(tr.renderIcon) ? tr.renderIcon : undefined, | ||||||
|  |                 trid: tr.id | ||||||
|  |             })) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const mappings = tr.mappings ?? [] |         for (const mapping of tr.mappings ?? []) { | ||||||
|         for (const mapping of mappings) { |  | ||||||
|             let descr = "Layer '" + layer.name.txt + "'" |  | ||||||
|             descr += |  | ||||||
|                 " shows " + |  | ||||||
|                 mapping.if.asHumanString(false, false, {}) + |  | ||||||
|                 " with a fixed text, namely '" + |  | ||||||
|                 mapping.then.txt + |  | ||||||
|                 "'" |  | ||||||
|             if ( |  | ||||||
|                 q != undefined && |  | ||||||
|                 mapping.hideInAnswer != true // != true will also match if a
 |  | ||||||
|             ) { |  | ||||||
|                 descr += " and allows to pick this as a default answer" |  | ||||||
|             } |  | ||||||
|             descr += ` (in the mapcomplete.org theme '${layout.title.txt}')` |  | ||||||
|             for (const kv of mapping.if.asChange({})) { |             for (const kv of mapping.if.asChange({})) { | ||||||
|                 let d = descr |                 result.push({ | ||||||
|                 if (q != undefined && kv.v == "") { |                     key: kv.k, | ||||||
|                     d = `${descr} Picking this answer will delete the key ${kv.k}.` |                     value: kv.v, | ||||||
|                 } |                     layerName, | ||||||
|                 result.push(generateTagOverview(kv, d + condition)) |                     layer, | ||||||
|  |                     shownText: `${mapping.if.asHumanString()} is displayed as "${mapping.then.txt}"`, | ||||||
|  |                     icon: !Utils.isEmoji(mapping.icon) ? mapping.icon : undefined, | ||||||
|  |                     emoji: Utils.isEmoji(mapping.icon) ? mapping.icon : undefined, | ||||||
|  |                     trid: tr.id | ||||||
|  |                 }) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -126,8 +126,8 @@ function generateLayerUsage(layer: LayerConfig, layout: ThemeConfig): any[] { | ||||||
|  * Generates the JSON-object representing the theme for inclusion on taginfo |  * Generates the JSON-object representing the theme for inclusion on taginfo | ||||||
|  * @param layout |  * @param layout | ||||||
|  */ |  */ | ||||||
| function generateTagInfoEntry(layout: ThemeConfig): any { | function generateTagInfoEntry(layout: ThemeConfig): string { | ||||||
|     const usedTags = [] |     const usedTags: TagInfoPrototype[] = [] | ||||||
|     for (const layer of layout.layers) { |     for (const layer of layout.layers) { | ||||||
|         if (layer.source === null) { |         if (layer.source === null) { | ||||||
|             continue |             continue | ||||||
|  | @ -135,7 +135,7 @@ function generateTagInfoEntry(layout: ThemeConfig): any { | ||||||
|         if (layer.source.geojsonSource !== undefined && layer.source.isOsmCacheLayer !== true) { |         if (layer.source.geojsonSource !== undefined && layer.source.isOsmCacheLayer !== true) { | ||||||
|             continue |             continue | ||||||
|         } |         } | ||||||
|         usedTags.push(...generateLayerUsage(layer, layout)) |         usedTags.push(...generateLayerUsage(layer)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (usedTags.length == 0) { |     if (usedTags.length == 0) { | ||||||
|  | @ -147,7 +147,53 @@ function generateTagInfoEntry(layout: ThemeConfig): any { | ||||||
|         icon = icon.substring(2) |         icon = icon.substring(2) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const themeInfo = { | 
 | ||||||
|  |     const merged: Map<string, TagInfoPrototype[]> = new Map<string, TagInfoPrototype[]>() | ||||||
|  |     for (const entry of usedTags) { | ||||||
|  |         const key = entry.key + ";" + (entry.value ?? "") + ";" + entry.shownText | ||||||
|  |         let entries = merged.get(key) | ||||||
|  |         if (!entries) { | ||||||
|  |             entries = [] | ||||||
|  |             merged.set(key, entries) | ||||||
|  |         } | ||||||
|  |         entries.push(entry) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const entries: TagInfoEntry[] = [] | ||||||
|  | 
 | ||||||
|  |     const repo = "https://source.mapcomplete.org/MapComplete/MapComplete/" | ||||||
|  |     const branchBase = repo + "src/branch/develop/" | ||||||
|  |     Array.from(merged.values()).forEach((prototypes: TagInfoPrototype[]) => { | ||||||
|  |         // We use a prototype without condition, as this has a higher chance of being the "root"-layer
 | ||||||
|  |         const p = prototypes[0] | ||||||
|  |         const layers = prototypes.map(p => p.layerName) | ||||||
|  | 
 | ||||||
|  |         let layerDescr = `layers ${layers.join(", ")}` | ||||||
|  |         if (layers.length === 1) { | ||||||
|  |             layerDescr = `layer ${layers[0]}` | ||||||
|  |         } | ||||||
|  |         let doc_url = branchBase + "Docs/Layers/" + p.layer.id + ".md" | ||||||
|  |         if (p.trid) { | ||||||
|  |             doc_url += "#" + p.trid.replace(/[^a-zA-Z0-9]/g, "_") | ||||||
|  |         } | ||||||
|  |         let defaultIcon = undefined | ||||||
|  |         if (p.layer.hasDefaultIcon()) { | ||||||
|  |             defaultIcon = p.layer.mapRendering.map(pr => pr.marker?.at(-1)?.icon?.render?.txt).find(x => x !== undefined) | ||||||
|  |         } | ||||||
|  |         let value = p.value | ||||||
|  |         if (value === "") { | ||||||
|  |             value = undefined | ||||||
|  |         } | ||||||
|  |         entries.push({ | ||||||
|  |             key: p.key, | ||||||
|  |             value, | ||||||
|  |             description: p.shownText + " by " + layerDescr, | ||||||
|  |             doc_url, | ||||||
|  |             icon_url: p.icon ?? defaultIcon | ||||||
|  |         }) | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     const themeInfo: TagInfoProjectFile = { | ||||||
|         // data format version, currently always 1, will get updated if there are incompatible changes to the format (required)
 |         // data format version, currently always 1, will get updated if there are incompatible changes to the format (required)
 | ||||||
|         data_format: 1, |         data_format: 1, | ||||||
|         // timestamp when project file was updated is not given as it pollutes the github history
 |         // timestamp when project file was updated is not given as it pollutes the github history
 | ||||||
|  | @ -156,12 +202,12 @@ function generateTagInfoEntry(layout: ThemeConfig): any { | ||||||
|             description: layout.shortDescription.txt, // short description of the project (required)
 |             description: layout.shortDescription.txt, // short description of the project (required)
 | ||||||
|             project_url: "https://mapcomplete.org/" + layout.id, // home page of the project with general information (required)
 |             project_url: "https://mapcomplete.org/" + layout.id, // home page of the project with general information (required)
 | ||||||
|             doc_url: |             doc_url: | ||||||
|                 "https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/Docs/Themes", // documentation of the project and especially the tags used (optional)
 |                 repo + "src/branch/develop/Docs/Themes", // documentation of the project and especially the tags used (optional)
 | ||||||
|             icon_url: "https://mapcomplete.org/" + icon, // project logo, should work in 16x16 pixels on white and light gray backgrounds (optional)
 |             icon_url: "https://mapcomplete.org/" + icon, // project logo, should work in 16x16 pixels on white and light gray backgrounds (optional)
 | ||||||
|             contact_name: "Pieter Vander Vennet", // contact name, needed for taginfo maintainer (required)
 |             contact_name: "Pieter Vander Vennet", // contact name, needed for taginfo maintainer (required)
 | ||||||
|             contact_email: "pietervdvn@posteo.net", // contact email, needed for taginfo maintainer (required)
 |             contact_email: "info@mapcomplete.org" // contact email, needed for taginfo maintainer (required)
 | ||||||
|         }, |         }, | ||||||
|         tags: usedTags, |         tags: entries | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const filename = "mapcomplete_" + layout.id |     const filename = "mapcomplete_" + layout.id | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue