|
var cluster = require('cluster'); |
|
var sqlite3 = require('sqlite3'); |
|
|
|
|
|
function Tile(z, x, y) { |
|
this.z = z; |
|
this.x = x; |
|
this.y = y; |
|
} |
|
|
|
Tile.fromZXY = function(num) { |
|
var y = num % 1e7; |
|
var n = (num - y) % 1e14; |
|
return new Tile((num - y - n) / 1e14, n / 1e7, y); |
|
}; |
|
|
|
|
|
|
|
if (cluster.isMaster) { |
|
var argv = require('optimist') |
|
.usage('Usage: $0 [filename]') |
|
.demand(1) |
|
.default('minzoom', 0) |
|
.default('maxzoom', 16) |
|
.default('bbox', '-180,-85,180,85') |
|
.default('concurrency', 8) |
|
.argv; |
|
|
|
argv.bbox = argv.bbox.split(',').map(parseFloat); |
|
argv.filename = require('path').resolve(argv._[0]); |
|
|
|
var lastSecond = 0; |
|
var checked = 0; |
|
var total = 0; |
|
var parents = 0; |
|
function log(msg) { |
|
if (msg.cmd === 'childless') { |
|
console.log(msg.tile.z + '/' + msg.tile.x + '/' + msg.tile.y); |
|
} else if (msg.cmd === 'checked') { |
|
checked += msg.checked; |
|
lastSecond += msg.checked; |
|
parents += msg.parents; |
|
} |
|
} |
|
|
|
// Fork workers. |
|
var workers = []; |
|
var remainingWorkers = 0; |
|
for (var i = 0; i < 8; i++) { |
|
var worker = cluster.fork(); |
|
worker.on('message', log); |
|
workers.push(worker); |
|
remainingWorkers++; |
|
} |
|
|
|
function reportProgress() { |
|
console.warn('checked ' + checked + '\t\t' + parents + '/' + total + ' parents\t\t' + lastSecond + '/s\t' + remainingWorkers + ' workers remaining'); |
|
lastSecond = 0; |
|
} |
|
|
|
var db = new sqlite3.Database(argv.filename, sqlite3.OPEN_READONLY); |
|
db.get('SELECT COUNT(zxy) AS length FROM map WHERE zxy >= ? AND zxy < ?', |
|
argv.minzoom*1e14, (argv.minzoom+1)*1e14, function(err, row) { |
|
if (err) throw err; |
|
|
|
setInterval(reportProgress, 1000); |
|
|
|
var offset = 0; |
|
total = row.length; |
|
var chunk = Math.ceil(row.length / 8); |
|
for (var i = 0; i < workers.length; i++) { |
|
workers[i].send({ |
|
'cmd': 'start', |
|
argv: argv, |
|
offset: offset, |
|
limit: i == workers.length - 1 ? row.length - offset : chunk |
|
}); |
|
offset += chunk; |
|
} |
|
}); |
|
|
|
cluster.on('death', function(worker) { |
|
console.warn('worker ' + worker.pid + ' exited'); |
|
remainingWorkers--; |
|
if (remainingWorkers === 0) { |
|
reportProgress(); |
|
process.exit(0); |
|
} |
|
}); |
|
} else { |
|
var db, iterator, checker; |
|
var stack = []; |
|
var maxzoom; |
|
var available = 0; |
|
var running = 0; |
|
var checked = 0; |
|
var parents = 0; |
|
|
|
var interval = setInterval(report, 100); |
|
process.on('message', starter); |
|
|
|
function starter(msg) { |
|
if (msg.cmd === 'start') { |
|
process.removeListener('message', starter); |
|
run(msg.argv, msg.offset, msg.limit); |
|
} |
|
} |
|
|
|
function report() { |
|
process.send({ cmd: 'checked', checked: checked, parents: parents }); |
|
parents = 0; |
|
checked = 0; |
|
} |
|
|
|
function run(argv, offset, limit) { |
|
available = argv.concurrency; |
|
maxzoom = argv.maxzoom - 1; |
|
db = new sqlite3.Database(argv.filename, sqlite3.OPEN_READONLY); |
|
iterator = db.prepare('SELECT zxy FROM tiles WHERE zxy >= ? AND zxy < ? LIMIT ?, ?', |
|
argv.minzoom*1e14, (argv.minzoom+1)*1e14, offset, limit, function(err) { |
|
if (err) throw err; |
|
|
|
checker = db.prepare('SELECT zxy FROM tiles WHERE zxy IN (?, ?, ?, ?)', function(err) { |
|
if (err) throw err; |
|
while (available > 0) next(); |
|
}); |
|
}); |
|
} |
|
|
|
function next() { |
|
running++; |
|
available--; |
|
|
|
if (stack.length) { |
|
return query(stack.pop()); |
|
} |
|
|
|
// The stack is empty. Fall back to scanline for the top zoom level. |
|
iterator.get(function(err, row) { |
|
if (err) throw err; |
|
if (row) { |
|
parents++; |
|
return query(Tile.fromZXY(row.zxy)); |
|
} else { |
|
running--; |
|
available++; |
|
if (running === 0) { |
|
report(); |
|
iterator.finalize(); |
|
checker.finalize(); |
|
clearInterval(interval); |
|
db.close(function() { |
|
setTimeout(function() { |
|
process.exit(0); |
|
}, 1000); |
|
}); |
|
} |
|
} |
|
}); |
|
}; |
|
|
|
function query(tile) { |
|
var child = (tile.z+1)*1e14 + tile.x*2e7 + tile.y*2; |
|
|
|
checker.all(child, child+1e7, child+1, child+1e7+1, function check(err, rows) { |
|
running--; |
|
available++; |
|
checked++; |
|
if (err) { |
|
console.warn(err.stack); |
|
} else if (!rows.length) { |
|
// There's no child tile. |
|
process.send({ cmd: 'childless', tile: tile }); |
|
} else if (tile.z < maxzoom) { |
|
for (var i = 0; i < rows.length; i++) { |
|
stack.push(Tile.fromZXY(rows[i].zxy)); |
|
} |
|
} |
|
|
|
while (available > 0) next(); |
|
}); |
|
} |
|
} |