def runProcess(processIndex, tasksQueue, resultsQueue, outputFolder, runMode, useLink):
    kill_received = False
    while not kill_received:
        tileAbsPath = None
        try:
            # This call will patiently wait until new job is available
            tileAbsPath = tasksQueue.get()
        except:
            # if there is an error we will quit
            kill_received = True
        if tileAbsPath == None:
            # If we receive a None job, it means we can stop
            kill_received = True
        else:
            tileOutputFolder = outputFolder + '/' + os.path.basename(tileAbsPath)
            os.system('mkdir -p ' + tileOutputFolder)
            tileFilesAbsPaths = utils.getFiles(tileAbsPath, recursive = True)
            for tileFileAbsPath in tileFilesAbsPaths:
                outputAbsPath = outputFolder + '/' + os.path.basename(tileAbsPath) + '/' + os.path.basename(tileFileAbsPath)
                commands = []
                if 's' in runMode:
                    commands.append('lassort.exe -i ' + tileFileAbsPath + ' -o ' + outputAbsPath)
                else:
                    if useLink:
                        commands.append('ln -s ' + tileFileAbsPath + ' ' + outputAbsPath)
                    else:
                        commands.append('cp ' + tileFileAbsPath + ' ' + outputAbsPath)
                if 'i' in runMode:
                    commands.append('lasindex -i ' + outputAbsPath)
                for command in commands:
                    utils.shellExecute(command, True)
            resultsQueue.put((processIndex, tileAbsPath)) 
def runProcess(processIndex, tasksQueue, resultsQueue, minX, minY, maxX, maxY, outputFolder, tempFolder, axisTiles):
    kill_received = False
    while not kill_received:
        inputFile = None
        try:
            # This call will patiently wait until new job is available
            inputFile = tasksQueue.get()
        except:
            # if there is an error we will quit
            kill_received = True
        if inputFile == None:
            # If we receive a None job, it means we can stop
            kill_received = True
        else:            
            # Get number of points and BBOX of this file
            (fCount, fMinX, fMinY, _, fMaxX, fMaxY, _, _, _, _, _, _, _) = utils.getPCFileDetails(inputFile)
            print 'Processing', os.path.basename(inputFile), fCount, fMinX, fMinY, fMaxX, fMaxY
            # For the four vertices of the BBOX we get in which tile they should go
            posMinXMinY = getTileIndex(fMinX, fMinY, minX, minY, maxX, maxY, axisTiles)
            posMinXMaxY = getTileIndex(fMinX, fMaxY, minX, minY, maxX, maxY, axisTiles)
            posMaxXMinY = getTileIndex(fMaxX, fMinY, minX, minY, maxX, maxY, axisTiles)
            posMaxXMaxY = getTileIndex(fMaxX, fMaxY, minX, minY, maxX, maxY, axisTiles)

            if (posMinXMinY == posMinXMaxY) and (posMinXMinY == posMaxXMinY) and (posMinXMinY == posMaxXMaxY):
                # If they are the same the whole file can be directly copied to the tile
                tileFolder = outputFolder + '/' + getTileName(*posMinXMinY)
                if not os.path.isdir(tileFolder):
                    utils.shellExecute('mkdir -p ' + tileFolder)
                utils.shellExecute('cp ' + inputFile + ' ' + tileFolder)
            else:
                # If not, we run PDAL gridder to split the file in pieces that can go to the tiles
                tGCount = runPDALSplitter(processIndex, inputFile, outputFolder, tempFolder, minX, minY, maxX, maxY, axisTiles)
                if tGCount != fCount:
                    print 'WARNING: split version of ', inputFile, ' does not have same number of points (', tGCount, 'expected', fCount, ')'
            resultsQueue.put((processIndex, inputFile, fCount))   
