MapComplete/scripts/downloadCommunityIndex.ts

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()