Slice script now clips to tile box if flag is set

This commit is contained in:
Pieter Vander Vennet 2023-01-26 01:41:21 +01:00
parent b88bb5b6d0
commit 8cf3b88172
2 changed files with 163 additions and 133 deletions

View file

@ -783,4 +783,14 @@ export class GeoOperations {
): boolean { ): boolean {
return booleanWithin(feature, possiblyEncloingFeature) return booleanWithin(feature, possiblyEncloingFeature)
} }
/**
* Create a union between two features
*/
static union = turf.union
/**
* Create an intersection between two features
*/
static intersect = turf.intersect
} }

View file

@ -4,159 +4,179 @@ import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSou
import * as readline from "readline" import * as readline from "readline"
import ScriptUtils from "./ScriptUtils" import ScriptUtils from "./ScriptUtils"
import { Utils } from "../Utils" import { Utils } from "../Utils"
import Script from "./Script"
import { BBox } from "../Logic/BBox"
import { GeoOperations } from "../Logic/GeoOperations"
/** /**
* This script slices a big newline-delimeted geojson file into tiled geojson * This script slices a big newline-delimeted geojson file into tiled geojson
* It was used to convert the CRAB-data into geojson tiles * It was used to convert the CRAB-data into geojson tiles
*/ */
async function readFeaturesFromLineDelimitedJsonFile(inputFile: string): Promise<any[]> { class Slice extends Script {
const fileStream = fs.createReadStream(inputFile) constructor() {
super("Break data into tiles")
}
const rl = readline.createInterface({ async readFeaturesFromLineDelimitedJsonFile(inputFile: string): Promise<any[]> {
input: fileStream, const fileStream = fs.createReadStream(inputFile)
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[] = [] const rl = readline.createInterface({
// @ts-ignore input: fileStream,
for await (const line of rl) { 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")
}
}
return allFeatures
}
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")
}
}
return allFeatures
}
async readFeaturesFromGeoJson(inputFile: string): Promise<any[]> {
try { try {
allFeatures.push(JSON.parse(line)) return JSON.parse(fs.readFileSync(inputFile, { encoding: "utf-8" })).features
} catch (e) { } catch (e) {
console.error("Could not parse", line) // We retry, but with a line-by-line approach
break return await this.readGeojsonLineByLine(inputFile)
}
if (allFeatures.length % 10000 === 0) {
ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now")
} }
} }
return allFeatures
}
async function readGeojsonLineByLine(inputFile: string): Promise<any[]> { async main(args: string[]) {
const fileStream = fs.createReadStream(inputFile) console.log("GeoJSON slicer")
if (args.length < 3) {
const rl = readline.createInterface({ console.log(
input: fileStream, "USAGE: <input-file.geojson> <target-zoom-level> <output-directory> [--clip]"
crlfDelay: Infinity, )
}) return
// 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 { const inputFile = args[0]
allFeatures.push(JSON.parse(line)) const zoomlevel = Number(args[1])
} catch (e) { const outputDirectory = args[2]
console.error("Could not parse", line) const doSlice = args[3] === "--clip"
break
if (!fs.existsSync(outputDirectory)) {
fs.mkdirSync(outputDirectory)
console.log("Directory created")
} }
if (allFeatures.length % 10000 === 0) { console.log("Using directory ", outputDirectory)
ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now")
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)
} }
} allFeatures = Utils.NoNull(allFeatures)
return allFeatures
}
async function readFeaturesFromGeoJson(inputFile: string): Promise<any[]> { console.log("Loaded all", allFeatures.length, "points")
try {
return JSON.parse(fs.readFileSync(inputFile, { encoding: "utf-8" })).features
} catch (e) {
// We retry, but with a line-by-line approach
return await readGeojsonLineByLine(inputFile)
}
}
async function main(args: string[]) { const keysToRemove = ["STRAATNMID", "GEMEENTE", "POSTCODE"]
console.log("GeoJSON slicer") for (const f of allFeatures) {
if (args.length < 3) { if (f.properties === null) {
console.log("USAGE: <input-file.geojson> <target-zoom-level> <output-directory>") console.log("Got a feature without properties!", f)
return continue
} }
for (const keyToRm of keysToRemove) {
const inputFile = args[0] delete f.properties[keyToRm]
const zoomlevel = Number(args[1]) }
const outputDirectory = args[2] delete f.bbox
if (!fs.existsSync(outputDirectory)) {
fs.mkdirSync(outputDirectory)
console.log("Directory created")
}
console.log("Using directory ", outputDirectory)
let allFeatures: any[]
if (inputFile.endsWith(".geojson")) {
console.log("Detected geojson")
allFeatures = await readFeaturesFromGeoJson(inputFile)
} else {
console.log("Loading as newline-delimited features")
allFeatures = await readFeaturesFromLineDelimitedJsonFile(inputFile)
}
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) { TiledFeatureSource.createHierarchy(StaticFeatureSource.fromGeojson(allFeatures), {
delete f.properties[keyToRm] minZoomLevel: zoomlevel,
} maxZoomLevel: zoomlevel,
delete f.bbox maxFeatureCount: Number.MAX_VALUE,
} registerTile: (tile) => {
TiledFeatureSource.createHierarchy(StaticFeatureSource.fromGeojson(allFeatures), { const path = `${outputDirectory}/tile_${tile.z}_${tile.x}_${tile.y}.geojson`
minZoomLevel: zoomlevel, const box = BBox.fromTile(tile.z, tile.x, tile.y)
maxZoomLevel: zoomlevel, let features = tile.features.data.map((ff) => ff.feature)
maxFeatureCount: Number.MAX_VALUE, if (doSlice) {
registerTile: (tile) => { features = Utils.NoNull(
const path = `${outputDirectory}/tile_${tile.z}_${tile.x}_${tile.y}.geojson` features.map((f) => {
const features = tile.features.data.map((ff) => ff.feature) const intersection = GeoOperations.intersect(f, box.asGeoJson({}))
features.forEach((f) => { if (intersection) {
delete f.bbox intersection.properties = f.properties
}) }
fs.writeFileSync( return intersection
path, })
JSON.stringify( )
{ }
type: "FeatureCollection", features.forEach((f) => {
features: features, delete f.bbox
}, })
null, fs.writeFileSync(
" " path,
JSON.stringify(
{
type: "FeatureCollection",
features: features,
},
null,
" "
)
) )
) ScriptUtils.erasableLog(
ScriptUtils.erasableLog( "Written ",
"Written ", path,
path, "which has ",
"which has ", tile.features.data.length,
tile.features.data.length, "features"
"features" )
) },
}, })
}) }
} }
let args = [...process.argv] new Slice().run()
args.splice(0, 2)
main(args).then((_) => {
console.log("All done!")
})