forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			133 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			133 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import Script from "./Script"
 | |
| import { CommunityResource } from "../src/Logic/Web/CommunityIndex"
 | |
| import { Utils } from "../src/Utils"
 | |
| import { FeatureCollection, MultiPolygon, Polygon } from "geojson"
 | |
| import { existsSync, mkdirSync, writeFileSync } from "fs"
 | |
| 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) {
 | |
|             const type = resources[k].type
 | |
|             if (type === "twitter" || type === "facebook" || type === "x") {
 | |
|                 // These channels are toxic nowadays - we simply omit them
 | |
|                 continue
 | |
|             }
 | |
|             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"
 | |
|         )
 | |
|         if (!existsSync(targetDirectory)) {
 | |
|             mkdirSync(targetDirectory)
 | |
|         }
 | |
|         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
 | |
|         const writtenTilesOverview: Record<number, number[]> = {}
 | |
|         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
 | |
|             }
 | |
|             for (const f of clipped) {
 | |
|                 f.properties.resources = DownloadCommunityIndex.stripResourcesObj(f.properties.resources)
 | |
|             }
 | |
| 
 | |
|             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++
 | |
|             let yList = writtenTilesOverview[x]
 | |
|             if (!yList) {
 | |
|                 yList = []
 | |
|                 writtenTilesOverview[x] = yList
 | |
|             }
 | |
|             yList.push(y)
 | |
|             console.log(`Written tile ${path}`)
 | |
|         }
 | |
|         console.log(`Created ${written} tiles, skipped ${skipped}`)
 | |
|         writeFileSync(targetDirectory + "/tile_6_overview.json", JSON.stringify(writtenTilesOverview), "utf8")
 | |
|         console.log("Created overview file")
 | |
|     }
 | |
| 
 | |
| 
 | |
|     async main(args: string[]): Promise<void> {
 | |
|         const path = args[0]
 | |
|         if (!path) {
 | |
|             this.printHelp()
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         await DownloadCommunityIndex.update(path)
 | |
| 
 | |
|     }
 | |
| }
 | |
| 
 | |
| new DownloadCommunityIndex().run()
 |