¿Acelerando el campo de marca de tiempo calculado de Python en ArcGIS Desktop?

9

Soy nuevo en Python y he comenzado a crear scripts para flujos de trabajo de ArcGIS. Me pregunto cómo puedo acelerar mi código para generar un campo numérico doble "Horas" a partir de un campo de marca de tiempo. Comienzo con un archivo de forma de registro de puntos de seguimiento (ruta de navegación) generado por DNR Garmin, con un campo de marca de tiempo LTIME (un campo de texto, longitud 20) para cuando se tomó cada registro de punto de seguimiento. El script calcula la diferencia en Horas entre cada marca de tiempo sucesiva ("LTIME") y la coloca en un nuevo campo ("Horas").

De esa manera puedo regresar y resumir cuánto tiempo pasé en un área / polígono en particular. La parte principal es después del print "Executing getnextLTIME.py script..." Aquí está el código:

# ---------------------------------------------------------------------------
# 
# Created on: Sept 9, 2010
# Created by: The Nature Conservancy
# Calculates delta time (hours) between successive rows based on timestamp field
#
# Credit should go to Richard Crissup, ESRI DTC, Washington DC for his
# 6-27-2008 date_diff.py posted as an ArcScript
'''
    This script assumes the format "month/day/year hours:minutes:seconds".
    The hour needs to be in military time. 
    If you are using another format please alter the script accordingly. 
    I do a little checking to see if the input string is in the format
    "month/day/year hours:minutes:seconds" as this is a common date time
    format. Also the hours:minute:seconds is included, otherwise we could 
    be off by almost a day.

    I am not sure if the time functions do any conversion to GMT, 
    so if the times passed in are in another time zone than the computer
    running the script, you will need to pad the time given back in 
    seconds by the difference in time from where the computer is in relation
    to where they were collected.

'''
# ---------------------------------------------------------------------------
#       FUNCTIONS
#----------------------------------------------------------------------------        
import arcgisscripting, sys, os, re
import time, calendar, string, decimal
def func_check_format(time_string):
    if time_string.find("/") == -1:
        print "Error: time string doesn't contain any '/' expected format \
            is month/day/year hour:minutes:seconds"
    elif time_string.find(":") == -1:
        print "Error: time string doesn't contain any ':' expected format \
            is month/day/year hour:minutes:seconds"

        list = time_string.split()
        if (len(list)) <> 2:
            print "Error time string doesn't contain and date and time separated \
                by a space. Expected format is 'month/day/year hour:minutes:seconds'"


def func_parse_time(time_string):
'''
    take the time value and make it into a tuple with 9 values
    example = "2004/03/01 23:50:00". If the date values don't look like this
    then the script will fail. 
'''
    year=0;month=0;day=0;hour=0;minute=0;sec=0;
    time_string = str(time_string)
    l=time_string.split()
    if not len(l) == 2:
        gp.AddError("Error: func_parse_time, expected 2 items in list l got" + \
            str(len(l)) + "time field value = " + time_string)
        raise Exception 
    cal=l[0];cal=cal.split("/")
    if not len(cal) == 3:
        gp.AddError("Error: func_parse_time, expected 3 items in list cal got " + \
            str(len(cal)) + "time field value = " + time_string)
        raise Exception
    ti=l[1];ti=ti.split(":")
    if not len(ti) == 3:
        gp.AddError("Error: func_parse_time, expected 3 items in list ti got " + \
            str(len(ti)) + "time field value = " + time_string)
        raise Exception
    if int(len(cal[0]))== 4:
        year=int(cal[0])
        month=int(cal[1])
        day=int(cal[2])
    else:
        year=int(cal[2])
        month=int(cal[0])
        day=int(cal[1])       
    hour=int(ti[0])
    minute=int(ti[1])
    sec=int(ti[2])
    # formated tuple to match input for time functions
    result=(year,month,day,hour,minute,sec,0,0,0)
    return result


#----------------------------------------------------------------------------

def func_time_diff(start_t,end_t):
    '''
    Take the two numbers that represent seconds
    since Jan 1 1970 and return the difference of
    those two numbers in hours. There are 3600 seconds
    in an hour. 60 secs * 60 min   '''

    start_secs = calendar.timegm(start_t)
    end_secs = calendar.timegm(end_t)

    x=abs(end_secs - start_secs)
    #diff = number hours difference
    #as ((x/60)/60)
    diff = float(x)/float(3600)   
    return diff

#----------------------------------------------------------------------------

print "Executing getnextLTIME.py script..."

