Ускоряет ли Python вычисленное поле метки времени в ArcGIS Desktop?

9

Я новичок в Python и начал создавать скрипты для рабочих процессов ArcGIS. Мне интересно, как я могу ускорить мой код для создания двойного числового поля «Часы» из поля отметки времени. Я начну с шейп-файла журнала точек отслеживания («хлебная крошка»), сгенерированного DNR Garmin, с полем метки времени LTIME (текстовое поле длиной 20) для каждой записи точки отслеживания. Сценарий вычисляет разницу в часах между каждой последовательной отметкой времени («LTIME») и помещает ее в новое поле («Часы»).

Таким образом я могу вернуться и подвести итог, сколько времени я провел в определенной области / полигоне. Основная часть после print "Executing getnextLTIME.py script..." кода здесь:

# ---------------------------------------------------------------------------
# 
# 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
хорошая программа! Я не видел ничего, чтобы ускорить расчет. Полевой калькулятор занимает вечность!
Брэд Несом

Ответы:

12

Курсор всегда очень медленный в среде геообработки. Самый простой способ обойти это - передать блок кода Python в инструмент геообработки CalculateField.

Примерно так должно работать:

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)

Очевидно, вам придется изменить его, чтобы он принимал поля и такие параметры, но это должно быть довольно быстро.

Обратите внимание, что хотя ваши функции синтаксического анализа даты / времени на самом деле быстрее, чем функция strptime (), стандартная библиотека почти всегда не содержит ошибок.

Дэвид
источник
Спасибо Дэвид. Я не понимал, что CalculateField был быстрее; Я постараюсь проверить это. Я думаю, что единственная проблема может быть в том, что набор данных может быть не в порядке. В некоторых случаях это происходит. Есть ли способ сначала отсортировать по возрастанию в поле LTIME, а затем применить CalculateField или сказать, чтобы CalculateField выполнялся в определенном порядке?
Рассел
Просто обратите внимание, что вызов предварительно настроенных функций gp будет быстрее в большинстве случаев. Я объяснил, почему в предыдущем посте gis.stackexchange.com/questions/8186/…
Раги Язер Бурхум
+1 за использование встроенного пакета datetime , поскольку он предлагает отличную функциональность и почти заменяет пакеты time / calendar
Mike T,
1
это было невероятно! Я попробовал ваш код и интегрировал его с предложением @OptimizePrime «в памяти», и это заняло среднее время выполнения сценария от 55 секунд до 2 секунд (810 записей). Это именно то, что я искал. Огромное спасибо. Я многому научился.
Рассел
3

@ Дэвид дал вам очень чистое решение. +1 за использование сильных сторон базы кода arcgisscripting.

Другой вариант - скопировать набор данных в память, используя:

  • gp.CopyFeatureclass («путь к вашему источнику», «in_memory \ имя скопированного объекта») - для класса объектов базы геоданных, шейп-файла или,
  • gp.CopyRows («путь к вашему источнику»,) - для таблицы базы геоданных, dbf и т. д.

Это устраняет накладные расходы, возникающие при запросе курсора из базы кода ESRI COM.

Это связано с преобразованием типов данных Python в типы данных C и доступом к базе кода ESRI COM.

Когда ваши данные хранятся в памяти, вы уменьшаете необходимость доступа к диску (дорогостоящий процесс). Кроме того, вы уменьшаете потребность в библиотеках python и C / C ++ для передачи данных при использовании arcgisscripting.

Надеюсь это поможет.

OptimizePrime
источник
1

Отличная альтернатива использованию старого UpdateCursor стиля из arcgisscripting, который был доступен доступна с ArcGIS 10.1 для рабочего стола, является arcpy.da.UpdateCursor .

Я обнаружил, что они обычно примерно в 10 раз быстрее.

Если бы этот вопрос был написан, они могли бы / не могли быть предложены, но никто не должен их игнорировать.

PolyGeo
источник