forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			364 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { parse } from "csv-parse/sync"
 | 
						|
import { readFileSync, writeFileSync } from "fs"
 | 
						|
import { Utils } from "../../Utils"
 | 
						|
import { GeoJSONObject, geometry } from "@turf/turf"
 | 
						|
 | 
						|
function parseAndClean(filename: string): Record<any, string>[] {
 | 
						|
    const csvOptions = {
 | 
						|
        columns: true,
 | 
						|
        skip_empty_lines: true,
 | 
						|
        trim: true,
 | 
						|
    }
 | 
						|
    const records: Record<any, string>[] = parse(readFileSync(filename), csvOptions)
 | 
						|
    return records.map((r) => {
 | 
						|
        for (const key of Object.keys(r)) {
 | 
						|
            if (r[key].endsWith("niet van toepassing")) {
 | 
						|
                delete r[key]
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return r
 | 
						|
    })
 | 
						|
}
 | 
						|
 | 
						|
const structuren = {
 | 
						|
    "Voltijds Gewoon Secundair Onderwijs": "secondary",
 | 
						|
    "Gewoon Lager Onderwijs": "primary",
 | 
						|
    "Gewoon Kleuteronderwijs": "kindergarten",
 | 
						|
    Kleuteronderwijs: "kindergarten",
 | 
						|
    "Buitengewoon Lager Onderwijs": "primary",
 | 
						|
    "Buitengewoon Secundair Onderwijs": "secondary",
 | 
						|
    "Buitengewoon Kleuteronderwijs": "kindergarten",
 | 
						|
    "Deeltijds Beroepssecundair Onderwijs": "secondary",
 | 
						|
}
 | 
						|
 | 
						|
const degreesMapping = {
 | 
						|
    "Derde graad": "upper_secondary",
 | 
						|
    "Tweede graad": "middle_secondary",
 | 
						|
    "Eerste graad": "lower_secondary",
 | 
						|
}
 | 
						|
const classificationOrder = [
 | 
						|
    "kindergarten",
 | 
						|
    "primary",
 | 
						|
    "secondary",
 | 
						|
    "lower_secondary",
 | 
						|
    "middle_secondary",
 | 
						|
    "upper_secondary",
 | 
						|
]
 | 
						|
 | 
						|
const stelselsMapping = {
 | 
						|
    "Beide stelsels": "linear_courses;modular_courses",
 | 
						|
    "Lineair stelsel": "linear_courses",
 | 
						|
    "Modulair stelsel": "modular_courses",
 | 
						|
}
 | 
						|
 | 
						|
const rmKeys = [
 | 
						|
    "schoolnummer",
 | 
						|
    "instellingstype",
 | 
						|
    "adres",
 | 
						|
    "begindatum",
 | 
						|
    "hoofdzetel",
 | 
						|
    "huisnummer",
 | 
						|
    "kbo-nummer",
 | 
						|
    "beheerder(s)",
 | 
						|
    "bestuur",
 | 
						|
    "clb",
 | 
						|
    "ingerichte hoofdstructuren",
 | 
						|
    "busnummer",
 | 
						|
    "crab-code",
 | 
						|
    "crab-huisnr",
 | 
						|
    "einddatum",
 | 
						|
    "fax",
 | 
						|
    "gemeente",
 | 
						|
    "intern_vplnummer",
 | 
						|
    "kbo_nummer",
 | 
						|
    "lx",
 | 
						|
    "ly",
 | 
						|
    "niscode",
 | 
						|
    "onderwijsniveau",
 | 
						|
    "onderwijsvorm",
 | 
						|
    "scholengemeenschap",
 | 
						|
    "postcode",
 | 
						|
    "provincie",
 | 
						|
    "provinciecode",
 | 
						|
    "soort instelling",
 | 
						|
    "status erkenning",
 | 
						|
    "straat",
 | 
						|
    "VWO-vestigingsplaatscode",
 | 
						|
    "taalstelsel",
 | 
						|
    "net",
 | 
						|
]
 | 
						|
 | 
						|
const rename = {
 | 
						|
    "e-mail": "email",
 | 
						|
    naam: "name",
 | 
						|
    telefoon: "phone",
 | 
						|
}
 | 
						|
 | 
						|
function fuzzIdenticals(features: { geometry: { coordinates: [number, number] } }[]) {
 | 
						|
    var seen = new Set<string>()
 | 
						|
    for (const feature of features) {
 | 
						|
        var coors = feature.geometry.coordinates
 | 
						|
        let k = coors[0] + "," + coors[1]
 | 
						|
        while (seen.has(k)) {
 | 
						|
            coors[0] += 0.00025
 | 
						|
            k = coors[0] + "," + coors[1]
 | 
						|
        }
 | 
						|
        seen.add(k)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Sorts classifications in order
 | 
						|
 *
 | 
						|
 * sortClassifications(["primary","secondary","kindergarten"] // => ["kindergarten", "primary", "secondary"]
 | 
						|
 */
 | 
						|
function sortClassifications(classification: string[]) {
 | 
						|
    return classification.sort(
 | 
						|
        (a, b) => classificationOrder.indexOf(a) - classificationOrder.indexOf(b)
 | 
						|
    )
 | 
						|
}
 | 
						|
 | 
						|
function main() {
 | 
						|
    console.log("Parsing schools...")
 | 
						|
    const aantallen = "/home/pietervdvn/Downloads/Scholen/aantallen.csv"
 | 
						|
    const perSchool = "/home/pietervdvn/Downloads/Scholen/perschool.csv"
 | 
						|
 | 
						|
    const schoolfields = [
 | 
						|
        "schoolnummer",
 | 
						|
        "intern_vplnummer",
 | 
						|
        "net",
 | 
						|
        "naam",
 | 
						|
        "hoofdzetel",
 | 
						|
        "adres",
 | 
						|
        "straat",
 | 
						|
        "huisnummer",
 | 
						|
        "busnummer",
 | 
						|
        "postcode",
 | 
						|
        "gemeente",
 | 
						|
        "niscode",
 | 
						|
        "provinciecode",
 | 
						|
        "provincie",
 | 
						|
        "VWO-vestigingsplaatscode",
 | 
						|
        "crab-code",
 | 
						|
        "crab-huisnr",
 | 
						|
        "lx",
 | 
						|
        "ly",
 | 
						|
        "kbo-nummer",
 | 
						|
        "telefoon",
 | 
						|
        "fax",
 | 
						|
        "e-mail",
 | 
						|
        "website",
 | 
						|
        "beheerder(s)",
 | 
						|
        "soort instelling",
 | 
						|
        "onderwijsniveau",
 | 
						|
        "instellingstype",
 | 
						|
        "begindatum",
 | 
						|
        "einddatum",
 | 
						|
        "status erkenning",
 | 
						|
        "clb",
 | 
						|
        "bestuur",
 | 
						|
        "scholengemeenschap",
 | 
						|
        "taalstelsel",
 | 
						|
        "ingerichte hoofdstructuren",
 | 
						|
    ] as const
 | 
						|
 | 
						|
    const schoolGeojson: {
 | 
						|
        features: {
 | 
						|
            properties: Record<typeof schoolfields[number], string>
 | 
						|
            geometry: {
 | 
						|
                type: "Point"
 | 
						|
                coordinates: [number, number]
 | 
						|
            }
 | 
						|
        }[]
 | 
						|
    } = JSON.parse(readFileSync("scripts/schools/scholen.geojson", "utf8"))
 | 
						|
 | 
						|
    fuzzIdenticals(schoolGeojson.features)
 | 
						|
 | 
						|
    const aantallenFields = [
 | 
						|
        "schooljaar",
 | 
						|
        "nr koepel",
 | 
						|
        "koepel",
 | 
						|
        "instellingscode",
 | 
						|
        "intern volgnr vpl",
 | 
						|
        "volgnr vpl",
 | 
						|
        "naam instelling",
 | 
						|
        "GON-school",
 | 
						|
        "GOK-school",
 | 
						|
        "instellingsnummer scholengemeenschap",
 | 
						|
        "scholengemeenschap",
 | 
						|
        "code schoolbestuur",
 | 
						|
        "schoolbestuur",
 | 
						|
        "type vestigingsplaats",
 | 
						|
        "fusiegemeente hoofdvestigingsplaats",
 | 
						|
        "straatnaam vestigingsplaats",
 | 
						|
        "huisnr vestigingsplaats",
 | 
						|
        "bus vestigingsplaats",
 | 
						|
        "postcode vestigingsplaats",
 | 
						|
        "deelgemeente vestigingsplaats",
 | 
						|
        "fusiegemeente vestigingsplaats",
 | 
						|
        "hoofdstructuur (code)",
 | 
						|
        "hoofdstructuur",
 | 
						|
        "administratieve groep (code)",
 | 
						|
        "administratieve groep",
 | 
						|
        "graad lager onderwijs",
 | 
						|
        "pedagogische methode",
 | 
						|
        "graad secundair onderwijs",
 | 
						|
        "leerjaar",
 | 
						|
        "A of B-stroom",
 | 
						|
        "basisopties",
 | 
						|
        "beroepenveld",
 | 
						|
        "onderwijsvorm",
 | 
						|
        "studiegebied",
 | 
						|
        "studierichting",
 | 
						|
        "stelsel",
 | 
						|
        "okan cluster",
 | 
						|
        "type buitengewoon onderwijs",
 | 
						|
        "opleidingsvorm (code)",
 | 
						|
        "opleidingsvorm",
 | 
						|
        "fase",
 | 
						|
        "opleidingen",
 | 
						|
        "geslacht",
 | 
						|
        "aantal inschrijvingen",
 | 
						|
    ] as const
 | 
						|
    const aantallenParsed: Record<typeof aantallenFields[number], string>[] =
 | 
						|
        parseAndClean(aantallen)
 | 
						|
    const perschoolFields = [
 | 
						|
        "schooljaar",
 | 
						|
        "nr koepel",
 | 
						|
        "koepel",
 | 
						|
        "instellingscode",
 | 
						|
        "naam instelling",
 | 
						|
        "straatnaam",
 | 
						|
        "huisnr",
 | 
						|
        "bus",
 | 
						|
        "postcode",
 | 
						|
        "deelgemeente",
 | 
						|
        "fusiegemeente",
 | 
						|
        "aantal inschrijvingen",
 | 
						|
    ] as const
 | 
						|
    const perschoolParsed: Record<typeof perschoolFields[number], string>[] =
 | 
						|
        parseAndClean(perSchool)
 | 
						|
 | 
						|
    schoolGeojson.features = schoolGeojson.features
 | 
						|
        .filter((sch) => sch.properties.lx != "0" && sch.properties.ly != "0")
 | 
						|
        .filter((sch) => sch.properties.instellingstype !== "Universiteit")
 | 
						|
 | 
						|
    const c = schoolGeojson.features.length
 | 
						|
    console.log("Got ", schoolGeojson.features.length, "items after filtering")
 | 
						|
    let i = 0
 | 
						|
    let lastWrite = 0
 | 
						|
    for (const feature of schoolGeojson.features) {
 | 
						|
        i++
 | 
						|
        const now = Date.now()
 | 
						|
        if (now - lastWrite > 1000) {
 | 
						|
            lastWrite = now
 | 
						|
            console.log("Processing " + i + "/" + c)
 | 
						|
        }
 | 
						|
        const props = feature.properties
 | 
						|
 | 
						|
        const aantallen = aantallenParsed.filter((i) => i.instellingscode == props.schoolnummer)
 | 
						|
 | 
						|
        if (aantallen.length > 0) {
 | 
						|
            const fetch = (key: typeof aantallenFields[number]) =>
 | 
						|
                Utils.NoNull(Utils.Dedup(aantallen.map((x) => x[key])))
 | 
						|
 | 
						|
            props["onderwijsvorm"] = fetch("onderwijsvorm").join(";")
 | 
						|
 | 
						|
            /*
 | 
						|
            const gonSchool = aantallen.some(x => x["GON-school"] === "GON-school")
 | 
						|
            const gokSchool = aantallen.some(x => x["GOK-school"] === "GON-school")
 | 
						|
            const onderwijsvorm = fetch("onderwijsvorm")
 | 
						|
            const koepel = fetch("koepel")
 | 
						|
            const stelsel = fetch("stelsel").join(";")
 | 
						|
            const scholengemeenschap = fetch("scholengemeenschap")
 | 
						|
            
 | 
						|
            */
 | 
						|
            const hoofdstructuur = fetch("hoofdstructuur")
 | 
						|
 | 
						|
            let specialEducation = false
 | 
						|
            let classification = hoofdstructuur.map((s) => {
 | 
						|
                const v = structuren[s]
 | 
						|
                if (s.startsWith("Buitengewoon")) {
 | 
						|
                    specialEducation = true
 | 
						|
                }
 | 
						|
                if (v === undefined) {
 | 
						|
                    console.error("Type not found: " + s)
 | 
						|
                    return ""
 | 
						|
                }
 | 
						|
                return v
 | 
						|
            })
 | 
						|
            const graden = fetch("graad secundair onderwijs")
 | 
						|
            if (classification[0] === "secondary") {
 | 
						|
                if (graden.length !== 3) {
 | 
						|
                    classification = graden.map((degree) => degreesMapping[degree])
 | 
						|
                }
 | 
						|
            }
 | 
						|
            sortClassifications(classification)
 | 
						|
            props["school"] = Utils.Dedup(classification).join("; ")
 | 
						|
 | 
						|
            // props["koepel"] = koepel.join(";")
 | 
						|
            // props["scholengemeenschap"] = scholengemeenschap.join(";")
 | 
						|
            // props["stelsel"] = stelselsMapping[stelsel]
 | 
						|
 | 
						|
            if (specialEducation) {
 | 
						|
                props["school:for"] = "special_education"
 | 
						|
            }
 | 
						|
            if (props.taalstelsel === "Nederlandstalig") {
 | 
						|
                props["language:nl"] = "yes"
 | 
						|
            }
 | 
						|
 | 
						|
            if (props.instellingstype === "Instelling voor deeltijds kunstonderwijs") {
 | 
						|
                props["amenity"] = "college"
 | 
						|
                props["school:subject"] = "art"
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        const schoolinfo = perschoolParsed.filter((i) => i.instellingscode == props.schoolnummer)
 | 
						|
        if (schoolinfo.length == 0) {
 | 
						|
            // pass
 | 
						|
        } else if (schoolinfo.length == 1) {
 | 
						|
            props["capacity"] = schoolinfo[0]["aantal inschrijvingen"]
 | 
						|
                .split(";")
 | 
						|
                .map((i) => Number(i))
 | 
						|
                .reduce((sum, i) => sum + i, 0)
 | 
						|
        } else {
 | 
						|
            throw "Multiple schoolinfo's found for " + props.schoolnummer
 | 
						|
        }
 | 
						|
 | 
						|
        //props["source:ref"] = props.schoolnummer
 | 
						|
        props["amenity"] = "school"
 | 
						|
        if (props["school"] === "kindergarten") {
 | 
						|
            props["amenity"] = "kindergarten"
 | 
						|
            props["isced:2011:level"] = "early_education"
 | 
						|
            delete props["school"]
 | 
						|
        }
 | 
						|
 | 
						|
        for (const renameKey in rename) {
 | 
						|
            const into = rename[renameKey]
 | 
						|
            if (props[renameKey] !== undefined) {
 | 
						|
                props[into] = props[renameKey]
 | 
						|
                delete props[renameKey]
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        for (const rmKey of rmKeys) {
 | 
						|
            delete props[rmKey]
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    //schoolGeojson.features = schoolGeojson.features.filter(f => f.properties["capacity"] !== undefined)
 | 
						|
    /*schoolGeojson.features.forEach((f, i) => {
 | 
						|
        f.properties["id"] = "school/"+i
 | 
						|
    })*/
 | 
						|
    schoolGeojson.features = schoolGeojson.features.filter(
 | 
						|
        (f) => f.properties["amenity"] === "kindergarten"
 | 
						|
    )
 | 
						|
 | 
						|
    writeFileSync("scripts/schools/amended_schools.geojson", JSON.stringify(schoolGeojson), "utf8")
 | 
						|
    console.log("Done")
 | 
						|
}
 | 
						|
 | 
						|
if (!process.argv[1].endsWith("mocha")) {
 | 
						|
    main()
 | 
						|
}
 |