Beschleunigen Sie das von Python berechnete Zeitstempelfeld in ArcGIS Desktop?


9

Ich bin neu in Python und habe begonnen, Skripts für ArcGIS-Workflows zu erstellen. Ich frage mich, wie ich meinen Code beschleunigen kann, um aus einem Zeitstempelfeld ein doppelt numerisches Feld "Stunden" zu generieren. Ich beginne mit einem von DNR Garmin generierten Shapefile für das Trackpoint-Protokoll (Breadcrumb Trail) mit einem LTIME-Zeitstempelfeld (ein Textfeld, Länge 20) für den Zeitpunkt, zu dem jeder Trackpoint-Datensatz erstellt wurde. Das Skript berechnet die Stundendifferenz zwischen den einzelnen aufeinanderfolgenden Zeitstempeln ("LTIME") und fügt diese in ein neues Feld ("Stunden") ein.

Auf diese Weise kann ich zurückgehen und zusammenfassen, wie viel Zeit ich in einem bestimmten Bereich / Polygon verbracht habe. Der Hauptteil ist nach dem print "Executing getnextLTIME.py script..." Hier ist der 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."

1
schönes Programm! Ich habe nichts gesehen, um die Berechnung zu beschleunigen. Der Feldrechner dauert ewig !!
Brad Nesom

Antworten:


12

Cursor sind in der Geoverarbeitungsumgebung immer sehr langsam. Der einfachste Weg, dies zu umgehen, besteht darin, einen Python-Codeblock an das Geoverarbeitungswerkzeug CalculateField zu übergeben.

So etwas sollte funktionieren:

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)

Natürlich müssten Sie es ändern, um Felder und Parameter zu übernehmen, aber es sollte ziemlich schnell sein.

Beachten Sie, dass die Standardbibliothek fast immer fehlerfreier ist, obwohl Ihre Datums- / Zeitanalysefunktionen tatsächlich ein Haar schneller sind als die Funktion strptime ().


Danke David. Mir war nicht klar, dass das CalculateField schneller war. Ich werde versuchen, dies zu testen. Das einzige Problem, von dem ich denke, dass es sein kann, ist, dass der Datensatz möglicherweise nicht in Ordnung ist. Gelegentlich passiert dies. Gibt es eine Möglichkeit, zuerst aufsteigend im LTIME-Feld zu sortieren und dann das CalculateField anzuwenden oder das CalculateField anzuweisen, in einer bestimmten Reihenfolge ausgeführt zu werden?
Russell

Nur ein Hinweis: Das Aufrufen der vordefinierten GP-Funktionen ist die meiste Zeit schneller. Ich erklärte in einem früheren Beitrag gis.stackexchange.com/questions/8186/…
Ragi Yaser Burhum

+1 für die Verwendung des in datetime integrierten Pakets, da es großartige Funktionen bietet und Zeit- / Kalenderpakete fast ersetzt
Mike T

1
das war unglaublich! Ich habe Ihren Code ausprobiert und ihn in den Vorschlag "In Memory" von @OptimizePrime integriert. Die durchschnittliche Laufzeit des Skripts betrug 55 Sekunden bis 2 Sekunden (810 Datensätze). Genau das habe ich gesucht. Ich danke dir sehr. Ich habe viel gelernt.
Russell

3

@ David hat Ihnen eine sehr saubere Lösung gegeben. +1 für die Verwendung der Stärken der Arcgisscripting-Codebasis.

Eine weitere Option ist das Kopieren des Datensatzes in den Speicher mit:

  • gp.CopyFeatureclass ("Pfad zu Ihrer Quelle", "in_memory \ kopierter Feature-Name") - für eine Geodatabase-Feature-Class, ein Shapefile oder,
  • gp.CopyRows ("Pfad zu Ihrer Quelle") - für eine Geodatabase-Tabelle, eine Datenbank usw.

Dadurch wird der Overhead beseitigt, der entsteht, wenn Sie einen Cursor von der ESRI COM-Codebasis anfordern.

Der Overhead entsteht durch die Konvertierung von Python-Datentypen in C-Datentypen und den Zugriff auf die ESRI COM-Codebasis.

Wenn Sie Ihre Daten im Speicher haben, müssen Sie weniger auf die Festplatte zugreifen (ein kostenintensiver Prozess). Außerdem reduzieren Sie die Notwendigkeit, dass die Python- und C / C ++ - Bibliotheken Daten übertragen, wenn Sie arcgisscripting verwenden.

Hoffe das hilft.


1

Eine hervorragende Alternative zur Verwendung eines alten UpdateCursors aus arcgisscripting, der seit ArcGIS 10.1 für Desktop verfügbar ist, ist arcpy.da.UpdateCursor .

Ich habe festgestellt, dass diese normalerweise etwa zehnmal schneller sind.

Dies wäre / könnte keine Option gewesen sein, als diese Frage geschrieben wurde, sollte aber von niemandem übersehen werden, der diese Fragen und Antworten jetzt liest.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.