def run(inputFolder, outputFolder, runMode, useLink, numberProcs):
    # Check input parameters
    if not os.path.isdir(inputFolder):
        raise Exception('Error: Input folder does not exist!')
    if os.path.isfile(outputFolder):
        raise Exception('Error: There is a file with the same name as the output folder. Please, delete it!')
    elif os.path.isdir(outputFolder) and os.listdir(outputFolder):
        raise Exception('Error: Output folder exists and it is not empty. Please, delete the data in the output folder!')
    if runMode not in ('s','i','si','is'):
        raise Exception('Error: running mode must be s, i, or si')

    
    # Make it absolute path
    inputFolder = os.path.abspath(inputFolder)

    utils.shellExecute('mkdir -p ' + outputFolder)

    # Create queues for the distributed processing
    tasksQueue = multiprocessing.Queue() # The queue of tasks (inputFiles)
    resultsQueue = multiprocessing.Queue() # The queue of results
    
    tilesNames = os.listdir(inputFolder)
    if 'tiles.js' in tilesNames:
        tilesNames.remove('tiles.js')
        utils.shellExecute('cp ' + inputFolder + '/tiles.js ' + outputFolder+ '/tiles.js')
    
    numTiles = len(tilesNames)
    
    # Add tasks/inputFiles
    for i in range(numTiles):
        tasksQueue.put(inputFolder + '/' + tilesNames[i])
    for i in range(numberProcs): #we add as many None jobs as numberProcs to tell them to terminate (queue is FIFO)
        tasksQueue.put(None)

    processes = []
    # We start numberProcs users processes
    for i in range(numberProcs):
        processes.append(multiprocessing.Process(target=runProcess, 
            args=(i, tasksQueue, resultsQueue, outputFolder, runMode, useLink)))
        processes[-1].start()

    # Get all the results (actually we do not need the returned values)
    for i in range(numTiles):
        resultsQueue.get()
        print 'Completed %d of %d (%.02f%%)' % (i+1, numTiles, 100. * float(i+1) / float(numTiles))
    # wait for all users to finish their execution
    for i in range(numberProcs):
        processes[i].join()
def run(inputFolder, outputFolder, format, levels, spacing, extent, numberProcs):
    # Check input parameters
    if not os.path.isdir(inputFolder):
        raise Exception('Error: Input folder does not exist!')
    if os.path.isfile(outputFolder):
        raise Exception('Error: There is a file with the same name as the output folder. Please, delete it!')
    elif os.path.isdir(outputFolder) and os.listdir(outputFolder):
        raise Exception('Error: Output folder exists and it is not empty. Please, delete the data in the output folder!')
    
    # Make it absolute path
    inputFolder = os.path.abspath(inputFolder)

    utils.shellExecute('mkdir -p ' + outputFolder)

    # Create queues for the distributed processing
    tasksQueue = multiprocessing.Queue() # The queue of tasks (inputFiles)
    resultsQueue = multiprocessing.Queue() # The queue of results
    
    tilesNames = os.listdir(inputFolder)
    if 'tiles.js' in tilesNames:
        tilesNames.remove('tiles.js')
        utils.shellExecute('cp ' + inputFolder + '/tiles.js ' + outputFolder+ '/tiles.js')
    
    numTiles = len(tilesNames)
    
    # Add tasks/inputFiles
    for i in range(numTiles):
        tasksQueue.put(inputFolder + '/' + tilesNames[i])
    for i in range(numberProcs): #we add as many None jobs as numberProcs to tell them to terminate (queue is FIFO)
        tasksQueue.put(None)

    processes = []
    # We start numberProcs users processes
    for i in range(numberProcs):
        processes.append(multiprocessing.Process(target=runProcess, 
            args=(i, tasksQueue, resultsQueue, outputFolder, format, levels, spacing, extent)))
        processes[-1].start()

    # Get all the results (actually we do not need the returned values)
    for i in range(numTiles):
        resultsQueue.get()
        print 'Completed %d of %d (%.02f%%)' % (i+1, numTiles, 100. * float(i+1) / float(numTiles))
    # wait for all users to finish their execution
    for i in range(numberProcs):
        processes[i].join()
def runProcess(processIndex, tasksQueue, resultsQueue, outputFolder, format, levels, spacing, extent):
    kill_received = False
    while not kill_received:
        tileAbsPath = None
        try:
            # This call will patiently wait until new job is available
            tileAbsPath = tasksQueue.get()
        except:
            # if there is an error we will quit
            kill_received = True
        if tileAbsPath == None:
            # If we receive a None job, it means we can stop
            kill_received = True
        else:
            tileOutputFolder = outputFolder + '/' + os.path.basename(tileAbsPath)
            os.system('mkdir -p ' + tileOutputFolder)
            c = 'PotreeConverter --outdir ' + tileOutputFolder +  ' --levels ' + str(levels) + ' --output-format ' + str(format).upper() + ' --source ' + tileAbsPath + ' --spacing ' + str(spacing) + ' --aabb "' + extent + '" >> ' + tileOutputFolder + '.log  2>&1'
            utils.shellExecute(c, True)
            resultsQueue.put((processIndex, tileAbsPath)) 
