Accelerare il campo del timestamp calcolato da Python in ArcGIS Desktop?


9

Sono nuovo di Python e ho iniziato a creare script per i flussi di lavoro ArcGIS. Mi chiedo come posso accelerare il mio codice per generare un doppio campo numerico "Ore" da un campo data / ora. Comincio con uno shapefile del log del punto traccia (breadcrumb trail) generato da DNR Garmin, con un campo timestamp LTIME (un campo di testo, lunghezza 20) per quando è stata presa ogni registrazione del trackpoint. Lo script calcola la differenza in Ore tra ogni indicatore temporale successivo ("LTIME") e lo inserisce in un nuovo campo ("Ore").

In questo modo posso tornare indietro e riassumere quanto tempo ho trascorso in una particolare area / poligono. La parte principale è dopo print "Executing getnextLTIME.py script..." Ecco il codice:

# ---------------------------------------------------------------------------
# 
# 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."

1
bel programma! Non ho visto nulla per accelerare il calcolo. Il calcolatore di campo impiega un'eternità !!
Brad Nesom,

Risposte:


12

I cursori sono sempre molto lenti nell'ambiente di geoprocessing. Il modo più semplice per aggirare questo è passare un blocco di codice Python nello strumento di geoprocessing CalculateField.

Qualcosa del genere dovrebbe funzionare:

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)

Ovviamente dovresti modificarlo per prendere campi e parametri, ma dovrebbe essere abbastanza veloce.

Nota che sebbene le tue funzioni di analisi della data / ora siano in realtà un pelo più veloci della funzione strptime (), la libreria standard è quasi sempre più priva di bug.


Grazie David. Non mi rendevo conto che CalculateField era più veloce; Proverò a provare questo. L'unico problema che penso ci possa essere è che il set di dati potrebbe essere fuori servizio. A volte, questo succede. Esiste un modo per ordinare prima Ascendente sul campo LTIME e quindi applicare CalculateField o dire a CalculateField di eseguire in un determinato ordine?
Russell,

Solo una nota, chiamare le funzioni gp preconfigurate sarà più veloce per la maggior parte del tempo. Ho spiegato perché in un post precedente gis.stackexchange.com/questions/8186/…
Ragi Yaser Burhum,

+1 per l'utilizzo del pacchetto integrato datetime , in quanto offre grandi funzionalità e quasi sostituisce i pacchetti tempo / calendario
Mike T

1
è stato incredibile! Ho provato il tuo codice e l'ho integrato con il suggerimento "in memoria" di @OptimizePrime e il tempo medio di esecuzione dello script è durato da 55 secondi a 2 secondi (810 record). Questo è esattamente il tipo di cosa che stavo cercando. Grazie mille. Ho imparato molto.
Russell,

3

@David ti ha dato una soluzione molto pulita. +1 per l'utilizzo dei punti di forza della base di codici di arcgisscripting.

Un'altra opzione è quella di copiare il set di dati in memoria usando:

  • gp.CopyFeatureclass ("percorso alla fonte", "in_memory \ nome funzione copiato") - per una classe di caratteristiche geodatabase, shapefile o,
  • gp.CopyRows ("path to your source",) - per una tabella Geodatabase, dbf ecc

Ciò rimuove l'overhead sostenuto quando si richiede un cursore dalla base di codice COM ESRI.

Il sovraccarico deriva dalla conversione dei tipi di dati Python in tipi di dati C e dall'accesso alla base di codice COM ESRI.

Quando hai i tuoi dati in memoria, stai riducendo la necessità di accedere al disco (un processo ad alto costo). Inoltre, riduci la necessità che le librerie python e C / C ++ trasferiscano i dati quando usi arcgisscripting.

Spero che sia di aiuto.


1

Un'ottima alternativa all'utilizzo di un UpdateCursor vecchio stile di arcgisscripting, disponibile da ArcGIS 10.1 per Desktop, è arcpy.da.UpdateCursor .

Ho scoperto che questi sono in genere circa 10 volte più veloci.

Questi potrebbero / potrebbero non essere stati un'opzione quando questa domanda è stata scritta, ma non dovrebbero essere trascurati da chiunque legga queste domande e risposte ora.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.