Thursday, June 22, 2017

Python - Create Forecast Video and PDF

After generating water depth and water surface elevation layers from a processed DEM up to 20' feet @ 0.5' interval. This script converts flood layers into a Video animation and PDF image of each of the flood layers.

'''----------------------------------------------------------------------------
 Tool Name:   Create Forecast Video and PDF
 Source Name: createforecastvideoandpdf.py
 Version:     ArcGIS 10.3.1
 Author:      Prashant Kuranjekar
----------------------------------------------------------------------------'''
import os
import sys
import arcpy
import inspect
import apwrutils
import njtconfig
import datetime
import time
import dateutil
from dateutil import parser


def trace():
    import traceback, inspect
    tb = sys.exc_info()[2]
    tbinfo = traceback.format_tb(tb)[0]
    # script name + line number
    line = tbinfo.split(", ")[1]
    filename = inspect.getfile(inspect.currentframe())
    # Get Python syntax error
    synerror = traceback.format_exc().splitlines()[-1]
    return line, filename, synerror

class CreateForecastVideoandPDF(object):
 
    def __init__(self):
        self.bCallFromPYT = True
        """Define the tool (tool name is the name of the class)."""
        self.label = 'Forecast Processing'
        self.category=''
        self.description = 'Performs forecast processing.'
        self.canRunInBackground = True

    def getParameterInfo(self):
        """Define parameter definitions"""
        return

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        if arcpy.CheckExtension("Spatial") == "Available":
            return True
        else:
            return False

    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parmater
        has been changed."""
        return

    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""
        return

    def execute(self, parameters, messages):
        """The source code of the tool."""
        debug=0 # temporary fc are deleted if debug = 0
        sOK = njtconfig.C_OK
        ds = time.clock()
        dds = time.clock()
        listtodel=[]
        starttime=None
        endtime=None
        processstatus=njtconfig.processStatus.Success
        maxforecasthour=999
        mxd=None
        bcreatevideo = True

        try:
            in_mxdpath = parameters[0]      
            in_workspace = parameters[1]
            in_facilitynameprefix = parameters[2]
            in_modelname = parameters[3]
            in_forecasttime = parameters[4]
            in_outputfolder = parameters[5]

            if len(parameters)>6:
                increatevideo = parameters[6]
            if len(parameters)>7:
                inframeduration = parameters[7]

            #Parse optional increatevideo
            screatevideo = str(increatevideo)
            if screatevideo.lower()=="false":
                bcreatevideo=False
            #Parse optional inframeduration
            if bcreatevideo:
                try:
                    dframeduration = float(inframeduration)
                except:
                    arcpy.AddMessage("Using default frame duration set to 2 s.")
                    dframeduration = 2

            #Template mxd location
            #mxdpath=r"C:\Project\Trunk\NJTTSMgr\mxd\Animation_NOAA150\Animation_NOAA150.mxd"
            mxd = arcpy.mapping.MapDocument(in_mxdpath)

            arcpy.env.workspace = in_outputfolder

            #Point to database
            if in_workspace.lower().endswith(".gdb"):
                whereclause = njtconfig.FN_MODELNAME + "='" + in_modelname + "' AND " + njtconfig.FN_FORECASTTIME + "=date'" + in_forecasttime + "'"
            elif in_workspace.lower().endswith(".sde"):
                whereclause = njtconfig.FN_MODELNAME + "='" + in_modelname + "' AND " + njtconfig.FN_FORECASTTIME + "='" + in_forecasttime + "'"
            else:
                 arcpy.AddError("Input workspace must be a filegeodatabase or a remote geodatabase.")
                 processstatus=processstatus=njtconfig.processStatus.InvalidWorkspace
                 return(njtconfig.C_NOTOK,processstatus)

            arcpy.AddMessage("Where clause: " + whereclause)

            videolayers=dict()
            videolayers.setdefault("Assets",njtconfig.LN_ASSETSFORECAST)
            videolayers.setdefault("Site Boundary",njtconfig.LN_SITEBOUNDARY)
            videolayers.setdefault("Flood Depth Contour",njtconfig.LN_FORECASTFLOODEXTENT)


            #Repoint mxd to data of interest
            #assets into baseassets
            #siteboundary into site boundary
            #floodbase contour into floodextent
                     

            for lyr in arcpy.mapping.ListLayers(mxd):
                if lyr.supports("DATASOURCE"):
                    if lyr.name in videolayers:
                        lyr.replaceDataSource(in_workspace,"SDE_WORKSPACE",videolayers[lyr.name],False)
                        if arcpy.Exists(lyr.dataSource)==False:
                            arcpy.AddError("Missing input data " + str(videolayers[lyr.name]) + ".")
                            processstatus=njtconfig.processStatus.MissingInput
                            return(njtconfig.C_NOTOK,processstatus)
         
                 
            #Apply querydef
            for lyr in arcpy.mapping.ListLayers(mxd):
                if lyr.isFeatureLayer and lyr.name in videolayers:
                    #arcpy.AddMessage("str(lyr.name)) #+ " " + str(len(arcpy.ListFields(lyr,njtconfig.FN_FORECASTTIME))) + " " + str(len(arcpy.ListFiles(njtconfig.FN_MODELNAME)))
                    if len(arcpy.ListFields(lyr.dataSource,njtconfig.FN_FORECASTTIME))==1 and len(arcpy.ListFields(lyr.dataSource,njtconfig.FN_MODELNAME))==1:
                        lyr.definitionQuery=whereclause
                        arcpy.AddMessage(lyr.name + ": count " + str((arcpy.management.GetCount(lyr)[0])))
                        if lyr.time.isTimeEnabled:
                            timefield = lyr.time.startTimeField                        
                            #arcpy.AddMessage(timefield)
                            if len(arcpy.ListFields(lyr.dataSource,timefield))==0:
                                arcpy.AddError("Missing input time field " +  timefield + " in " + lyr.dataSource + ".")
                                processstatus=njtconfig.processStatus.MissingInput
                                #return(njtconfig.C_NOTOK,processstatus)
                            else:
                                if lyr.name=='Assets':
                                    #arcpy.AddMessage("get time")
                                    #Get start and end time
                                    with arcpy.da.SearchCursor(lyr,timefield,sql_clause=(None, 'ORDER BY ' + timefield)) as cursor:
                                        for row in cursor:
                                            starttime=row[0]
                                            #arcpy.AddMessage(str(starttime))
                                            break
                                    with arcpy.da.SearchCursor(lyr,timefield,sql_clause=(None, 'ORDER BY ' + timefield + ' DESC')) as cursor:
                                        for row in cursor:
                                            endtime=row[0]
                                            #arcpy.AddMessage(str(endtime))
                                            break
                                #else:
                                #   arcpy.AddMessage(lyr.name)

         
            if starttime is None or endtime is None:
                arcpy.AddError("There are no time enabled features matching the input forecast and model name.")
                processstatus=njtconfig.processStatus.MissingInput
                return(njtconfig.C_NOTOK,processstatus)

            #Look for Forecasttimeod
            #Look for input layers
            #arcpy.AddMessage("Retrieving input layers and tables from input workspace...")
            arcpy.env.workspace = in_workspace
            #Look for feature dataset
            datasets = arcpy.ListDatasets()
            if datasets and len(datasets)>1:
                datasets = arcpy.ListDatasets('*' + njtconfig.C_FEATUREDATASET)
         
            featuredataset=""
            if datasets:
                for dataset in datasets:
                    #arcpy.AddMessage("dataset " + str(dataset))
                    featuredataset=dataset
         
            inputlayer = njtconfig.LN_FORECASTTIMEOD
            bhasmissinginputs=False

            if arcpy.ListFeatureClasses('*.' + inputlayer,'',featuredataset):
                in_forecasttimeod = arcpy.Describe(arcpy.ListFeatureClasses('*.' + inputlayer,'',featuredataset)[0]).catalogPath
            elif arcpy.ListFeatureClasses(inputlayer,'',featuredataset):
                in_forecasttimeod = inputlayer,arcpy.Describe(arcpy.ListFeatureClasses(inputlayer,'',featuredataset)[0]).catalogPath
            else:
                arcpy.AddError("Required feature class " + inputlayer + " does not exist in input geodatabase " + in_workspace + ".")
                bhasmissinginputs=True

            if bhasmissinginputs:
                    processstatus=njtconfig.processStatus.MissingInput
                    return(njtconfig.C_NOTOK,processstatus)
         
            #Retrieve text for forecast layout
            with arcpy.da.SearchCursor(in_forecasttimeod,[njtconfig.FN_MAXWATERLEVEL,njtconfig.FN_FORECASTHOURMAX,njtconfig.FN_MAXMAP,njtconfig.FN_MAXPCTAFFECTEDI,njtconfig.FN_STARTFORTIME,njtconfig.FN_ENDFORTIME],whereclause) as cursor:
                for row in cursor:
                    maxforecasthour = row[1]
                    maxwaterlevel = row[0]
                    pdffilename = row[2]
                    maxpctaffectedi=row[3]
                    starthour = row[4]
                    endhour=row[5]

         
            #Update map element
            elements = arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "ModelName")
            if len(elements)>0:
                elm=elements[0]
                elm.text=in_modelname
                #arcpy.AddMessage(elm.text)
         
            tmpfilename="HOBO_NOAA-150_102912_1200_13.pdf"
            tmpforecasttime="2012/10/29 12:00 p.m."
            tmpmodelname="NOAA-150"
            tmphourstomaxwaterlevel = "13"
            tmpmaxwaterlevel = "8 Feet (NAVD88)"
            tmppercentaffected = "65%"
            elements = arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "MainText")
            if len(elements)>0:
                mainelm=elements[0]
                maintext = mainelm.text
                maintext = maintext.replace(tmpfilename,pdffilename)
                maintext = maintext.replace(tmpforecasttime,in_forecasttime)
                maintext = maintext.replace(tmpmodelname,in_modelname)
                maintext = maintext.replace(tmphourstomaxwaterlevel,str(int(maxforecasthour))) #-int(starthour)))
                if maxwaterlevel<=1:
                    maintext = maintext.replace(tmpmaxwaterlevel,str(maxwaterlevel) + " Foot (NAVD88)")
                else:
                    maintext = maintext.replace(tmpmaxwaterlevel,str(maxwaterlevel) + " Feet (NAVD88)")
                maintext = maintext.replace(tmppercentaffected,str(maxpctaffectedi) + "%")

                mainelm.text = maintext
                arcpy.AddMessage(mainelm.text)
         

            #Loop through selected features for the forecast
            df = arcpy.mapping.ListDataFrames(mxd, "Layers")[0]
            arcpy.AddMessage("Time interval " + df.time.timeStepInterval )
            arcpy.AddMessage("Start time " + str(starttime))
            arcpy.AddMessage("End time " + str(endtime))
            df.time.resetTimeExtent()
            df.time.timeWindowUnits="HOURS"
            df.time.startTime = starttime
            df.time.endTime = endtime
            df.time.currentTime = starttime
         
            #arcpy.AddMessage("df.time.startTime " + str(df.time.startTime))
            #arcpy.AddMessage("df.time.endTime " + str(df.time.endTime))
            #arcpy.AddMessage("df.time.currentime " + str(df.time.currentTime))

            arcpy.env.workspace = in_outputfolder
            i=starthour
            basefilename=in_facilitynameprefix + "_" + in_modelname + "_" + str(parser.parse(in_forecasttime).strftime("%m%d%y_%H%M"))
            if bcreatevideo:
                arcpy.AddMessage("Exporting images...")
            while df.time.currentTime <= endtime:
                arcpy.AddMessage("Exporting image " + str(i) + " " + str(parser.parse(str(df.time.currentTime)).strftime("%m%d%y %H%M")) + " " + in_modelname + "_image" + str(i) + ".jpg")
                fileName = os.path.join(in_outputfolder,in_modelname + "_image" + str(i) + ".jpg" )
                #arcpy.AddMessage(fileName)
                arcpy.mapping.ExportToJPEG(mxd, fileName, "PAGE_LAYOUT")
                #Create pdf for maximum forecast hour
                if i == maxforecasthour:
                    arcpy.AddMessage("Generating pdf file " + pdffilename + " at maximum forecast hour " + str(maxforecasthour) + "...")
                    arcpy.mapping.ExportToPDF(mxd, os.path.join(in_outputfolder,pdffilename), "PAGE_LAYOUT")
                df.time.currentTime = (df.time.currentTime + df.time.timeStepInterval)
                i=i+1

            arcpy.AddMessage(str(i) + " images exported using prefix " + in_modelname + "_image.")

            #Convert to video
            if bcreatevideo:
                videoduration = i * float(dframeduration) #2
                arcpy.AddMessage("Generating video (duration: " + str(videoduration) + "s) ...")
                avifilename = basefilename + ".avi"
                arcpy.RasterToVideo_conversion (in_outputfolder, avifilename,'JPG', 'Microsoft Video 1', 'TIME', videoduration, '75')

                arcpy.AddMessage(avifilename + " succesfully created: dt= " + str(apwrutils.Utils.GetDSMsg(dds,'%.1fs.')) + " dtTotal=" + str(apwrutils.Utils.GetDSMsg(ds,'%ds.')))

        except arcpy.ExecuteError:
            arcpy.AddWarning(str(arcpy.GetMessages(2)))
     
        except:
            arcpy.AddWarning(trace())
            sOK = apwrutils.C_NOTOK
            processstatus=njtconfig.processStatus.Error
         
        finally:

             #Cleanup
            #for jpg in arcpy.ListFiles("*.jpg"):
            #    arcpy.Delete_management(jpg)
            arcpy.AddMessage("Cleaning up...")
            if mxd:
                del mxd
               #Reset environments
            arcpy.ResetEnvironments()
            print ("Process status: " + str(processstatus))
        return (sOK,processstatus)

if(__name__=='__main__'):

    try:
        inMapTemplate = arcpy.GetParameterAsText(0)
        inWorkspace = arcpy.GetParameterAsText(1) #Input SQL Server Workspace
        inFacilityNamePrefix = arcpy.GetParameterAsText(2)
        inModelName = arcpy.GetParameterAsText(3)
        inForecastTime = arcpy.GetParameterAsText(4)
        inOutputFolder = arcpy.GetParameterAsText(5)
        inCreateVideo = arcpy.GetParameterAsText(6)
        inFrameDuration = arcpy.GetParameterAsText(7)

        oProcessor = CreateForecastVideoandPDF()
        params = (inMapTemplate,inWorkspace, inFacilityNamePrefix ,inModelName,inForecastTime,inOutputFolder,inCreateVideo,inFrameDuration)
           
        tResults=None
        tResults = oProcessor.execute(params, None)

        if(tResults!=None):
            processstatus = tResults[1]
            #Set output parameter
            arcpy.SetParameterAsText(8,processstatus)

        del oProcessor

    except arcpy.ExecuteError:
        print (str(arcpy.GetMessages(2)))
        arcpy.AddError(str(arcpy.GetMessages(2)))
    except:
        print (trace())
        arcpy.AddError(str(trace()))
        arcpy.AddError(str(arcpy.GetMessages(2)))
    finally:
        dt = datetime.datetime.now()
        print  ('Finished at ' + dt.strftime("%Y-%m-%d %H:%M:%S"))

No comments:

Post a Comment