def runProcess(processIndex, tasksQueue, resultsQueue, outputFolder, format, levels, spacing, extent):
    kill_received = False
    while not kill_received:
        tileAbsPath = None
        try:
            # This call will patiently wait until new job is available
            tileAbsPath = tasksQueue.get()
        except:
            # if there is an error we will quit
            kill_received = True
        if tileAbsPath == None:
            # If we receive a None job, it means we can stop
            kill_received = True
        else:
            tileOutputFolder = outputFolder + '/' + os.path.basename(tileAbsPath)
            os.system('mkdir -p ' + tileOutputFolder)
            c = 'PotreeConverter --outdir ' + tileOutputFolder +  ' --levels ' + str(levels) + ' --output-format ' + str(format).upper() + ' --source ' + tileAbsPath + ' --spacing ' + str(spacing) + ' --aabb "' + extent + '" &> ' + tileOutputFolder + '.log'
            utils.shellExecute(c, True)
            resultsQueue.put((processIndex, tileAbsPath)) 
def run(inputFolderA, inputFolderB, outputFolder, moveFiles):
    # Check input parameters
    if (not os.path.isdir(inputFolderA)) or (not os.path.isdir(inputFolderB)):
        raise Exception('Error: Some of the input folder does not exist!')
    if os.path.isfile(outputFolder):
        raise Exception('Error: There is a file with the same name as the output folder. Please, delete it!')
    elif os.path.isdir(outputFolder) and os.listdir(outputFolder):
        raise Exception('Error: Output folder exists and it is not empty. Please, delete the data in the output folder!')
    
    # Make the paths absolute path
    inputFolderA = os.path.abspath(inputFolderA)
    inputFolderB = os.path.abspath(inputFolderB)
    outputFolder = os.path.abspath(outputFolder)
    
    if moveFiles:
        cmcommand = 'mv '
    else:
        cmcommand = 'cp -r '
    
    dataA = inputFolderA + '/data'
    dataB = inputFolderB + '/data'
    dataO = outputFolder + '/data'
    
    # Check if the octtrees have actual data (i.e. one folder with the root node)
    hasNodeA = os.listdir(dataA) == ['r',] 
    hasNodeB = os.listdir(dataB) == ['r',] 
    
    if hasNodeA or hasNodeB:
        utils.shellExecute('mkdir -p ' + outputFolder)
        if hasNodeA and hasNodeB:
            # If both Octrees have data we need to merge them
            # Create output cloud.js from joining the two input ones 
            cloudJSA = inputFolderA + '/cloud.js'
            cloudJSB = inputFolderB + '/cloud.js'
            if not (os.path.isfile(cloudJSA)) or not (os.path.isfile(cloudJSB)):
                raise Exception('Error: Some cloud.js is missing!')  
            # We also get the hierarchyStepSize
            hierarchyStepSize = createCloudJS(cloudJSA, cloudJSB, outputFolder + '/cloud.js')
            listFileRootA =  os.listdir(dataA + '/r')
            if 'r.las' in listFileRootA:
                extension = 'las'
            elif 'r.laz' in listFileRootA:
                extension = 'laz'
            else:
                raise Exception('Error: ' + __file__ + ' only compatible with las/laz format')
            joinNode('r', dataA + '/r', dataB + '/r', dataO + '/r', hierarchyStepSize, extension, cmcommand)
        elif hasA:
            utils.shellExecute(cmcommand + inputFolderA + '/* ' + outputFolder)
        else:
            utils.shellExecute(cmcommand + inputFolderB + '/* ' + outputFolder)            
    else:
        print 'Nothing to merge: both Octtrees are empty!'
