Acelerando o campo de carimbo de data e hora calculado do Python no ArcGIS Desktop?

9

Eu sou novo no Python e comecei a criar scripts para fluxos de trabalho do ArcGIS. Gostaria de saber como posso acelerar meu código para gerar um campo numérico duplo "Horas" a partir de um campo de carimbo de data / hora. Começo com um arquivo de shapefile de registro de trilha (trilha de trilha) gerado pelo DNR Garmin, com um campo de carimbo de data / hora LTIME (um campo de texto, comprimento 20) para quando cada registro de trilha foi obtido. O script calcula a diferença de horas entre cada carimbo de data / hora sucessivo ("LTIME") e coloca isso em um novo campo ("horas").

Dessa forma, posso voltar e resumir quanto tempo passei em uma área / polígono em particular. A parte principal é após o print "Executing getnextLTIME.py script..." Aqui está o 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
fonte
11
bom programa! Não vi nada para acelerar o cálculo. A calculadora de campo leva uma eternidade !!
Brad Nesom 27/05

Respostas:

12

O cursor é sempre muito lento no ambiente de geoprocessamento. A maneira mais fácil de contornar isso é passar um bloco de código Python para a ferramenta de geoprocessamento CalculateField.

Algo assim deve 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, você precisaria modificá-lo para obter campos e parâmetros, mas deve ser bem rápido.

Observe que, embora suas funções de análise de data / hora sejam realmente mais rápidas que a função strptime (), a biblioteca padrão é quase sempre mais livre de erros.

David
fonte
Obrigado David. Não percebi que o CalculateField era mais rápido; Vou tentar testar isso. O único problema que acho que pode haver é que o conjunto de dados esteja fora de ordem. Ocasionalmente, isso acontece. Existe uma maneira de classificar ascendente no campo LTIME primeiro e depois aplicar o CalculateField ou dizer ao CalculateField para executar em uma determinada ordem?
Russell
Apenas uma observação: chamar as funções de GP pré-enlatadas será mais rápido na maioria das vezes. Expliquei por que em um post anterior gis.stackexchange.com/questions/8186/…
Ragi Yaser Burhum
+1 para usar a data e hora built-in pacote, já que oferece grande funcionalidade e quase substitui / pacotes de calendário tempo
Mike T
11
isso foi incrível! Eu tentei o seu código e o integrei à sugestão "in memory" do @OptimizePrime e o tempo médio de execução do script foi de 55 segundos para 2 segundos (810 registros). Este é exatamente o tipo de coisa que eu estava procurando. Muito obrigado. Eu aprendi muito.
Russell
3

O @ David ofereceu uma solução muito limpa. +1 para usar os pontos fortes da base de código de scripts de arco.

Outra opção é copiar o conjunto de dados para a memória usando:

  • gp.CopyFeatureclass ("caminho para sua fonte", "in_memory \ copied feature name") - para uma classe de recursos de geodatabase, shapefile ou,
  • gp.CopyRows ("caminho para sua fonte",) - para uma tabela de geodatabase, dbf etc

Isso remove a sobrecarga incorrida quando você solicita um cursor da base de código ESRI COM.

A sobrecarga ocorre com a conversão de tipos de dados python em tipos C e o acesso à base de códigos ESRI COM.

Quando você tem seus dados na memória, reduz a necessidade de acessar o disco (um processo de alto custo). Além disso, você reduz a necessidade de as bibliotecas python e C / C ++ transferir dados, quando você usa arcgisscripting.

Espero que isto ajude.

OptimizePrime
fonte
1

Uma excelente alternativa ao uso de um UpdateCursor antigo do arcgisscripting, disponível desde o ArcGIS 10.1 for Desktop, é o arcpy.da.UpdateCursor .

Eu descobri que estes são tipicamente cerca de 10 vezes mais rápidos.

Isso poderia ou não ter sido uma opção quando esta pergunta foi escrita, mas não deve ser ignorada por qualquer pessoa que esteja lendo estas perguntas e respostas agora.

PolyGeo
fonte