Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make composite tile #4

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tools/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
gzipTileset : require('./lib/gzipTileset'),
makeCompositeTile : require('./lib/makeCompositeTile'),
pipeline : require('./lib/runPipeline')
};
13 changes: 4 additions & 9 deletions tools/lib/getDefaultWriteCallback.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
'use strict';
var fsExtra = require('fs-extra');
var path = require('path');
var Promise = require('bluebird');

var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile);
var writeTile = require('./writeTile');

module.exports = getDefaultWriteCallback;

/**
* @private
*/
function getDefaultWriteCallback(outputDirectory) {
return function(file, data) {
var outputFile = path.join(outputDirectory, file);
return fsExtraOutputFile(outputFile, data);
function getDefaultWriteCallback() {
return function(file, data, options) {
return writeTile(file, data, options);
Copy link
Contributor

@lilleyse lilleyse Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to just keep this really simple like it was before where it just gets data and write to a file. Gzipping or anything else should be handled by the calling code. Maybe this makes writeTile not needed anymore.

};
}
39 changes: 39 additions & 0 deletions tools/lib/getFilesInDirectory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';
var Cesium = require('cesium');
var Promise = require('bluebird');
var fsExtra = require('fs-extra');
var path = require('path');

var defaultValue = Cesium.defaultValue;

var fsExtraReaddir = Promise.promisify(fsExtra.readdir);
var fsExtraStat = Promise.promisify(fsExtra.stat);

module.exports = getFilesInDirectory;

function getFilesInDirectory(directory, options) {
var files = [];
options = defaultValue(options, defaultValue);
var recursive = defaultValue(options.recursive, false);
var filter = defaultValue(options.filter, function() {
return true;
});
return findFiles(directory, files, recursive, filter);
}

function findFiles(directory, files, recursive, filter) {
return fsExtraReaddir(directory).map(function(fileName) {
var fullPath = path.join(directory, fileName);
return fsExtraStat(fullPath)
.then(function(stats) {
if (stats.isFile() && filter(fullPath)) {
files.push(fullPath);
} else if (recursive && stats.isDirectory()) {
return findFiles(fullPath, files, recursive, filter);
}
});
})
.then(function() {
return files;
});
}
111 changes: 19 additions & 92 deletions tools/lib/gzipTileset.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
'use strict';
var Cesium = require('cesium');
var fsExtra = require('fs-extra');
var path = require('path');
var Promise = require('bluebird');
var path = require('path');
var zlib = require('zlib');
var getDefaultWriteCallback = require('./getDefaultWriteCallback');
var isGzipped = require('./isGzipped');

var fsExtraReadFile = Promise.promisify(fsExtra.readFile);
var getFilesInDirectory = require('./getFilesInDirectory');
var isTileFile = require('./isTileFile');
var readTile = require('./readTile');

var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined;
var DeveloperError = Cesium.DeveloperError;

var zlibGzip = Promise.promisify(zlib.gzip);

module.exports = gzipTileset;

/**
Expand Down Expand Up @@ -40,100 +41,26 @@ function gzipTileset(options) {
outputDirectory = path.normalize(defaultValue(outputDirectory,
path.join(path.dirname(inputDirectory), path.basename(inputDirectory) + '-' + (gzip ? 'gzipped' : 'ungzipped'))));

var writeCallback = defaultValue(options.writeCallback, getDefaultWriteCallback(outputDirectory));
var writeCallback = defaultValue(options.writeCallback, getDefaultWriteCallback());
var logCallback = options.logCallback;

if (defined(logCallback)) {
logCallback((gzip ? 'Compressing' : 'Uncompressing') + ' files...');
}

var operation = gzip ? zlib.gzipSync : zlib.gunzipSync;

return new Promise(function(resolve, reject) {
getNumberOfFilesInDirectory(inputDirectory)
.then(function(numberOfFiles) {
var writeFile = getWriteFile(writeCallback, numberOfFiles, resolve, reject);
fsExtra.walk(inputDirectory)
.on('data', function (item) {
if (!item.stats.isDirectory()) {
var inputFile = item.path;
var file = path.relative(inputDirectory, item.path);

if (gzip && tilesOnly && !isTile(inputFile)) {
copyFile(inputFile, file, writeFile);
} else {
isGzipped(inputFile)
.then(function(fileIsGzipped) {
if (fileIsGzipped === gzip) {
// File is already in the correct state
copyFile(inputFile, file, writeFile);
} else {
fsExtraReadFile(inputFile)
.then(function(data) {
data = operation(data);
writeFile(file, data);
})
.catch(reject);
}
})
.catch(reject);
}
}
})
.on('error', reject);
})
.catch(reject);
});
}

function isTile(file) {
var extension = path.extname(file);
return extension === '.b3dm' ||
extension === '.i3dm' ||
extension === '.pnts' ||
extension === '.cmpt' ||
extension === '.vctr';
}

function getNumberOfFilesInDirectory(directory) {
return new Promise(function(resolve, reject) {
var numberOfFiles = 0;
fsExtra.walk(directory)
.on('data', function (item) {
if (!item.stats.isDirectory()) {
++numberOfFiles;
return getFilesInDirectory(inputDirectory, {
Copy link
Contributor

@lilleyse lilleyse Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used to do a similar approach here, but changed it for this comment: #3 (comment). I'm fine with merging the changes in this file in though.

I have a branch that cleans up gzipTileset and the end result looks similar to this, but walks the directory without buffering the file names up front (through a helper function). And really that too is probably going to get blown away again at some later point.

recursive: true
}).map(function (filename) {
var writeFile = path.join(outputDirectory, path.relative(inputDirectory, filename));
return readTile(filename)
.then(function(data) {
if (gzip && (!tilesOnly || isTileFile(writeFile))) {
return zlibGzip(data);
}
return Promise.resolve(data);
})
.on('end', function () {
resolve(numberOfFiles);
})
.on('error', reject);
.then(function(data) {
return writeCallback(writeFile, data);
});
});
}

function getWriteFile(writeCallback, numberOfFiles, resolve, reject) {
var numberComplete = 0;
function complete() {
++numberComplete;
if (numberComplete === numberOfFiles) {
resolve();
}
}
return function(file, data) {
var promise = writeCallback(file, data);
if (defined(promise)) {
promise
.then(complete)
.catch(reject);
} else {
complete();
}
};
}

function copyFile(inputFile, file, writeFile) {
return fsExtraReadFile(inputFile)
.then(function(data) {
return writeFile(file, data);
});
}
23 changes: 13 additions & 10 deletions tools/lib/isGzipped.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
'use strict';
var fsExtra = require('fs-extra');
var Promise = require('bluebird');
var Cesium = require('cesium');

var fsExtraReadFile = Promise.promisify(fsExtra.readFile);
var DeveloperError = Cesium.DeveloperError;
var defined = Cesium.defined;

module.exports = isGzipped;

/**
* @private
* Test if the provided data is gzipped.
*
* @param {Buffer} data A buffer containing the data to test.
* @returns {Boolean} True if the data is gzipped, False if not.
*/
function isGzipped(file) {
return fsExtraReadFile(file)
.then(function (data) {
return (data[0] === 0x1f) && (data[1] === 0x8b);
});
}
function isGzipped(data) {
if (!defined(data)) {
throw new DeveloperError('data must be defined.');
}
return data[0] === 0x1f && data[1] === 0x8b;
}
18 changes: 18 additions & 0 deletions tools/lib/isGzippedFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';
var Promise = require('bluebird');
var fsExtra = require('fs-extra');
var isGzipped = require('./isGzipped');

var fsExtraReadFile = Promise.promisify(fsExtra.readFile);

module.exports = isGzippedFile;

/**
* @private
*/
function isGzippedFile(file) {
return fsExtraReadFile(file)
.then(function (data) {
return isGzipped(data);
});
}
13 changes: 13 additions & 0 deletions tools/lib/isTileFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';
var path = require('path');

module.exports = isTileFile;

function isTileFile(file) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark as @Private

var extension = path.extname(file);
return extension === '.b3dm' ||
extension === '.i3dm' ||
extension === '.pnts' ||
extension === '.cmpt' ||
extension === '.vctr';
}
31 changes: 31 additions & 0 deletions tools/lib/makeCompositeTile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';
module.exports = makeCompositeTile;

/**
* Combines an array of tile buffers into a single composite tile.
*
* @param {Buffer[]} tileBuffers An array of buffers holding tile data.
* @returns {Buffer} A single buffer holding the composite tile.
*/
function makeCompositeTile(tileBuffers) {
var header = new Buffer(16);
var buffers = [];
buffers.push(header);
var byteLength = header.length;
for (var i = 0; i < tileBuffers.length; i++) {
var tile = tileBuffers[i];
// Byte align all tiles to 4 bytes
var tilePadding = tile.length % 4;
if (tilePadding !== 0) {
tile = Buffer.concat([tile, new Buffer(4 - tilePadding)]);
}
tile.writeUInt32LE(tile.length, 8); // byteLength
byteLength += tile.length;
buffers.push(tile);
}
header.write('cmpt', 0); // magic
header.writeUInt32LE(1, 4); // version
header.writeUInt32LE(byteLength, 8); // byteLength
header.writeUInt32LE(tileBuffers.length, 12); // tilesLength
return Buffer.concat(buffers);
}
33 changes: 33 additions & 0 deletions tools/lib/readTile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';
var Cesium = require('cesium');
var Promise = require('bluebird');
var fsExtra = require('fs-extra');
var zlib = require('zlib');
var isGzipped = require('./isGzipped');

var DeveloperError = Cesium.DeveloperError;
var defined = Cesium.defined;

var fsExtraReadFile = Promise.promisify(fsExtra.readFile);
var zlibGunzip = Promise.promisify(zlib.gunzip);

module.exports = readTile;

/**
* Reads tile data from a file.
*
* @param {String} filePath The file path to read from.
* @returns {Promise} A promise that resolves with the data when the read operation completes.
*/
function readTile(filePath) {
if (!defined(filePath)) {
throw new DeveloperError('filePath must be defined');
}
return fsExtraReadFile(filePath)
.then(function(buffer) {
if (isGzipped(buffer)) {
return zlibGunzip(buffer);
}
return buffer;
});
}
14 changes: 7 additions & 7 deletions tools/lib/runPipeline.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';
var Cesium = require('cesium');
var Promise = require('bluebird');
var fsExtra = require('fs-extra');
var path = require('path');
var Promise = require('bluebird');
var getWorkingDirectory = require('./getWorkingDirectory');
var gzipTileset = require('./gzipTileset');

Expand Down Expand Up @@ -90,22 +90,22 @@ function runPipeline(pipeline, options) {
}

stageObjects.push({
options : stageOptions,
stageFunction : stageFunction,
name : stageName
options: stageOptions,
stageFunction: stageFunction,
name: stageName
});
}

// Run the stages in sequence
return Promise.each(stageObjects, function(stage) {
return Promise.each(stageObjects, function (stage) {
return fsExtraEmptyDir(stage.options.outputDirectory)
.then(function() {
.then(function () {
if (defined(logCallback)) {
logCallback('Running ' + stage.name);
}
return stage.stageFunction(stage.options);
});
}).finally(function() {
}).finally(function () {
return Promise.all([
fsExtraRemove(workingDirectory1),
fsExtraRemove(workingDirectory2)
Expand Down
Loading