ArcGIS DesktopでPython計算タイムスタンプフィールドを高速化しますか?


9

Pythonは初めてで、ArcGISワークフロー用のスクリプトの作成を開始しました。タイムスタンプフィールドから "Hours"ダブル数値フィールドを生成するためにコードをどのように高速化できるのかと思っています。まず、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
素敵なプログラム!計算を高速化するものは何も見ていません。フィールド計算機は永遠にかかります!!
Brad Nesom、2011年

回答:


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/…で
Ragi Yaser Burhum

優れた機能を提供し、時刻/カレンダーパッケージをほぼ置き換えるため、datetime組み込みパッケージを使用するための+1
Mike T

1
それはすごかった!私はあなたのコードを試して、@ OptimizePrimeの「メモリ内」提案と統合しました。スクリプトの平均実行時間は55秒から2秒(810レコード)でした。これはまさに私が探していたものです。どうもありがとうございます。私は多くのことを学びました。
ラッセル

3

@Davidは非常にクリーンなソリューションを提供してくれました。+1は、arcgisscriptingコードベースの長所を使用します。

別のオプションは、次を使用してデータセットをメモリにコピーすることです:

  • gp.CopyFeatureclass( "ソースへのパス"、 "in_memory \ copiedフィーチャ名")-ジオデータベースフィーチャクラス、シェープファイル、または
  • gp.CopyRows( "path to your source"、)-ジオデータベーステーブル、dbfなど

これにより、ESRI COMコードベースからカーソルを要求したときに発生するオーバーヘッドがなくなります。

オーバーヘッドは、Pythonデータ型からCデータ型への変換と、ESRI COMコードベースへのアクセスから生じます。

メモリにデータがある場合、ディスクにアクセスする必要性が減ります(高コストのプロセス)。また、arcgisscriptingを使用すると、pythonおよびC / C ++ライブラリがデータを転送する必要性が減少します。

お役に立てれば。


1

ArcGIS 10.1 for Desktop以降で利用可能となっている、arcgisscriptingからの古いスタイルのUpdateCursorを使用する優れた代替手段は、arcpy.da.UpdateCursorです。

これらは通常、約10倍速いことがわかりました。

これらは、この質問が書かれたときにはオプションであったかもしれませんが、このQ&Aを読んでいる人は見落としてはいけません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.