def run(inputFolder, srid, dbName, dbPass, dbUser, dbHost, dbPort, numberProcs):
    # Make connection
    connectionString = utils.getConnectString(dbName, dbUser, dbPass, dbHost, dbPort)
    connection = psycopg2.connect(connectionString)
    cursor = connection.cursor()
    
    # Make it absolute path
    inputFolder = os.path.abspath(inputFolder)
    
    # Create table if it does not exist
    cursor.execute('CREATE TABLE ' + utils.DB_TABLE_RAW + ' (filepath text, numberpoints integer, minz double precision, maxz double precision, geom public.geometry(Geometry, %s))', [srid, ])
    connection.commit()
    connection.close()
    
    # Create queues for the distributed processing
    tasksQueue = multiprocessing.Queue() # The queue of tasks (inputFiles)
    resultsQueue = multiprocessing.Queue() # The queue of results
    
    inputFiles = utils.getFiles(inputFolder, recursive=True)
    numFiles = len(inputFiles)
    
    # Add tasks/inputFiles
    for i in range(numFiles):
        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, connectionString, srid)))
        processes[-1].start()

    # Get all the results (actually we do not need the returned values)
    for i in range(numFiles):
        resultsQueue.get()
        print 'Completed %d of %d (%.02f%%)' % (i+1, numFiles, 100. * float(i+1) / float(numFiles))
    # wait for all users to finish their execution
    for i in range(numberProcs):
        processes[i].join()
        
    # Create an index for the geometries
    connection = psycopg2.connect(connectionString)
    cursor = connection.cursor()
    cursor.execute('CREATE INDEX ' + utils.DB_TABLE_RAW + '_geom ON '  + utils.DB_TABLE_RAW + ' USING GIST ( geom )')
    connection.commit()
    connection.close()
def run(srid, userMail, level, bBox, dbName, dbPass, dbUser, dbHost, dbPort, baseURL, basePath):
    message = ''
    statusOk = True
    outputAbsPath = None
    timeStamp = datetime.datetime.now().strftime("%H_%M_%S_%f")

    try:
        # Get the extent of the bounding boxes anc check they are float values
        (minX,minY,maxX,maxY) = bBox.replace('"','').replace("'","").split(' ')
        for v in (minX,minY,maxX,maxY):
            float(v)
        
        # Make connection
        connectionString = utils.getConnectString(dbName, dbUser, dbPass, dbHost, dbPort)
        connection = psycopg2.connect(connectionString)
        cursor = connection.cursor()
        
        cursor.execute('SELECT max(level) FROM ' + utils.DB_TABLE_POTREE)
        maxLevelPotree = cursor.fetchone()[0]

#        print level,maxLevelPotree
        if level != '':
            if int(level) <= maxLevelPotree:
                dbTable = utils.DB_TABLE_POTREE
            else:
                dbTable = utils.DB_TABLE_RAW
                print 'Specified level (' + level + ') is not available in the potree data. Using raw data'
        else:
            dbTable = utils.DB_TABLE_RAW
            
        estimatedNumPoints = None
        if dbTable == utils.DB_TABLE_POTREE:
            cursor.execute("""SELECT 
   floor(sum(numberpoints * (st_area(st_intersection(geom, qgeom)) / st_area(geom)))) 
FROM """ + utils.DB_TABLE_RAW + """, (SELECT ST_SetSRID(ST_MakeBox2D(ST_Point(""" + minX + """, """ + minY + """),ST_Point(""" + maxX + """, """ + maxY + """)), """ + str(srid) + """) as qgeom) AS B 
WHERE geom && qgeom AND st_area(geom) != 0""")
            estimatedNumPoints = cursor.fetchone()[0] 
        
        connection.close()
        
        outputFileName = '%s_%s_%s_%s_%s.laz' % (timeStamp,minX,minY,maxX,maxY)
        outputAbsPath = basePath + '/' + outputFileName
        
        if os.path.isfile(outputAbsPath):
            raise Exception('The file already existed!')
        else:
            query = 'SELECT filepath FROM ' + dbTable + ' where ST_SetSRID(ST_MakeBox2D(ST_Point(' + minX + ', ' + minY + '),ST_Point(' + maxX + ', ' + maxY + ')), ' + str(srid) + ') && geom'
            if dbTable == utils.DB_TABLE_POTREE:
                query += ' AND level = ' + str(level)
            
            inputList = outputAbsPath + '.list'
            connectionStringCommandLine = utils.getConnectString(dbName, dbUser, dbPass, dbHost, dbPort, cline = True)
            precommand = 'psql ' + connectionStringCommandLine + ' -t -A -c "' + query + '" > ' + inputList
            print precommand
            os.system(precommand)
            
            command = 'lasmerge -lof ' + inputList + ' -inside ' + minX + ' ' + minY + ' ' + maxX + ' ' + maxY + ' -merged -o ' + outputAbsPath
            print command
            os.system(command)
    except:
        statusOk = False
        message = 'There was some error in the file generation: ' + traceback.format_exc()
    
    
    if outputAbsPath != None and os.path.isfile(outputAbsPath) and statusOk:
        (count, _, _, _, _, _, _, _, _, _, _, _, _) = utils.getPCFileDetails(outputAbsPath)
        size = utils.getFileSize(outputAbsPath)
        
        approxStr = ''
        if dbTable == utils.DB_TABLE_POTREE:
            approxStr = """
Note that due to the large extent of your selected area only the """ + '%.4f' % (float(count)/float(estimatedNumPoints)) + """ %% of the points are stored.
"""
        
        content = """Subject: Data is ready

Your selected data is ready. """ + str(count) + """ points were selected and stored in """ + outputAbsPath.replace(basePath, baseURL) + """ with a size of """ + str(size) + """ MB.
""" + approxStr + """
Please download your data asap. This data will be deleted after 24 hours.

To visualize LAZ data there are a few alternatives. 

For desktop-based simple visualization you can use LAStools lasview.    

For web-based visualization you can use http://plas.io/

"""
    
    else:
        content = """Subject: Data is NOT ready

Your selection could not be stored. Sorry for the inconveniences."""

    content += message

    mailFileAbsPath = timeStamp + '_error.mail'
    mailFile = open(mailFileAbsPath, 'w')
    mailFile.write(content)
    mailFile.close()
    
    os.system('sendmail ' + userMail + ' < ' + mailFileAbsPath)