def runPDALSplitter(processIndex, inputFile, outputFolder, tempFolder, minX, minY, maxX, maxY, axisTiles):
    pTempFolder = tempFolder + '/' + str(processIndex)
    if not os.path.isdir(pTempFolder):
        utils.shellExecute('mkdir -p ' + pTempFolder)
        
    # Get the lenght required by the PDAL split filter in order to get "squared" tiles
    lengthPDAL = (maxX - minX) /  float(axisTiles)
    
    utils.shellExecute('pdal split -i ' + inputFile + ' -o ' + pTempFolder + '/' + os.path.basename(inputFile) + ' --origin_x ' + str(minX) + ' --origin_y ' + str(minY) + ' --length ' + str(lengthPDAL))
    tGCount = 0
    for gFile in os.listdir(pTempFolder):
        (gCount, gFileMinX, gFileMinY, _, gFileMaxX, gFileMaxY, _, _, _, _, _, _, _) = utils.getPCFileDetails(pTempFolder + '/' + gFile)
        # This tile should match with some tile. Let's use the central point to see which one
        pX = gFileMinX + ((gFileMaxX - gFileMinX) / 2.)
        pY = gFileMinY + ((gFileMaxY - gFileMinY) / 2.)
        tileFolder = outputFolder + '/' + getTileName(*getTileIndex(pX, pY, minX, minY, maxX, maxY, axisTiles))
        if not os.path.isdir(tileFolder):
            utils.shellExecute('mkdir -p ' + tileFolder)
        utils.shellExecute('mv ' + pTempFolder + '/' + gFile + ' ' + tileFolder + '/' + gFile)
        tGCount += gCount
    return tGCount
#!/usr/bin/env python
"""Creates a PC file with a selection of points from a BBox and level"""

import argparse, traceback, time, os, psycopg2, datetime
import utils

# Check the LAStools is installed and that it is in the PATH before libLAS
if utils.shellExecute('lasmerge -version').count('LAStools') == 0:
    raise Exception("LAStools lasmerge is not found!. Please check that it is in PATH and that it is before libLAS binaries")

USERNAME = utils.getUserName()

def argument_parser():
    """ Define the arguments and return the parser object"""
    parser = argparse.ArgumentParser(
    description="""Creates a PC file with a selection of points from a BBox and level""")
    parser.add_argument('-s','--srid',default='',help='SRID',type=int, required=True)
    parser.add_argument('-e','--mail',   default='',help='E-mail address to send e-mail after completion',type=str, required=True)
    parser.add_argument('-b','--bbox',   default='', help='Bounding box for the points selection given as "minX minY maxX maxY"',required=True,type=str)
    parser.add_argument('-l','--level',  default='',help='Level of data used for the generation (only used if the used table is the one with the potree data). If not provided the raw data is used',type=str)
    parser.add_argument('-d','--dbname',default=utils.DB_NAME,help='Postgres DB name [default ' + utils.DB_NAME + ']',type=str)
    parser.add_argument('-u','--dbuser', default=USERNAME,help='DB user [default ' + USERNAME + ']',type=str)
    parser.add_argument('-p','--dbpass', default='',help='DB pass',type=str)
    parser.add_argument('-t','--dbhost', default='',help='DB host',type=str)
    parser.add_argument('-r','--dbport', default='',help='DB port',type=str)
    parser.add_argument('-w','--baseurl', default='',help='Base URL for the output file (web access)',type=str)
    parser.add_argument('-f','--basepath', default='',help='Base path for the output file (internal access)',type=str)
    return parser


def run(srid, userMail, level, bBox, dbName, dbPass, dbUser, dbHost, dbPort, baseURL, basePath):
#!/usr/bin/env python
"""Sort and index with LAStools the raw tiles (before Potree generation). Note that lassort requires LAStools license, otherwise small noise is introduced"""

import argparse, traceback, time, os, multiprocessing
import utils

# Check the LAStools is installed and that it is in the PATH before libLAS
if utils.shellExecute('lasindex -version').count('LAStools') == 0:
    raise Exception("LAStools lasindex is not found!. Please check that it is in PATH and that it is before libLAS binaries")
if utils.shellExecute('lassort.exe -version').count('LAStools') == 0:
    raise Exception("LAStools lasindex is not found!. Please check that it is in PATH and that it is before libLAS binaries")

def argument_parser():
    """ Define the arguments and return the parser object"""
    parser = argparse.ArgumentParser(
    description="Sort and index with LAStools the raw tiles (before Potree generation)")
    parser.add_argument('-i','--input',default='',help='Input folder with the tiles',type=str, required=True)
    parser.add_argument('-o','--output',default='',help='Output folder for the sorted and indexed tiles',type=str, required=True)
    parser.add_argument('-m','--mode',default='si',help='Running mode. Specify s to run only the lassort, i to run only the lasindex and si to run both. [default is si, i.e. to run both lassort and lasindex].',type=str)
    parser.add_argument('-l','--link',help='Use ln -s instead of cp in the case where no sorting is required [default False]',default=False,action='store_true')
    parser.add_argument('-c','--proc',default=1,help='Number of processes [default is 1]',type=int)
    return parser

