Mempercepat bidang cap waktu terhitung Python di ArcGIS Desktop?

9

Saya baru mengenal Python dan sudah mulai membuat skrip untuk alur kerja ArcGIS. Saya bertanya-tanya bagaimana saya dapat mempercepat kode saya untuk menghasilkan bidang angka ganda "Jam" dari bidang cap waktu. Saya mulai dengan shapefile log titik trek (jejak roti) yang dihasilkan oleh DNR Garmin, dengan bidang cap waktu LTIME (bidang teks, panjang 20) ketika setiap catatan trackpoint diambil. Script menghitung perbedaan dalam Jam antara setiap cap waktu berturut-turut ("LTIME"), dan menempatkannya ke bidang baru ("Jam").

Dengan begitu saya bisa kembali dan merangkum berapa banyak waktu yang saya habiskan di area / poligon tertentu. Bagian utamanya adalah setelah print "Executing getnextLTIME.py script..." Here is the code:

# ---------------------------------------------------------------------------
# 
# 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
sumber
1
program yang bagus! Saya belum melihat apa pun untuk mempercepat perhitungan. Kalkulator bidang berlangsung selamanya !!
Brad Nesom

Jawaban:

12

Kursor selalu sangat lambat di lingkungan geoproses. Cara termudah untuk mengatasinya adalah dengan melewatkan blok kode Python ke dalam alat geoproses CalculateField.

Sesuatu seperti ini seharusnya bekerja:

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)

Jelas Anda harus memodifikasinya untuk mengambil bidang dan seperti parameter, tetapi harus cukup cepat.

Perhatikan bahwa meskipun fungsi parsing tanggal / waktu Anda sebenarnya lebih cepat daripada fungsi strptime (), pustaka standar hampir selalu lebih bebas bug.

David
sumber
Terima kasih David. Saya tidak menyadari bahwa CalculateField lebih cepat; Saya akan mencoba menguji ini. Satu-satunya masalah yang saya pikir mungkin ada adalah bahwa dataset mungkin rusak. Kadang-kadang, ini terjadi. Apakah ada cara untuk Mengurutkan Naik pada bidang LTIME pertama dan kemudian menerapkan CalculateField, atau untuk memberitahu CalculateField untuk mengeksekusi dalam urutan tertentu?
Russell
Sekedar catatan, memanggil fungsi gp pra-kalengan akan lebih cepat sebagian besar waktu. Saya menjelaskan mengapa dalam posting sebelumnya gis.stackexchange.com/questions/8186/...
Ragi Yaser Burhum
+1 untuk menggunakan paket bawaan datetime , karena ia menawarkan fungsionalitas luar biasa dan hampir menggantikan paket waktu / kalender
Mike T
1
itu luar biasa! Saya mencoba kode Anda, dan mengintegrasikannya dengan saran "dalam memori" @OptimizePrime dan butuh waktu rata-rata skrip berjalan dari 55 detik hingga 2 detik (810 catatan). Ini persis seperti hal yang saya cari. Terima kasih banyak. Aku belajar banyak.
Russell
3

@ David telah memberi Anda solusi yang sangat bersih. +1 untuk menggunakan kekuatan basis kode arcgisscripting.

Pilihan lain, adalah menyalin dataset ke memori menggunakan:

  • gp.CopyFeatureclass ("path ke sumber Anda", "in_memory \ nama fitur yang disalin") - untuk Kelas Fitur Geodatabase, shapefile atau,
  • gp.CopyRows ("path to your source",) - untuk tabel Geodatabase, dbf dll

Ini menghapus overhead yang terjadi ketika Anda meminta kursor dari basis kode ESRI COM.

Overhead muncul dari konversi tipe data python ke tipe data C dan akses ke basis kode ESRI COM.

Ketika Anda memiliki data di memori, Anda mengurangi kebutuhan untuk mengakses disk (proses biaya tinggi). Selain itu, Anda mengurangi kebutuhan pustaka python dan C / C ++ untuk mentransfer data, saat Anda menggunakan arcgisscripting.

Semoga ini membantu.

OptimizePrime
sumber
1

Alternatif yang sangat baik untuk menggunakan UpdateCursor gaya lama dari arcgisscripting, yang telah tersedia sejak ArcGIS 10.1 untuk Desktop, adalah arcpy.da.UpdateCursor .

Saya telah menemukan bahwa ini biasanya sekitar 10 kali lebih cepat.

Ini akan / mungkin tidak menjadi pilihan ketika pertanyaan ini ditulis tetapi tidak boleh diabaikan oleh siapa pun yang membaca T&J ini sekarang.

PolyGeo
sumber