2025-07-31 01:03:54 +02:00
import Script from "./Script"
2025-09-25 14:03:02 +02:00
import { existsSync , mkdirSync , writeFileSync } from "fs"
2025-07-31 12:18:57 +02:00
import { Utils } from "../src/Utils"
2025-09-25 14:03:02 +02:00
import { OfflineBasemapManager } from "../src/Logic/OfflineBasemapManager"
2025-09-26 01:53:34 +02:00
import { PmTilesExtractGenerator } from "./pmTilesExtractGenerator"
2025-07-31 01:03:54 +02:00
class GeneratePmTilesExtracts extends Script {
2025-07-31 01:11:12 +02:00
private targetDir : string
2025-07-31 12:04:20 +02:00
private skipped : number = 0
2025-09-26 01:53:34 +02:00
private extractsGenerator : PmTilesExtractGenerator
2025-07-31 01:03:54 +02:00
constructor ( ) {
2025-08-13 23:06:38 +02:00
super (
2025-09-25 14:12:28 +02:00
"Generates many pmtiles-archive from planet-latest.pmtiles. Expects the `pmtiles`-executable to be at `/data/pmtiles`." +
"\n\n" +
"The first argument should be the 'targetDirectory', where many subdirectories will be made containing the subarchives. A second argument (defaulting to [targetDir]/planet-latest.pmtiles) is the source data"
2025-08-13 23:06:38 +02:00
)
2025-07-31 01:03:54 +02:00
}
2025-09-26 01:53:34 +02:00
public * generateColumnIfNeeded (
2025-08-13 23:06:38 +02:00
z : number ,
x : number ,
boundary : number ,
maxzoom? : number
2025-09-26 01:53:34 +02:00
) : Generator < Promise < any > > {
const lastFileForColumn = this . extractsGenerator . getFilename ( z , x , boundary - 1 )
if ( existsSync ( lastFileForColumn ) ) {
2025-07-31 13:43:21 +02:00
// Skip this column, already exists
console . log ( "Skipping column " , x , "at zoom" , z )
this . skipped += boundary
return
}
2025-08-13 23:06:38 +02:00
console . log (
"Starting column" ,
x ,
"at zoom" ,
z ,
"as" ,
2025-09-26 01:53:34 +02:00
lastFileForColumn ,
2025-08-13 23:06:38 +02:00
"does not exist"
)
2025-07-31 13:43:21 +02:00
for ( let y = 0 ; y < boundary ; y ++ ) {
2025-09-26 01:53:34 +02:00
yield this . extractsGenerator . generateArchive ( z , x , y , maxzoom )
2025-07-31 13:43:21 +02:00
}
}
2025-08-13 23:06:38 +02:00
private * generateField ( z : number , maxzoom? : number ) : Generator < Promise < void > > {
2025-09-25 14:51:39 +02:00
const boundary = Math . max ( 1 , 2 << ( z - 1 ) )
2025-07-31 01:03:54 +02:00
for ( let x = 0 ; x < boundary ; x ++ ) {
2025-09-25 14:26:07 +02:00
if ( ! existsSync ( this . targetDir + "/" + z + "/" + x ) ) {
mkdirSync ( this . targetDir + "/" + z + "/" + x )
2025-09-25 14:03:02 +02:00
}
2025-07-31 13:48:31 +02:00
for ( const promise of this . generateColumnIfNeeded ( z , x , boundary , maxzoom ) ) {
yield promise
}
2025-07-31 01:03:54 +02:00
}
}
2025-09-26 01:53:34 +02:00
2025-07-31 11:57:23 +02:00
2025-08-13 23:06:38 +02:00
private * generateAll ( ) : Generator < Promise < void > > {
2025-07-31 01:03:54 +02:00
const zoomlevels : Record < number , number > = OfflineBasemapManager . zoomelevels
for ( const key in zoomlevels ) {
const minzoom : number = Number ( key )
const maxzoom : number | undefined = zoomlevels [ key ]
2025-09-25 14:26:07 +02:00
if ( ! existsSync ( this . targetDir + "/" + key ) ) {
mkdirSync ( this . targetDir + "/" + key )
2025-09-25 14:03:02 +02:00
}
2025-07-31 01:03:54 +02:00
for ( const promise of this . generateField ( minzoom , maxzoom ) ) {
yield promise
}
}
}
createBatch < T > ( generator : Generator < T > , length : number ) : T [ ] {
const batch = [ ]
do {
const next = generator . next ( )
if ( next . done ) {
return batch
}
batch . push ( next . value )
} while ( batch . length < length )
return batch
}
2025-07-31 01:11:12 +02:00
async main ( args : string [ ] ) : Promise < void > {
this . targetDir = args [ 0 ]
2025-09-26 01:53:34 +02:00
const sourceFile = this . targetDir + "/planet-latest.pmtiles"
2025-07-31 01:11:12 +02:00
if ( ! this . targetDir ) {
2025-09-25 14:26:07 +02:00
console . log ( "Please specify a target directory. Did you forget '--' in vite-node?" )
2025-07-31 01:11:12 +02:00
return
}
2025-09-26 01:53:34 +02:00
this . extractsGenerator = new PmTilesExtractGenerator ( sourceFile , this . targetDir )
2025-07-31 11:57:23 +02:00
let estimate = 0
for ( const key in OfflineBasemapManager . zoomelevels ) {
const z : number = Number ( key )
2025-08-01 02:05:17 +02:00
const boundary = 2 << z
2025-07-31 11:57:23 +02:00
estimate += boundary * boundary
}
2025-07-31 01:11:45 +02:00
console . log ( "Target dir is:" , this . targetDir )
2025-09-25 14:03:02 +02:00
const numberOfThreads = 24
2025-07-31 01:03:54 +02:00
const generator = this . generateAll ( )
let batch : Promise < void > [ ] = [ ]
2025-07-31 01:22:36 +02:00
let done = 0
2025-07-31 12:18:57 +02:00
const startDate = new Date ( )
2025-07-31 01:03:54 +02:00
do {
batch = this . createBatch ( generator , numberOfThreads )
await Promise . all ( batch )
2025-07-31 01:22:36 +02:00
done += batch . length
2025-07-31 12:18:57 +02:00
const now = new Date ( )
const timeElapsed = ( now . getTime ( ) - startDate . getTime ( ) ) / 1000
2025-08-13 23:06:38 +02:00
const speed = ( "" + done / timeElapsed ) . substring ( 0 , 5 )
const perc = ( "" + ( 100 * ( done + this . skipped ) ) / estimate ) . substring ( 0 , 5 )
const etaSecond = Math . floor ( ( ( estimate - done - this . skipped ) * timeElapsed ) / done )
console . log (
"Completed" ,
numberOfThreads ,
` processes; ${
done + this . skipped
} / $ { estimate } , $ { perc } % , $ { speed } tile / second , ETA : $ { Utils . toHumanTime (
etaSecond
) } `
)
2025-07-31 01:03:54 +02:00
} while ( batch . length > 0 )
2025-09-25 14:12:28 +02:00
writeFileSync ( this . targetDir + "/Last_pm_tile_extracts.txt" ,
2025-09-25 14:03:02 +02:00
[ new Date ( ) . getTime ( ) + "" ,
new Date ( ) . toISOString ( ) ,
"# The script converting the planet-latest.pmtiles into sub-archives has been successfully executed at the stated time" ,
] . join ( "\n" ) , "utf-8" ,
)
2025-07-31 01:03:54 +02:00
}
}
new GeneratePmTilesExtracts ( ) . run ( )