def runProcess(processIndex, tasksQueue, resultsQueue, outputFolder, runMode, useLink):
    kill_received = False
    while not kill_received:
        tileAbsPath = None
        try:
            # This call will patiently wait until new job is available
            tileAbsPath = tasksQueue.get()
        except:
#!/usr/bin/env python
"""Create the OctTrees of each tile of the input data folder"""

import argparse, traceback, time, os, multiprocessing
import utils

# Check that PotreeConverter with extension to specify the AABB is installed
if utils.shellExecute('PotreeConverter -h').count('--aabb') == 0:
    raise Exception("PotreeConverter with extension to specify the AABB is not installed. Be sure to have PotreeConverter installed from https://github.com/oscarmartinezrubi/PotreeConverter")

def argument_parser():
    """ Define the arguments and return the parser object"""
    parser = argparse.ArgumentParser(
    description="Create the Octtrees of each tile of the input data folder")
    parser.add_argument('-i','--input',default='',help='Input folder with the tiles',type=str, required=True)
    parser.add_argument('-o','--output',default='',help='Output folder for the Potree data',type=str, required=True)
    parser.add_argument('-f','--format',default='',help='Format (LAS or LAZ)',type=str, required=True)
    parser.add_argument('-l','--levels',default='',help='Number of levels for OctTree',type=int, required=True)
    parser.add_argument('-s','--spacing',default='',help='Spacing at root level',type=int, required=True)
    parser.add_argument('-e','--extent',default='',help='Extent to be used for all the OctTree, specify as "minX minY minZ maxX maxY maxZ"',type=str, required=True)
    parser.add_argument('-c','--proc',default=1,help='Number of processes [default is 1]',type=int)
    return parser

def runProcess(processIndex, tasksQueue, resultsQueue, outputFolder, format, levels, spacing, extent):
    kill_received = False
    while not kill_received:
        tileAbsPath = None
        try:
            # This call will patiently wait until new job is available
            tileAbsPath = tasksQueue.get()
        except:
def run(inputFolder, outputFolder, tempFolder, extent, numberTiles, numberProcs):
    # Check input parameters
    if not os.path.isdir(inputFolder) and not os.path.isfile(inputFolder):
        raise Exception('Error: Input folder does not exist!')
    if os.path.isfile(outputFolder):
        raise Exception('Error: There is a file with the same name as the output folder. Please, delete it!')
    elif os.path.isdir(outputFolder) and os.listdir(outputFolder):
        raise Exception('Error: Output folder exists and it is not empty. Please, delete the data in the output folder!')
    # Get the number of tiles per dimension (x and y)
    axisTiles = math.sqrt(numberTiles) 
    if (not axisTiles.is_integer()) or (int(axisTiles) % 2):
        raise Exception('Error: Number of tiles must be the square of number which is power of 2!')
    axisTiles = int(axisTiles)
    
    # Create output and temporal folder
    utils.shellExecute('mkdir -p ' + outputFolder)
    utils.shellExecute('mkdir -p ' + tempFolder)
    
    (minX, minY, maxX, maxY) = extent.split(' ')
    minX = float(minX)
    minY = float(minY)
    maxX = float(maxX)
    maxY = float(maxY)
    
    if (maxX - minX) != (maxY - minY):
        raise Exception('Error: Tiling requires that maxX-minX must be equal to maxY-minY!')
    
    inputFiles = utils.getFiles(inputFolder, recursive=True)
    numInputFiles = len(inputFiles)
    print '%s contains %d files' % (inputFolder, numInputFiles)

    # Create queues for the distributed processing
    tasksQueue = multiprocessing.Queue() # The queue of tasks (inputFiles)
    resultsQueue = multiprocessing.Queue() # The queue of results
    
    # Add tasks/inputFiles
    for i in range(numInputFiles):
        tasksQueue.put(inputFiles[i])
    for i in range(numberProcs): #we add as many None jobs as numberProcs to tell them to terminate (queue is FIFO)
        tasksQueue.put(None)

    processes = []
    # We start numberProcs users processes
    for i in range(numberProcs):
        processes.append(multiprocessing.Process(target=runProcess, 
            args=(i, tasksQueue, resultsQueue, minX, minY, maxX, maxY, outputFolder, tempFolder, axisTiles)))
        processes[-1].start()

    # Get all the results (actually we do not need the returned values)
    numPoints = 0
    for i in range(numInputFiles):
        (processIndex, inputFile, inputFileNumPoints) = resultsQueue.get()
        numPoints += inputFileNumPoints
        print 'Completed %d of %d (%.02f%%)' % (i+1, numInputFiles, 100. * float(i+1) / float(numInputFiles))
    # wait for all users to finish their execution
    for i in range(numberProcs):
        processes[i].join()
    
    # Write the tile.js file with information about the tiles
    cFile = open(outputFolder + '/tiles.js', 'w')
    d = {}
    d["NumberPoints"] = numPoints
    d["numXTiles"] = axisTiles
    d["numYTiles"] = axisTiles
    d["boundingBox"] = {'lx':minX,'ly':minY,'ux':maxX,'uy':maxY}
    cFile.write(json.dumps(d,indent=4,sort_keys=True))
    cFile.close()
