diff --git a/libs/Task.js b/libs/Task.js index ae9dd38..2f98c9a 100644 --- a/libs/Task.js +++ b/libs/Task.js @@ -26,7 +26,7 @@ let glob = require("glob"); let path = require('path'); let rmdir = require('rimraf'); let odmRunner = require('./odmRunner'); -let gdalRunner = require('./gdalRunner'); +let processRunner = require('./processRunner'); let archiver = require('archiver'); let os = require('os'); let Directories = require('./Directories'); @@ -46,8 +46,7 @@ module.exports = class Task{ this.options = options; this.gpcFiles = []; this.output = []; - this.runnerProcess = null; - this.tilingProcess = null; + this.runningProcesses = []; async.series([ // Read images info @@ -198,19 +197,14 @@ module.exports = class Task{ this.setStatus(statusCodes.CANCELED); if (wasRunning){ - if (this.runnerProcess){ + this.runningProcesses.forEach(proc => { // TODO: this does NOT guarantee that // the process will immediately terminate. - // In fact, often times ODM will continue running for a while + // For eaxmple in the case of the ODM process, the process will continue running for a while // This might need to be fixed on ODM's end. - this.runnerProcess.kill('SIGINT'); - this.runnerProcess = null; - } - - if (this.tilingProcess){ - this.tilingProcess.kill('SIGINT'); - this.tilingProcess = null; - } + proc.kill('SIGINT'); + }); + this.runningProcesses = []; } this.stopTrackingProcessingTime(true); @@ -290,34 +284,50 @@ module.exports = class Task{ }); }); }else{ - archive.finalize() + archive.finalize(); } }; }; + const handleProcessExit = (done) => { + return (err, code, signal) => { + if (err) done(err); + else{ + // Don't evaluate if we caused the process to exit via SIGINT? + if (code === 0) done(); + else done(new Error(`Process exited with code ${code}`)); + } + }; + }; + + const handleOutput = output => { + this.output.push(output); + }; + const generateTiles = (inputFile, outputDir) => { return (done) => { - this.tilingProcess = gdalRunner.runTiler({ + this.runningProcesses.push(processRunner.runTiler({ zoomLevels: "16-21", inputFile: path.join(this.getProjectFolderPath(), inputFile), outputDir: path.join(this.getProjectFolderPath(), outputDir) - }, (err, code, signal) => { - if (err) done(err); - else{ - // Don't evaluate if we caused the process to exit via SIGINT? - if (code === 0) done(); - else done(new Error(`Process exited with code ${code}`)); - } - }, output => { - this.output.push(output); - }); + }, handleProcessExit(done), handleOutput)); + }; + }; + + const generatePotreeCloud = (inputFile, outputDir) => { + return (done) => { + this.runningProcesses.push(processRunner.runPotreeConverter({ + inputFile: path.join(this.getProjectFolderPath(), inputFile), + outputDir: path.join(this.getProjectFolderPath(), outputDir) + }, handleProcessExit(done), handleOutput)); }; }; // All paths are relative to the project directory (./data//) async.series([ generateTiles(path.join('odm_orthophoto', 'odm_orthophoto.tif'), 'orthophoto_tiles'), - createZipArchive('all.zip', ['odm_orthophoto', 'odm_georeferencing', 'odm_texturing', 'odm_meshing', 'orthophoto_tiles']), + generatePotreeCloud(path.join('odm_georeferencing', 'odm_georeferenced_model.ply.las'), 'potree_pointcloud'), + createZipArchive('all.zip', ['odm_orthophoto', 'odm_georeferencing', 'odm_texturing', 'odm_meshing', 'orthophoto_tiles', 'potree_pointcloud']), createZipArchive('georeferenced_model.ply.zip', [path.join('odm_georeferencing', 'odm_georeferenced_model.ply')]), createZipArchive('georeferenced_model.las.zip', [path.join('odm_georeferencing', 'odm_georeferenced_model.ply.las')]), createZipArchive('georeferenced_model.csv.zip', [path.join('odm_georeferencing', 'odm_georeferenced_model.csv')]), @@ -355,7 +365,7 @@ module.exports = class Task{ runnerOptions["odm_georeferencing-gcpFile"] = fs.realpathSync(path.join(this.getGpcFolderPath(), this.gpcFiles[0])); } - this.runnerProcess = odmRunner.run(runnerOptions, (err, code, signal) => { + this.runningProcesses.push(odmRunner.run(runnerOptions, (err, code, signal) => { if (err){ this.setStatus(statusCodes.FAILED, {errorMessage: `Could not start process (${err.message})`}); finished(err); @@ -376,7 +386,8 @@ module.exports = class Task{ // Replace console colors output = output.replace(/\x1b\[[0-9;]*m/g, ""); this.output.push(output); - }); + }) + ); return true; }else{ diff --git a/libs/gdalRunner.js b/libs/gdalRunner.js deleted file mode 100644 index ebada72..0000000 --- a/libs/gdalRunner.js +++ /dev/null @@ -1,72 +0,0 @@ -/* -Node-OpenDroneMap Node.js App and REST API to access OpenDroneMap. -Copyright (C) 2016 Node-OpenDroneMap Contributors - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -"use strict"; -let fs = require('fs'); -let path = require('path'); -let assert = require('assert'); -let spawn = require('child_process').spawn; -let config = require('../config.js'); -let logger = require('./logger'); - - -module.exports = { - runTiler: function(options, done, outputReceived){ - assert(options.zoomLevels !== undefined, "zoomLevels must be defined"); - assert(options.inputFile !== undefined, "inputFile must be defined"); - assert(options.outputDir !== undefined, "outputDir must be defined"); - - let command = ["-z", options.zoomLevels, - "-n", - "-w", "none", - options.inputFile, - options.outputDir - ]; - logger.info(`About to run: gdal2tiles.py ${command.join(" ")}`); - - if (config.test){ - logger.info("Test mode is on, command will not execute"); - - let outputTestFile = path.join("..", "tests", "gdal2tiles_output.txt"); - fs.readFile(path.resolve(__dirname, outputTestFile), 'utf8', (err, text) => { - if (!err){ - let lines = text.split("\n"); - lines.forEach(line => outputReceived(line)); - - done(null, 0, null); - }else{ - logger.warn(`Error: ${err.message}`); - done(err); - } - }); - - return;// Skip rest - } - - // Launch - let childProcess = spawn("gdal2tiles.py", command); - - childProcess - .on('exit', (code, signal) => done(null, code, signal)) - .on('error', done); - - childProcess.stdout.on('data', chunk => outputReceived(chunk.toString())); - childProcess.stderr.on('data', chunk => outputReceived(chunk.toString())); - - return childProcess; - } -}; diff --git a/libs/processRunner.js b/libs/processRunner.js new file mode 100644 index 0000000..fb94aa9 --- /dev/null +++ b/libs/processRunner.js @@ -0,0 +1,93 @@ +/* +Node-OpenDroneMap Node.js App and REST API to access OpenDroneMap. +Copyright (C) 2016 Node-OpenDroneMap Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +"use strict"; +let fs = require('fs'); +let path = require('path'); +let assert = require('assert'); +let spawn = require('child_process').spawn; +let config = require('../config.js'); +let logger = require('./logger'); + + +function makeRunner(command, args, requiredOptions = [], outputTestFile = null){ + return function(options, done, outputReceived){ + for (let requiredOption of requiredOptions){ + assert(options[requiredOption] !== undefined, `${requiredOption} must be defined`); + } + + let commandArgs = args; + if (typeof commandArgs === 'function') commandArgs = commandArgs(options); + + logger.info(`About to run: ${command} ${commandArgs.join(" ")}`); + + if (config.test){ + logger.info("Test mode is on, command will not execute"); + + if (outputTestFile){ + fs.readFile(path.resolve(__dirname, outputTestFile), 'utf8', (err, text) => { + if (!err){ + let lines = text.split("\n"); + lines.forEach(line => outputReceived(line)); + + done(null, 0, null); + }else{ + logger.warn(`Error: ${err.message}`); + done(err); + } + }); + } + + return;// Skip rest + } + + // Launch + let childProcess = spawn(command, commandArgs); + + childProcess + .on('exit', (code, signal) => done(null, code, signal)) + .on('error', done); + + childProcess.stdout.on('data', chunk => outputReceived(chunk.toString())); + childProcess.stderr.on('data', chunk => outputReceived(chunk.toString())); + + return childProcess; + }; +} + + +module.exports = { + runTiler: makeRunner("gdal2tiles.py", + function(options){ + return ["-z", options.zoomLevels, + "-n", + "-w", "none", + options.inputFile, + options.outputDir + ]; + }, + ["zoomLevels", "inputFile", "outputDir"], + path.join("..", "tests", "gdal2tiles_output.txt")), + + runPotreeConverter: makeRunner("PotreeConverter", + function(options){ + return [options.inputFile, + "-o", options.outputDir]; + }, + ["inputFile", "outputDir"], + path.join("..", "tests", "potree_output.txt")) +}; diff --git a/tests/potree_output.txt b/tests/potree_output.txt new file mode 100644 index 0000000..04a746a --- /dev/null +++ b/tests/potree_output.txt @@ -0,0 +1,28 @@ +== params == +source[0]: /home/user/file.las +outdir: /home/user/output +spacing: 0 +diagonal-fraction: 250 +levels: -1 +format: +scale: 0 +pageName: +output-format: BINARY + +AABB: +min: [319,738, 3.0939e+06, -45.3844] +max: [319,831, 3.09405e+06, 11.8638] +size: [92.2953, 145.944, 57.2482] + +cubic AABB: +min: [319,738, 3.0939e+06, -45.3844] +max: [319,884, 3.09405e+06, 100.56] +size: [145.944, 145.944, 145.944] + +spacing calculated from diagonal: 1.01113 +READING: /home/user/file.las +closing writer + +conversion finished +268,568 points were processed and 268,568 points ( 100% ) were written to the output. +duration: 0.267s diff --git a/tests/processing_results/potree_pointcloud/cloud.js b/tests/processing_results/potree_pointcloud/cloud.js new file mode 100644 index 0000000..e69de29