| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  | import * as fs from "fs" | 
					
						
							|  |  |  | import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource" | 
					
						
							|  |  |  | import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" | 
					
						
							|  |  |  | import * as readline from "readline" | 
					
						
							|  |  |  | import ScriptUtils from "./ScriptUtils" | 
					
						
							| 
									
										
										
										
											2022-01-16 01:59:06 +01:00
										 |  |  | 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" | 
					
						
							| 
									
										
										
										
											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-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-01-26 01:41:21 +01:00
										 |  |  |         TiledFeatureSource.createHierarchy(StaticFeatureSource.fromGeojson(allFeatures), { | 
					
						
							|  |  |  |             minZoomLevel: zoomlevel, | 
					
						
							|  |  |  |             maxZoomLevel: zoomlevel, | 
					
						
							|  |  |  |             maxFeatureCount: Number.MAX_VALUE, | 
					
						
							|  |  |  |             registerTile: (tile) => { | 
					
						
							| 
									
										
										
										
											2023-01-26 02:17:53 +01:00
										 |  |  |                 handled = handled + 1 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |                 const path = `${outputDirectory}/tile_${tile.z}_${tile.x}_${tile.y}.geojson` | 
					
						
							|  |  |  |                 const box = BBox.fromTile(tile.z, tile.x, tile.y) | 
					
						
							|  |  |  |                 let features = tile.features.data.map((ff) => ff.feature) | 
					
						
							|  |  |  |                 if (doSlice) { | 
					
						
							|  |  |  |                     features = Utils.NoNull( | 
					
						
							|  |  |  |                         features.map((f) => { | 
					
						
							| 
									
										
										
										
											2023-01-26 02:17:53 +01:00
										 |  |  |                             const bbox = box.asGeoJson({}) | 
					
						
							|  |  |  |                             const properties = { | 
					
						
							|  |  |  |                                 ...f.properties, | 
					
						
							|  |  |  |                                 id: | 
					
						
							|  |  |  |                                     (f.properties?.id ?? "") + | 
					
						
							|  |  |  |                                     "_" + | 
					
						
							|  |  |  |                                     tile.z + | 
					
						
							|  |  |  |                                     "_" + | 
					
						
							|  |  |  |                                     tile.x + | 
					
						
							|  |  |  |                                     "_" + | 
					
						
							|  |  |  |                                     tile.y, | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                             if (GeoOperations.completelyWithin(bbox, f)) { | 
					
						
							|  |  |  |                                 bbox.properties = properties | 
					
						
							|  |  |  |                                 return bbox | 
					
						
							|  |  |  |                             } | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |                             const intersection = GeoOperations.intersect(f, box.asGeoJson({})) | 
					
						
							|  |  |  |                             if (intersection) { | 
					
						
							| 
									
										
										
										
											2023-01-26 02:17:53 +01:00
										 |  |  |                                 intersection.properties = properties | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |                             } | 
					
						
							|  |  |  |                             return intersection | 
					
						
							|  |  |  |                         }) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 features.forEach((f) => { | 
					
						
							|  |  |  |                     delete f.bbox | 
					
						
							|  |  |  |                 }) | 
					
						
							| 
									
										
										
										
											2023-01-26 02:17:53 +01:00
										 |  |  |                 if (features.length === 0) { | 
					
						
							|  |  |  |                     ScriptUtils.erasableLog( | 
					
						
							|  |  |  |                         handled + "/" + maxNumberOfTiles, | 
					
						
							|  |  |  |                         "Not writing ", | 
					
						
							|  |  |  |                         path, | 
					
						
							|  |  |  |                         ": no features" | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |                 fs.writeFileSync( | 
					
						
							|  |  |  |                     path, | 
					
						
							|  |  |  |                     JSON.stringify( | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             type: "FeatureCollection", | 
					
						
							|  |  |  |                             features: features, | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         null, | 
					
						
							|  |  |  |                         "  " | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |                 ScriptUtils.erasableLog( | 
					
						
							| 
									
										
										
										
											2023-01-26 02:17:53 +01:00
										 |  |  |                     handled + "/" + maxNumberOfTiles, | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  |                     "Written ", | 
					
						
							|  |  |  |                     path, | 
					
						
							|  |  |  |                     "which has ", | 
					
						
							|  |  |  |                     tile.features.data.length, | 
					
						
							|  |  |  |                     "features" | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-27 01:18:05 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 01:41:21 +01:00
										 |  |  | new Slice().run() |