"""
This script is used to distribute the points of a bunch of LAS/LAZ files in 
different tiles. The XY extent of the different tiles match the XY extent of the 
nodes of a certain level of a octree defined by the provided bounding box (z 
is not required by the XY tiling). Which level of the octree is matched 
depends on specified number of tiles:
 - 4 (2X2) means matching with level 1 of octree
 - 16 (4x4) means matching with level 2 of octree
 and so on. 
"""

import argparse, traceback, time, os, math, multiprocessing, json
import utils

# Check the LAStools is installed and that it is in the PATH before libLAS
if utils.shellExecute('pdal split --version').count('pdal split') == 0:
    raise Exception("PDAL split is not installed. Be sure to have PDAL installed!")

def argument_parser():
    """ Define the arguments and return the parser object"""
    parser = argparse.ArgumentParser(
    description="""This script is used to distribute the points of a bunch of LAS/LAZ files in 
different tiles. The XY extent of the different tiles match the XY extent of the 
nodes of a certain level of a octree defined by the provided bounding box (z 
is not required by the XY tiling). Which level of the octree is matched 
depends on specified number of tiles:
 - 4 (2X2) means matching with level 1 of octree
 - 16 (4x4) means matching with level 2 of octree
 and so on. """)
    parser.add_argument('-i','--input',default='',help='Input data folder (with LAS/LAZ files)',type=str, required=True)
    parser.add_argument('-o','--output',default='',help='Output data folder for the different tiles',type=str, required=True)
