forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			150 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
	
		
			5.2 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()
 |