| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  | import Script from "./Script" | 
					
						
							| 
									
										
										
										
											2025-01-27 03:51:21 +01:00
										 |  |  | import { CommunityResource } from "../src/Logic/Web/CommunityIndex" | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  | import { Utils } from "../src/Utils" | 
					
						
							|  |  |  | import { FeatureCollection, MultiPolygon, Polygon } from "geojson" | 
					
						
							| 
									
										
										
										
											2025-01-27 03:14:19 +01:00
										 |  |  | import { existsSync, mkdirSync, writeFileSync } from "fs" | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  | import { GeoOperations } from "../src/Logic/GeoOperations" | 
					
						
							|  |  |  | import { Tiles } from "../src/Models/TileRange" | 
					
						
							|  |  |  | import ScriptUtils from "./ScriptUtils" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class DownloadCommunityIndex extends Script { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     constructor() { | 
					
						
							|  |  |  |         super("Updates the community index") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     printHelp() { | 
					
						
							|  |  |  |         console.log("Arguments are:\noutputdirectory") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static targetZoomlevel: number = 6 | 
					
						
							|  |  |  |     private static upstreamUrl: string = "https://raw.githubusercontent.com/osmlab/osm-community-index/main/dist/" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Prunes away unnecessary fields from a CommunityResource | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static stripResource(r: Readonly<CommunityResource>): CommunityResource { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             id: r.id, | 
					
						
							|  |  |  |             languageCodes: r.languageCodes, | 
					
						
							|  |  |  |             account: r.account, | 
					
						
							|  |  |  |             type: r.type, | 
					
						
							|  |  |  |             resolved: { | 
					
						
							|  |  |  |                 name: r.resolved.name, | 
					
						
							|  |  |  |                 description: r.resolved.description, | 
					
						
							|  |  |  |                 url: r.resolved.url | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static stripResourcesObj(resources: Readonly<Record<string, Readonly<CommunityResource>>>) { | 
					
						
							|  |  |  |         const stripped: Record<string, CommunityResource> = {} | 
					
						
							|  |  |  |         for (const k in resources) { | 
					
						
							| 
									
										
										
										
											2025-01-27 04:02:03 +01:00
										 |  |  |             const type = resources[k].type | 
					
						
							|  |  |  |             if (type === "twitter" || type === "facebook" || type === "x") { | 
					
						
							| 
									
										
										
										
											2025-01-27 03:47:50 +01:00
										 |  |  |                 // These channels are toxic nowadays - we simply omit them
 | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |             stripped[k] = DownloadCommunityIndex.stripResource(resources[k]) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return stripped | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static async update(targetDirectory: string) { | 
					
						
							|  |  |  |         const data = await Utils.downloadJson<FeatureCollection<Polygon | MultiPolygon, { | 
					
						
							|  |  |  |             resources: Record<string, CommunityResource>, | 
					
						
							|  |  |  |             nameEn: string, | 
					
						
							|  |  |  |             id: string | 
					
						
							|  |  |  |         }>>(DownloadCommunityIndex.upstreamUrl + "completeFeatureCollection.json" | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2025-01-27 03:14:19 +01:00
										 |  |  |         if (!existsSync(targetDirectory)) { | 
					
						
							|  |  |  |             mkdirSync(targetDirectory) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |         const features = data.features | 
					
						
							|  |  |  |         const global = features.find( | 
					
						
							|  |  |  |             f => f.id === "Q2" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         const globalProperties = DownloadCommunityIndex.stripResourcesObj(global.properties.resources) | 
					
						
							|  |  |  |         writeFileSync(targetDirectory + "/global.json", JSON.stringify(globalProperties), "utf8") | 
					
						
							|  |  |  |         console.log("Written global properties") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const types = new Set<string>() | 
					
						
							|  |  |  |         for (const f of features) { | 
					
						
							|  |  |  |             const res = f.properties.resources | 
					
						
							|  |  |  |             for (const k in res) { | 
					
						
							|  |  |  |                 types.add(res[k].type) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (const type of types) { | 
					
						
							|  |  |  |             const url = `${DownloadCommunityIndex.upstreamUrl}img/${type}.svg` | 
					
						
							|  |  |  |             await ScriptUtils.DownloadFileTo(url, `${targetDirectory}/${type}.svg`) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const local = features.filter(f => f.id !== "Q2") | 
					
						
							|  |  |  |         const spread = GeoOperations.spreadIntoBboxes(local, DownloadCommunityIndex.targetZoomlevel) | 
					
						
							|  |  |  |         let written = 0 | 
					
						
							|  |  |  |         let skipped = 0 | 
					
						
							| 
									
										
										
										
											2025-01-27 03:14:19 +01:00
										 |  |  |         const writtenTilesOverview: Record<number, number[]> = {} | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |         writeFileSync(targetDirectory + "local.geojson", JSON.stringify({ type: "FeatureCollection", features: local })) | 
					
						
							|  |  |  |         for (const tileIndex of spread.keys()) { | 
					
						
							|  |  |  |             const features = spread.get(tileIndex) | 
					
						
							|  |  |  |             const clipped = GeoOperations.clipAllInBox(features, tileIndex) | 
					
						
							|  |  |  |             if (clipped.length === 0) { | 
					
						
							|  |  |  |                 skipped++ | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-01-27 04:00:51 +01:00
										 |  |  |             for (const f of clipped) { | 
					
						
							|  |  |  |                 f.properties.resources = DownloadCommunityIndex.stripResourcesObj(f.properties.resources) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |             const [z, x, y] = Tiles.tile_from_index(tileIndex) | 
					
						
							|  |  |  |             const path = `${targetDirectory}/tile_${z}_${x}_${y}.geojson` | 
					
						
							|  |  |  |             clipped.forEach((f) => { | 
					
						
							|  |  |  |                 delete f.bbox | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             writeFileSync(path, JSON.stringify({ type: "FeatureCollection", features: clipped }), "utf8") | 
					
						
							|  |  |  |             written++ | 
					
						
							| 
									
										
										
										
											2025-01-27 03:14:19 +01:00
										 |  |  |             let yList = writtenTilesOverview[x] | 
					
						
							|  |  |  |             if (!yList) { | 
					
						
							|  |  |  |                 yList = [] | 
					
						
							|  |  |  |                 writtenTilesOverview[x] = yList | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             yList.push(y) | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |             console.log(`Written tile ${path}`) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         console.log(`Created ${written} tiles, skipped ${skipped}`) | 
					
						
							| 
									
										
										
										
											2025-01-27 03:20:07 +01:00
										 |  |  |         writeFileSync(targetDirectory + "/tile_6_overview.json", JSON.stringify(writtenTilesOverview), "utf8") | 
					
						
							|  |  |  |         console.log("Created overview file") | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async main(args: string[]): Promise<void> { | 
					
						
							|  |  |  |         const path = args[0] | 
					
						
							|  |  |  |         if (!path) { | 
					
						
							|  |  |  |             this.printHelp() | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         await DownloadCommunityIndex.update(path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | new DownloadCommunityIndex().run() |