def joinNode(node, nodeAbsPathA, nodeAbsPathB, nodeAbsPathO, hierarchyStepSize, extension, cmcommand):
    hrcFile = node + '.hrc'
    hrcA = None
    if os.path.isfile(nodeAbsPathA + '/' + hrcFile):
        # Check if there is data in this node in Octtree A (we check if the HRC file for this node exist)
        hrcA = utils.readHRC(nodeAbsPathA + '/' + hrcFile, hierarchyStepSize)

    hrcB = None
    if os.path.isfile(nodeAbsPathB + '/' + hrcFile):
        # Check if there is data in this node in Octtree B (we check if the HRC file for this node exist)
        hrcB = utils.readHRC(nodeAbsPathB + '/' + hrcFile, hierarchyStepSize)
    
    if hrcA != None and hrcB != None:
        utils.shellExecute('mkdir -p ' + nodeAbsPathO)
        # If both Octtrees A and B have data in this node we have to merge them
        hrcO = utils.initHRC(hierarchyStepSize)
        for level in range(hierarchyStepSize+2):
            numChildrenA = len(hrcA[level])
            numChildrenB = len(hrcB[level])
            numChildrenO = max((numChildrenA, numChildrenB))
            if level < (hierarchyStepSize+1):
                for i in range(numChildrenO):
                    hasNodeA = (i < numChildrenA) and (hrcA[level][i] > 0)
                    hasNodeB = (i < numChildrenB) and (hrcB[level][i] > 0)
                    (childNode, isFile) = utils.getNodeName(level, i, node, hierarchyStepSize, extension)
                    if hasNodeA and hasNodeB:
                        hrcO[level].append(hrcA[level][i] + hrcB[level][i])
                        #merge lAZ or folder (iteratively)
                        if isFile:
                            utils.shellExecute('lasmerge -i ' +  nodeAbsPathA + '/' + childNode + ' ' +  nodeAbsPathB + '/' + childNode + ' -o ' + nodeAbsPathO + '/' + childNode)
                            #We now need to set the header of the output file as the input files (lasmerge will have shrink it and we do not want that
                            fixHeader(nodeAbsPathA + '/' + childNode, nodeAbsPathO + '/' + childNode)
                        else:
                            joinNode(node + childNode, nodeAbsPathA + '/' + childNode, nodeAbsPathB + '/' + childNode, nodeAbsPathO + '/' + childNode, hierarchyStepSize, extension, cmcommand)
                    elif hasNodeA:
                        #mv / cp
                        hrcO[level].append(hrcA[level][i])
                        utils.shellExecute(cmcommand + nodeAbsPathA + '/' + childNode + ' ' + nodeAbsPathO + '/' + childNode)
                    elif hasNodeB:
                        #mv / cp
                        hrcO[level].append(hrcB[level][i])
                        utils.shellExecute(cmcommand + nodeAbsPathB + '/' + childNode + ' ' + nodeAbsPathO + '/' + childNode)
                    else:
                        hrcO[level].append(0)
            else:
                hrcO[level] = list(numpy.array(hrcA[level] + ([0]*(numChildrenO - numChildrenA))) + numpy.array(hrcB[level] + ([0]*(numChildrenO - numChildrenB))))            
        # Write the HRC file
        utils.writeHRC(nodeAbsPathO + '/' + hrcFile, hierarchyStepSize, hrcO)
    elif hrcA != None:
        # Only Octtree A has data in this node. We can directly copy it to the output Octtree
        utils.shellExecute(cmcommand + nodeAbsPathA + ' ' + nodeAbsPathO)
    elif hrcB != None:
        # Only Octtree B has data in this node. We can directly copy it to the output Octtree
        utils.shellExecute(cmcommand + nodeAbsPathB + ' ' + nodeAbsPathO)
def fixHeader(inputFile, outputFile):
    (_, minX, minY, minZ, maxX, maxY, maxZ, _, _, _, _, _, _) = utils.getPCFileDetails(inputFile)
    utils.shellExecute('lasinfo -i %s -nc -nv -nco -set_bounding_box %f %f %f %f %f %f' % (outputFile, minX, minY, minZ, maxX, maxY, maxZ))
#!/usr/bin/env python
"""Create the OctTrees of each tile of the input data folder"""

import argparse, traceback, time, os, multiprocessing
import utils

# Check that PotreeConverter with extension to specify the AABB is installed
if utils.shellExecute('PotreeConverter -h').count('--aabb') == 0:
    raise Exception("PotreeConverter with extension to specify the AABB is not installed. Be sure to have PotreeConverter installed from https://github.com/oscarmartinezrubi/PotreeConverter")

def argument_parser():
    """ Define the arguments and return the parser object"""
    parser = argparse.ArgumentParser(
    description="Create the Octtrees of each tile of the input data folder")
    parser.add_argument('-i','--input',default='',help='Input folder with the tiles',type=str, required=True)
    parser.add_argument('-o','--output',default='',help='Output folder for the Potree data',type=str, required=True)
    parser.add_argument('-f','--format',default='',help='Format (LAS or LAZ)',type=str, required=True)
    parser.add_argument('-l','--levels',default='',help='Number of levels for OctTree',type=int, required=True)
    parser.add_argument('-s','--spacing',default='',help='Spacing at root level',type=int, required=True)
    parser.add_argument('-e','--extent',default='',help='Extent to be used for all the OctTree, specify as "minX minY minZ maxX maxY maxZ"',type=str, required=True)
    parser.add_argument('-c','--proc',default=1,help='Number of processes [default is 1]',type=int)
    return parser

def runProcess(processIndex, tasksQueue, resultsQueue, outputFolder, format, levels, spacing, extent):
    kill_received = False
    while not kill_received:
        tileAbsPath = None
        try:
            # This call will patiently wait until new job is available
            tileAbsPath = tasksQueue.get()
        except: