Accélérer le champ d'horodatage calculé Python dans ArcGIS Desktop?

9

Je suis nouveau sur Python et j'ai commencé à créer des scripts pour les workflows ArcGIS. Je me demande comment accélérer mon code pour générer un double champ numérique "Heures" à partir d'un champ d'horodatage. Je commence par un fichier de forme de journal de point de suivi (fil d'Ariane) généré par DNR Garmin, avec un champ d'horodatage LTIME (un champ de texte, longueur 20) pour chaque enregistrement de point de suivi. Le script calcule la différence en heures entre chaque horodatage successif ("LTIME") et le place dans un nouveau champ ("Heures").

De cette façon, je peux revenir en arrière et résumer le temps que j'ai passé dans une zone / un polygone particulier. La partie principale est après le print "Executing getnextLTIME.py script..." Voici le 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
la source
1
joli programme! Je n'ai rien vu pour accélérer le calcul. La calculatrice de terrain prend une éternité !!
Brad Nesom

Réponses:

12

Les curseurs sont toujours très lents dans l'environnement de géotraitement. La méthode la plus simple consiste à passer un bloc de code Python dans l'outil de géotraitement CalculateField.

Quelque chose comme ça devrait fonctionner:

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)

Évidemment, vous devrez le modifier pour prendre des champs et des paramètres, mais cela devrait être assez rapide.

Notez que bien que vos fonctions d'analyse de date / heure soient en fait plus rapides que la fonction strptime (), la bibliothèque standard est presque toujours plus exempte de bogues.

David
la source
Merci David. Je ne savais pas que le CalculateField était plus rapide; Je vais essayer de tester cela. Le seul problème que je pense qu'il peut y avoir est que l'ensemble de données peut être en panne. À l'occasion, cela se produit. Existe-t-il un moyen de trier par ordre croissant dans le champ LTIME, puis d'appliquer le CalculateField, ou d'indiquer au CalculateField de s'exécuter dans un certain ordre?
Russell
Juste une note, appeler les fonctions gp pré-conservées sera plus rapide la plupart du temps. J'ai expliqué pourquoi dans un post précédent gis.stackexchange.com/questions/8186/…
Ragi Yaser Burhum
+1 pour l'utilisation du package intégré datetime , car il offre de grandes fonctionnalités et remplace presque les packages heure / calendrier
Mike T
1
c'était incroyable! J'ai essayé votre code et je l'ai intégré à la suggestion "en mémoire" de @OptimizePrime. Le temps d'exécution moyen du script est passé de 55 secondes à 2 secondes (810 enregistrements). C'est exactement le genre de chose que je cherchais. Merci beaucoup. J'ai beaucoup appris.
Russell
3

@David vous a donné une solution très propre. +1 pour utiliser les forces de la base de code d'arggisscripting.

Une autre option consiste à copier l'ensemble de données dans la mémoire en utilisant:

  • gp.CopyFeatureclass ("chemin d'accès à votre source", "in_memory \ copied feature name") - pour une classe d'entités de géodatabase, un fichier de formes ou,
  • gp.CopyRows ("chemin vers votre source",) - pour une table de géodatabase, dbf, etc.

Cela supprime la surcharge générée lorsque vous demandez un curseur à la base de code ESRI COM.

Le surcoût provient de la conversion des types de données python en types de données C et de l'accès à la base de code ESRI COM.

Lorsque vous avez vos données en mémoire, vous réduisez le besoin d'accéder au disque (un processus coûteux). En outre, vous réduisez la nécessité pour les bibliothèques python et C / C ++ de transférer des données lorsque vous utilisez l'arggisscripting.

J'espère que cela t'aides.

OptimizePrime
la source
1

Une excellente alternative à l' utilisation d' un style ancien UpdateCursor de arcgisscripting, qui est disponible est disponible depuis ArcGIS 10.1 Desktop, est arcpy.da.UpdateCursor .

J'ai constaté que ceux-ci sont généralement environ 10 fois plus rapides.

Celles-ci n'auraient / n'auraient peut-être pas été une option lors de la rédaction de cette question, mais ne devraient pas être négligées par quiconque lisant ce Q&R maintenant.

PolyGeo
la source