Przyspieszanie pola datownika obliczonego w Pythonie w ArcGIS Desktop?

9

Jestem nowy w Pythonie i zacząłem tworzyć skrypty dla przepływów pracy ArcGIS. Zastanawiam się, jak mogę przyspieszyć mój kod, aby wygenerować podwójne pole liczbowe „Godziny” z pola znacznika czasu. Zaczynam od pliku kształtów dziennika śladu (szlak nawigacyjny) wygenerowanego przez DNR Garmin, z polem znacznika czasu LTIME (pole tekstowe o długości 20) dla każdego rekordu śladu. Skrypt oblicza różnicę godzin między poszczególnymi znacznikami czasu („LTIME”) i umieszcza ją w nowym polu („Godziny”).

W ten sposób mogę cofnąć się i podsumować, ile czasu spędziłem w danym obszarze / wielokącie. Główna część znajduje się po print "Executing getnextLTIME.py script..." kodzie Oto kod:

# ---------------------------------------------------------------------------
# 
# 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
źródło
1
fajny program! Nie widziałem niczego, co przyspieszyłoby obliczenia. Kalkulator terenowy trwa wiecznie !!
Brad Nesom

Odpowiedzi:

12

Kursory są zawsze bardzo powolne w środowisku geoprzetwarzania. Najprościej jest przekazać blok kodu Pythona do narzędzia geoprzetwarzania CalculateField.

Coś takiego powinno działać:

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)

Oczywiście trzeba go zmodyfikować, aby przyjmował pola i parametry, ale powinien być dość szybki.

Zauważ, że chociaż twoje funkcje parsowania daty / czasu są o wiele szybsze niż funkcja strptime (), standardowa biblioteka prawie zawsze jest wolna od błędów.

David
źródło
Dzięki David. Nie zdawałem sobie sprawy, że CalculateField był szybszy; Spróbuję to przetestować. Moim zdaniem jedynym problemem może być to, że zestaw danych może być niesprawny. Czasami tak się dzieje. Czy istnieje sposób, aby najpierw posortować Rosnąco w polu LTIME, a następnie zastosować CalculateField, lub nakazać CalculateField wykonanie w określonej kolejności?
Russell
Tylko uwaga, wywoływanie wstępnie skonfigurowanych funkcji GP będzie szybsze przez większość czasu. Wyjaśniłem, dlaczego w poprzednim poście gis.stackexchange.com/questions/8186/…
Ragi Yaser Burhum
+1 za korzystanie z wbudowanego pakietu datetime , ponieważ oferuje świetną funkcjonalność i prawie zastępuje pakiety czasu / kalendarza
Mike T
1
to było niesamowite! Wypróbowałem twój kod i zintegrowałem go z sugestią „w pamięci” @OptimizePrime i zajęło to średni czas działania skryptu z 55 sekund do 2 sekund (810 rekordów). Właśnie tego szukałem. Dziękuję bardzo. Dużo się nauczyłem.
Russell
3

@David dał ci bardzo czyste rozwiązanie. +1 za użycie siły bazy kodu arcgisscripting.

Inną opcją jest skopiowanie zestawu danych do pamięci za pomocą:

  • gp.CopyFeatureclass („ścieżka do źródła”, „in_memory \ skopiowana nazwa elementu”) - dla klasy obiektów Geodatabase, pliku kształtu lub
  • gp.CopyRows („ścieżka do twojego źródła”,) - dla tabeli Geodatabase, dbf itp

Usuwa to narzut związany z żądaniem kursora z bazy kodu ESRI COM.

Narzut wynika z konwersji typów danych python na typy danych C i dostępu do bazy kodów ESRI COM.

Gdy masz dane w pamięci, zmniejszasz potrzebę dostępu do dysku (proces o wysokich kosztach). Ponadto zmniejszasz potrzebę przesyłania danych przez biblioteki Python i C / C ++ podczas korzystania z arcgisscripting.

Mam nadzieję że to pomoże.

OptimizePrime
źródło
1

Doskonałą alternatywą dla korzystania ze starego stylu UpdateCursor z arcgisscripting, który jest dostępny, jest dostępna od ArcGIS 10.1 na komputery stacjonarne, to arcpy.da.UpdateCursor .

Odkryłem, że są one zwykle około 10 razy szybsze.

Nie byłyby to opcja, kiedy pytanie zostało napisane, ale nikt nie powinien teraz pomijać tych pytań.

PolyGeo
źródło