| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import * as fs from "fs" | 
					
						
							|  |  |  | import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" | 
					
						
							|  |  |  | import * as readline from "readline" | 
					
						
							|  |  |  | import ScriptUtils from "./ScriptUtils" | 
					
						
							|  |  |  | import { Utils } from "../Utils" | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  | import Script from "./Script" | 
					
						
							|  |  |  | import { BBox } from "../Logic/BBox" | 
					
						
							|  |  |  | import { GeoOperations } from "../Logic/GeoOperations" | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | import { Tiles } from "../Models/TileRange" | 
					
						
							|  |  |  | import { Feature } from "geojson" | 
					
						
							| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-09 17:56:37 +01:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |  * This script slices a big newline-delimeted geojson file into tiled geojson | 
					
						
							| 
									
										
										
										
											2021-12-09 17:56:37 +01:00
										 |  |  |  * It was used to convert the CRAB-data into geojson tiles | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  | class Slice extends Script { | 
					
						
							|  |  |  |     constructor() { | 
					
						
							|  |  |  |         super("Break data into tiles") | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |     async readFeaturesFromLineDelimitedJsonFile(inputFile: string): Promise<any[]> { | 
					
						
							|  |  |  |         const fileStream = fs.createReadStream(inputFile) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const rl = readline.createInterface({ | 
					
						
							|  |  |  |             input: fileStream, | 
					
						
							|  |  |  |             crlfDelay: Infinity, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         // Note: we use the crlfDelay option to recognize all instances of CR LF
 | 
					
						
							|  |  |  |         // ('\r\n') in input.txt as a single line break.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const allFeatures: any[] = [] | 
					
						
							|  |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         for await (const line of rl) { | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 allFeatures.push(JSON.parse(line)) | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 console.error("Could not parse", line) | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (allFeatures.length % 10000 === 0) { | 
					
						
							|  |  |  |                 ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now") | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |         return allFeatures | 
					
						
							| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |     async readGeojsonLineByLine(inputFile: string): Promise<any[]> { | 
					
						
							|  |  |  |         const fileStream = fs.createReadStream(inputFile) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const rl = readline.createInterface({ | 
					
						
							|  |  |  |             input: fileStream, | 
					
						
							|  |  |  |             crlfDelay: Infinity, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         // Note: we use the crlfDelay option to recognize all instances of CR LF
 | 
					
						
							|  |  |  |         // ('\r\n') in input.txt as a single line break.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const allFeatures: any[] = [] | 
					
						
							|  |  |  |         let featuresSeen = false | 
					
						
							|  |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         for await (let line: string of rl) { | 
					
						
							|  |  |  |             if (!featuresSeen && line.startsWith('"features":')) { | 
					
						
							|  |  |  |                 featuresSeen = true | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (!featuresSeen) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (line.endsWith(",")) { | 
					
						
							|  |  |  |                 line = line.substring(0, line.length - 1) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 allFeatures.push(JSON.parse(line)) | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 console.error("Could not parse", line) | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (allFeatures.length % 10000 === 0) { | 
					
						
							|  |  |  |                 ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now") | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |         return allFeatures | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |     async readFeaturesFromGeoJson(inputFile: string): Promise<any[]> { | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |             return JSON.parse(fs.readFileSync(inputFile, { encoding: "utf-8" })).features | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |         } catch (e) { | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |             // We retry, but with a line-by-line approach
 | 
					
						
							|  |  |  |             return await this.readGeojsonLineByLine(inputFile) | 
					
						
							| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     private handleTileData( | 
					
						
							|  |  |  |         features: Feature[], | 
					
						
							|  |  |  |         tileIndex: number, | 
					
						
							|  |  |  |         outputDirectory: string, | 
					
						
							|  |  |  |         doSlice: boolean, | 
					
						
							|  |  |  |         handled: number, | 
					
						
							|  |  |  |         maxNumberOfTiles: number | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         const [z, x, y] = Tiles.tile_from_index(tileIndex) | 
					
						
							|  |  |  |         const path = `${outputDirectory}/tile_${z}_${x}_${y}.geojson` | 
					
						
							|  |  |  |         const box = BBox.fromTileIndex(tileIndex) | 
					
						
							|  |  |  |         if (doSlice) { | 
					
						
							|  |  |  |             features = Utils.NoNull( | 
					
						
							|  |  |  |                 features.map((f) => { | 
					
						
							|  |  |  |                     const bbox = box.asGeoJson({}) | 
					
						
							|  |  |  |                     const properties = { | 
					
						
							|  |  |  |                         ...f.properties, | 
					
						
							|  |  |  |                         id: (f.properties?.id ?? "") + "_" + z + "_" + x + "_" + y, | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (GeoOperations.completelyWithin(bbox, <any>f)) { | 
					
						
							|  |  |  |                         bbox.properties = properties | 
					
						
							|  |  |  |                         return bbox | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     const intersection = GeoOperations.intersect(f, box.asGeoJson({})) | 
					
						
							|  |  |  |                     if (intersection) { | 
					
						
							|  |  |  |                         intersection.properties = properties | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return intersection | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         features.forEach((f) => { | 
					
						
							|  |  |  |             delete f.bbox | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         if (features.length === 0) { | 
					
						
							|  |  |  |             ScriptUtils.erasableLog( | 
					
						
							|  |  |  |                 handled + "/" + maxNumberOfTiles, | 
					
						
							|  |  |  |                 "Not writing ", | 
					
						
							|  |  |  |                 path, | 
					
						
							|  |  |  |                 ": no features" | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         fs.writeFileSync( | 
					
						
							|  |  |  |             path, | 
					
						
							|  |  |  |             JSON.stringify( | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     type: "FeatureCollection", | 
					
						
							|  |  |  |                     features: features, | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 null, | 
					
						
							|  |  |  |                 "  " | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         ScriptUtils.erasableLog( | 
					
						
							|  |  |  |             handled + "/" + maxNumberOfTiles, | 
					
						
							|  |  |  |             "Written ", | 
					
						
							|  |  |  |             path, | 
					
						
							|  |  |  |             "which has ", | 
					
						
							|  |  |  |             features.length, | 
					
						
							|  |  |  |             "features" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |     async main(args: string[]) { | 
					
						
							|  |  |  |         console.log("GeoJSON slicer") | 
					
						
							|  |  |  |         if (args.length < 3) { | 
					
						
							|  |  |  |             console.log( | 
					
						
							|  |  |  |                 "USAGE: <input-file.geojson> <target-zoom-level> <output-directory> [--clip]" | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |         const inputFile = args[0] | 
					
						
							|  |  |  |         const zoomlevel = Number(args[1]) | 
					
						
							|  |  |  |         const outputDirectory = args[2] | 
					
						
							|  |  |  |         const doSlice = args[3] === "--clip" | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |         if (!fs.existsSync(outputDirectory)) { | 
					
						
							|  |  |  |             fs.mkdirSync(outputDirectory) | 
					
						
							|  |  |  |             console.log("Directory created") | 
					
						
							| 
									
										
										
										
											2022-01-16 01:59:06 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |         console.log("Using directory ", outputDirectory) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let allFeatures: any[] | 
					
						
							|  |  |  |         if (inputFile.endsWith(".geojson")) { | 
					
						
							|  |  |  |             console.log("Detected geojson") | 
					
						
							|  |  |  |             allFeatures = await this.readFeaturesFromGeoJson(inputFile) | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             console.log("Loading as newline-delimited features") | 
					
						
							|  |  |  |             allFeatures = await this.readFeaturesFromLineDelimitedJsonFile(inputFile) | 
					
						
							| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |         allFeatures = Utils.NoNull(allFeatures) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.log("Loaded all", allFeatures.length, "points") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const keysToRemove = ["STRAATNMID", "GEMEENTE", "POSTCODE"] | 
					
						
							|  |  |  |         for (const f of allFeatures) { | 
					
						
							|  |  |  |             if (f.properties === null) { | 
					
						
							|  |  |  |                 console.log("Got a feature without properties!", f) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             for (const keyToRm of keysToRemove) { | 
					
						
							|  |  |  |                 delete f.properties[keyToRm] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             delete f.bbox | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-01-26 02:17:53 +01:00
										 |  |  |         const maxNumberOfTiles = Math.pow(2, zoomlevel) * Math.pow(2, zoomlevel) | 
					
						
							|  |  |  |         let handled = 0 | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         StaticFeatureSource.fromGeojson(allFeatures).features.addCallbackAndRun((feats) => { | 
					
						
							|  |  |  |             GeoOperations.slice(zoomlevel, feats).forEach((tileData, tileIndex) => { | 
					
						
							| 
									
										
										
										
											2023-01-26 02:17:53 +01:00
										 |  |  |                 handled = handled + 1 | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |                 this.handleTileData( | 
					
						
							|  |  |  |                     tileData, | 
					
						
							|  |  |  |                     tileIndex, | 
					
						
							|  |  |  |                     outputDirectory, | 
					
						
							|  |  |  |                     doSlice, | 
					
						
							|  |  |  |                     handled, | 
					
						
							|  |  |  |                     maxNumberOfTiles | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  | new Slice().run() |