try:
    gp = arcgisscripting.create(9.3)

    # set parameter to what user drags in
    fcdrag = gp.GetParameterAsText(0)
    psplit = os.path.split(fcdrag)

    folder = str(psplit[0]) #containing folder
    fc = str(psplit[1]) #feature class
    fullpath = str(fcdrag)

    gp.Workspace = folder

    fldA = gp.GetParameterAsText(1) # Timestamp field
    fldDiff = gp.GetParameterAsText(2) # Hours field

    # set the toolbox for adding the field to data managment
    gp.Toolbox = "management"
    # add the user named hours field to the feature class
    gp.addfield (fc,fldDiff,"double")
    #gp.addindex(fc,fldA,"indA","NON_UNIQUE", "ASCENDING")

    desc = gp.describe(fullpath)
    updateCursor = gp.UpdateCursor(fullpath, "", desc.SpatialReference, \
        fldA+"; "+ fldDiff, fldA)
    row = updateCursor.Next()
    count = 0
    oldtime = str(row.GetValue(fldA))
    #check datetime to see if parseable
    func_check_format(oldtime)
    gp.addmessage("Calculating " + fldDiff + " field...")

    while row <> None:
        if count == 0:
            row.SetValue(fldDiff, 0)
        else:
            start_t = func_parse_time(oldtime)
            b = str(row.GetValue(fldA))
            end_t = func_parse_time(b)
            diff_hrs = func_time_diff(start_t, end_t)
            row.SetValue(fldDiff, diff_hrs)
            oldtime = b

        count += 1
        updateCursor.UpdateRow(row)
        row = updateCursor.Next()

    gp.addmessage("Updated " +str(count+1)+ " rows.")
    #gp.removeindex(fc,"indA")
    del updateCursor
    del row

except Exception, ErrDesc:
    import traceback;traceback.print_exc()

print "Script complete."
Russell
fuente
1
buen programa! No he visto nada para acelerar el cálculo. ¡La calculadora de campo lleva una eternidad!
Brad Nesom

Respuestas:

12

Los cursores siempre son muy lentos en el entorno de geoprocesamiento. La forma más fácil de evitar esto es pasar un bloque de código Python a la herramienta de geoprocesamiento CalculateField.

Algo como esto debería funcionar:

import arcgisscripting
gp = arcgisscripting.create(9.3)

# Create a code block to be executed for each row in the table
# The code block is necessary for anything over a one-liner.
codeblock = """
import datetime
class CalcDiff(object):
    # Class attributes are static, that is, only one exists for all 
    # instances, kind of like a global variable for classes.
    Last = None
    def calcDiff(self,timestring):
        # parse the time string according to our format.
        t = datetime.datetime.strptime(timestring, '%m/%d/%Y %H:%M:%S')
        # return the difference from the last date/time
        if CalcDiff.Last:
            diff =  t - CalcDiff.Last
        else:
            diff = datetime.timedelta()
        CalcDiff.Last = t
        return float(diff.seconds)/3600.0
"""

expression = """CalcDiff().calcDiff(!timelabel!)"""

gp.CalculateField_management(r'c:\workspace\test.gdb\test','timediff',expression,   "PYTHON", codeblock)

Obviamente, tendría que modificarlo para tomar campos y parámetros, pero debería ser bastante rápido.

Tenga en cuenta que aunque sus funciones de análisis de fecha / hora son realmente mucho más rápidas que la función strptime (), la biblioteca estándar casi siempre está más libre de errores.

David
fuente
Gracias david No me di cuenta de que CalculateField era más rápido; Intentaré probar esto. El único problema que creo que puede haber es que el conjunto de datos puede estar fuera de servicio. En ocasiones, esto sucede. ¿Hay alguna manera de ordenar Ascending en el campo LTIME primero y luego aplicar el CalculateField, o decirle al CalculateField que se ejecute en un orden determinado?
Russell
Solo una nota, llamar a las funciones gp pre-enlatadas será más rápido la mayor parte del tiempo. Le expliqué por qué en una publicación anterior gis.stackexchange.com/questions/8186/…
Ragi Yaser Burhum
+1 por usar el paquete integrado de fecha y hora, ya que ofrece una gran funcionalidad y casi reemplaza los paquetes de hora / calendario
Mike T
1
¡eso fue increible! Probé su código, y lo integré con la sugerencia "en memoria" de @OptimizePrime y tomó el tiempo promedio de ejecución del script de 55 segundos a 2 segundos (810 registros). Este es exactamente el tipo de cosas que estaba buscando. Muchas gracias. Aprendí mucho.
Russell
3

@David te ha dado una solución muy limpia. +1 por usar las fortalezas de la base de código arcgisscripting.

Otra opción es copiar el conjunto de datos a la memoria usando:

  • gp.CopyFeatureclass ("ruta de acceso a su fuente", "in_memory \ nombre de entidad copiado") - para una clase de entidad de geodatabase, archivo de forma o,
  • gp.CopyRows ("ruta a su fuente",) - para una tabla de Geodatabase, dbf, etc.

Esto elimina la sobrecarga incurrida cuando solicita un cursor de la base de código COM de ESRI.

La sobrecarga proviene de la conversión de los tipos de datos de python a los tipos de datos C y el acceso a la base de código COM de ESRI.

Cuando tiene sus datos en la memoria, está reduciendo la necesidad de acceder al disco (un proceso de alto costo). Además, reduce la necesidad de que las bibliotecas python y C / C ++ transfieran datos cuando usa arcgisscripting.

Espero que esto ayude.

OptimizePrime
fuente
1

Una excelente alternativa al uso de un UpdateCursor de estilo antiguo de arcgisscripting, que ha estado disponible desde ArcGIS 10.1 for Desktop, es arcpy.da.UpdateCursor .

He descubierto que estos son típicamente aproximadamente 10 veces más rápidos.

Estas podrían / ​​no haber sido una opción cuando se redactó esta pregunta, pero nadie debería leerla ahora.

PolyGeo
fuente