root/MGET/Branches/Jason/PythonPackage/src/GeoEco/SpatialAnalysis/Temporal.py @ 980

Revision 980, 56.4 KB (checked in by jjr8, 13 months ago)

Rebuilt installation packages. This will be merged with the Trunk and released as MGET 0.8a40.

Line 
1# SpatialAnalysis/Temporal.py - Classes for temporal analysis.
2#
3# Copyright (C) 2012 Jason J. Roberts
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 2
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License (available in the file LICENSE.TXT)
14# for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20import datetime
21import itertools
22import math
23
24from GeoEco.DynamicDocString import DynamicDocString
25from GeoEco.Internationalization import _
26from GeoEco.Logging import Logger
27
28
29class Periodicity(object):
30    __doc__ = DynamicDocString()
31
32    @classmethod
33    def AnalyzeTable(cls, table, dateField, valueField=None, normalizationField=None, aggregationMethod=u'Mean', where=None, binWidth=None, timeUnits=None, yAxisLabel=None, scaleFactor=None, samplingPeriod=None, maxPeriod=400., periodogramMethod=u'Lomb-Scargle', oversamplingFactor=4., outputFile=None, title=None, width=4., dpi=300., fontSize=10., dayOfYearPlot=u'Automatic', moonPhasePlot=u'Automatic', timeOfDayPlot=u'Automatic', overwriteExisting=False):
34        cls.__doc__.Obj.ValidateMethodInvocation()
35
36        # Perform additional validation.
37
38        if valueField is None:
39            normalizationField = None
40
41        if normalizationField is not None and aggregationMethod == u'sum':
42            raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
43
44        if binWidth is not None and timeUnits == u'automatic':
45            raise ValueError(_(u'A bin width was specified but the time units were not. This is not allowed. If a bin width is specified, the time units must also be specified.'))
46
47        # Read the data from the table into a dictionary that maps
48        # dates to values for those dates. If the caller's data have
49        # high temporal precision then each date will usually have
50        # just one value. But many datasets are not that precise--they
51        # may have a date with no time component, for example. These
52        # may have more than one value per date. Later, we may need
53        # to average or sum them together, as requested by the caller.
54
55        import numpy
56
57        Logger.Info(_(u'Retrieving records to analyze from %(table)s.') % {u'table': table.DisplayName})
58
59        countsForDates = {}
60        valuesForDates = {}
61        normalizeByForDates = {}
62        minDate = None
63        maxDate = None
64
65        if where is None:
66            cursor = table.OpenSelectCursor(rowCount=table.GetRowCount())     # Provide a value for rowCount, so the ArcGIS progressor will be used.
67        else:
68            cursor = table.OpenSelectCursor(where=where)
69        try:
70            while cursor.NextRow():
71                date = cursor.GetValue(dateField)
72                if date is None:
73                    continue
74
75                if valueField is not None:
76                    value = cursor.GetValue(valueField)
77                    if value is None:
78                        continue
79                    value = float(value)
80               
81                if normalizationField is not None:
82                    normalizeBy = cursor.GetValue(normalizationField)
83                    if normalizeBy is None or normalizeBy == 0:
84                        continue
85                    normalizeBy = float(normalizeBy)
86
87                if date not in countsForDates:
88                    countsForDates[date] = 1
89                    if valueField is not None:
90                        valuesForDates[date] = [value]
91                    if normalizationField is not None:
92                        normalizeByForDates[date] = [normalizeBy]
93                else:
94                    countsForDates[date] += 1
95                    if valueField is not None:
96                        valuesForDates[date].append(value)
97                    if normalizationField is not None:
98                        normalizeByForDates[date].append(normalizeBy)
99
100                if minDate is None or date < minDate:
101                    minDate = date
102                if maxDate is None or date > maxDate:
103                    maxDate = date
104        finally:
105            del cursor
106
107        # Validate that we got some data to analyze.
108
109        if len(countsForDates) <= 0:
110            if where is None:
111                raise ValueError(_(u'There are no records in %(table)s to analyze.') % {u'table': table.DisplayName})
112            raise ValueError(_(u'There were no records in %(table)s selected by the where clause %(where)s') % {u'table': table.DisplayName, u'where': where})
113
114        # Build an ascending list of dates.
115
116        dates = sorted(countsForDates.keys())
117
118        # Determine if the dates have time components.
119
120        datesHaveTimes = False
121        for i in range(1, len(dates)):
122            if dates[i].hour != dates[i-1].hour or dates[i].minute != dates[i-1].minute or dates[i].second != dates[i-1].second or dates[i].microsecond != dates[i-1].microsecond:
123                datesHaveTimes = True
124                break
125
126        # If the caller did not specify a sampling period, choose 1
127        # hour or 1 day, depending on whether or not the dates have
128        # time components.
129
130        if samplingPeriod is None:
131            if datesHaveTimes:
132                samplingPeriod = 1./24
133            else:
134                samplingPeriod = 1.
135
136        # Otherwise (the caller did not specify a sampling period) and
137        # the caller specified a sampling period of less than 1 day
138        # but the dates do not include time components, set the
139        # sampling period to 1 day. (Daily periodicity cannot be
140        # detected without time components, so there is no use
141        # checking for it. Doing so will just produce big spectral
142        # power spikes at 1 day and its harmonics, which will be
143        # confusing to interpret.)
144
145        elif samplingPeriod < 1. and not datesHaveTimes:
146            samplingPeriod = 1.
147            Logger.Warning(_(u'A sampling period of less than 1 day was specified but the dates in %(dn)s do not have time components. Without times, it is not possible to detect daily periodicity and therefore there is no reason to use such a small sampling period. A sampling period of 1 day will be used instead.') % {u'dn': table.DisplayName})
148
149        # Validate that the duration of the data is long enough.
150
151        if minDate == maxDate:
152            raise ValueError(_(u'All of the records have the same date (%(date)s). To analyze temporal periodicity, the records must have some variation in their dates.') % {u'date': minDate.strftime('%c')})
153
154        dataDuration = maxDate - minDate
155        dataDuration = float(dataDuration.days) + dataDuration.seconds/86400. + dataDuration.microseconds/(86400.*1000000.)
156        if dataDuration <= samplingPeriod * 2:
157            raise ValueError(_(u'The sampling period (%(sp)g days) is greater than one-half of the duration of the records (%(dur)g days). In order for a signal to be detected, the duration of the records must be at least twice as long as the sampling period. Please decrease the sampling period or select records that cover a longer duration.') % {u'sp': samplingPeriod, u'dur': dataDuration})
158
159        # If the caller requested the Lomb-Scargle method, calculate
160        # the periodogram using lomb_scargle.py.
161
162        if periodogramMethod.lower() == u'lomb-scargle':
163
164            # Build a list of values for the dates. If the caller did
165            # not provide a value field, then we use the count of
166            # records for the dates. If the caller did provide a value
167            # field then we must aggregate the values that occur on
168            # the same date as requested. Note that, unlike the FFT
169            # method, we do not have to aggregate values from
170            # *different* dates together in order to provide values at
171            # a constant time increment.
172
173            def Normalize(numerator, denominator):        # This function is necessary because Python 2.4 does not support the [... if ... else ... for ...] list comprehension syntax; see below.
174                if denominator != 0:
175                    return numerator / denominator
176                return 0.
177
178            if valueField is None:
179                values = [float(countsForDates[d]) for d in dates]
180            elif aggregationMethod == u'sum':
181                if normalizationField is None:
182                    values = [sum(valuesForDates[d]) for d in dates]
183                else:
184                    raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
185            elif normalizationField is None:
186                values = [Normalize(sum(valuesForDates[d]), countsForDates[d]) for d in dates]
187            else:
188                values = [Normalize(sum(valuesForDates[d]), sum(normalizeByForDates[d])) for d in dates]
189
190            # Convert the datetime values to the number of days
191            # elapsed since minDate.
192
193            elapsed = [float((d - minDate).days) + (d - minDate).seconds/86400. + (d - minDate).microseconds/(86400.*1000000.) for d in dates]
194
195            # Calculate the periodogram.
196
197            from GeoEco.AssimilatedModules.lomb_scargle import fasper
198
199            frequencies, powers, Nout, Jmax, Prob = fasper(numpy.array(elapsed), numpy.array(values), oversamplingFactor, 1./samplingPeriod)
200            periods = 1/frequencies
201
202##        # If the caller requested the fast fourier transform method,
203##        # calculate the periodgram using numpy.
204##
205##        elif periodogramMethod.lower() == u'fft':
206##
207##            # TODO: REWRITE THIS CODE. FOR NOW, I'M LEAVING FFT OUT.
208##
209##            # Allocate a list for holding the data at the requested
210##            # sampling frequency, sort the data into these buckets, and
211##            # perform the requested aggregation.
212##
213##            n = int(math.ceil(dataDuration / samplingPeriod))
214##            valueTimeSeries = [[] for i in range(n)]
215##
216##            for i in range(len(dates)):
217##                elapsed = dates[i] - minDate
218##                elapsed = float(elapsed.days) + elapsed.seconds/86400. + elapsed.microseconds/(86400.*1000000.)
219##                valueTimeSeries[int(math.floor(elapsed / samplingPeriod))].append(values[i])
220##
221##            del values, dates
222##           
223##            if aggregationMethod == u'mean':
224##                valueTimeSeries = [v[0] / max(1, v[1]) for v in zip([sum(v) for v in valueTimeSeries], [len(v) for v in valueTimeSeries])]
225##            else:
226##                valueTimeSeries = [sum(v) for v in valueTimeSeries]
227##
228##            # Compute a descrete Fourier Transform for the time series of
229##            # values.
230##
231##            dft = numpy.fft.rfft(valueTimeSeries)[1:]       # Discard the zero-frequency term at index 0
232##
233##            if transform is None:
234##                powers = abs(dft)
235##            elif transform == u'square':
236##                powers = abs(dft)**2
237##            elif transform == u'log':
238##                powers = math.log(abs(dft))
239##            elif transform == u'log10':
240##                powers = math.log10(abs(dft))
241##            else:
242##                raise ValueError(_(u'Unknown transform value.'))
243##
244##            periods = 1. / (numpy.arange(1, len(powers) + 1, dtype='float64')  )
245
246        else:
247            raise NotImplementedError(_(u'"%(method)s" is not a supported method for computing the periodogram.') % {u'method': periodogramMethod})
248
249        # If the caller specified 'Automatic' for any of the radial
250        # plots, determine whether we should plot them, based on the
251        # temporal range and other characteristics of the data.
252
253        if timeOfDayPlot == u'automatic':
254            datesHaveTimes = False
255            for i in range(1, len(dates)):
256                if dates[i].hour != dates[i-1].hour or dates[i].minute != dates[i-1].minute or dates[i].second != dates[i-1].second or dates[i].microsecond != dates[i-1].microsecond:
257                    datesHaveTimes = True
258                    break
259
260        dayOfYearPlot = dayOfYearPlot == u'yes' or dayOfYearPlot == u'automatic' and maxDate - minDate >= datetime.timedelta(335)
261        moonPhasePlot = moonPhasePlot == u'yes' or moonPhasePlot == u'automatic' and maxDate - minDate >= datetime.timedelta(28)
262        timeOfDayPlot = timeOfDayPlot == u'yes' or timeOfDayPlot == u'automatic' and maxDate - minDate >= datetime.timedelta(23./24.) and datesHaveTimes
263
264        # Create the figure. Compute the height from the width and the
265        # number of subplots.
266
267        import matplotlib
268        import matplotlib.pyplot as plt
269        import matplotlib.gridspec as gridspec
270        import matplotlib.lines as lines
271
272        matplotlib.rcParams.update({'font.size': fontSize})
273
274        numRows = 2 + int(dayOfYearPlot or moonPhasePlot or timeOfDayPlot)
275        numCols = max(1, int(dayOfYearPlot) + int(moonPhasePlot) + int(timeOfDayPlot))
276
277        if outputFile is None:
278            figsize = (max(6, 3 * numCols), 3 * numRows)
279            dpi = 80
280        else:
281            figsize = (width, width * (float(numRows)/float(numCols)))
282       
283        fig = plt.figure(figsize=figsize, dpi=dpi)
284        try:
285            gs = gridspec.GridSpec(numRows, numCols)
286           
287            # Subplot 1: bar plot of values for dates.
288            # ========================================
289
290            # If the caller did not specify a bin width, select one
291            # automatically. Our goal is to show fine resolution, so
292            # we try to have 400 or 600 bins (we use 600 if the figure
293            # has 3 columns) using a bin width that would be naturally
294            # chosen by a person (e.g. "1 week", "30 days", etc).
295
296            TotalSeconds = lambda dt: dt.days*86400. + dt.seconds + dt.microseconds*0.000001
297
298            if binWidth is None:
299                binWidthNames = [_(u'Hour'),
300                                 _(u'4 Hours'),
301                                 _(u'6 Hours'),
302                                 _(u'12 Hours'),
303                                 _(u'Day'),
304                                 _(u'3 Days'),
305                                 _(u'Week'),
306                                 _(u'14 Days'),
307                                 _(u'30 Days'),
308                                 _(u'90 Days'),
309                                 _(u'Year')]
310
311                binWidthDeltas = [datetime.timedelta(seconds=3600),
312                                  datetime.timedelta(seconds=3600*4),
313                                  datetime.timedelta(seconds=3600*6),
314                                  datetime.timedelta(seconds=3600*12),
315                                  datetime.timedelta(days=1),
316                                  datetime.timedelta(days=3),
317                                  datetime.timedelta(days=7),
318                                  datetime.timedelta(days=14),
319                                  datetime.timedelta(days=30),
320                                  datetime.timedelta(days=90),
321                                  datetime.timedelta(days=365)]
322
323                if numCols <= 2:
324                    desiredBins = 400
325                else:
326                    desiredBins = 600
327
328                numBinsForDeltas = [TotalSeconds(maxDate - minDate) / TotalSeconds(delta) < desiredBins for delta in binWidthDeltas]
329
330                if True not in numBinsForDeltas:
331                    binWidthInSeconds = TotalSeconds(binWidthDeltas[-1])
332                    binWidthName = binWidthNames[-1]
333                else:
334                    binWidthInSeconds = TotalSeconds(binWidthDeltas[numBinsForDeltas.index(True)])
335                    binWidthName = binWidthNames[numBinsForDeltas.index(True)]
336
337            # Otherwise (the caller did specify a bin width), create a
338            # display name for it and compute its width in seconds.
339
340            else:
341                if timeUnits == u'hours':
342                    binWidthInSeconds = TotalSeconds(datetime.timedelta(hours=binWidth))
343                    if binWidthInSeconds == 3600:
344                        binWidthName = 'Hour'
345                    elif binWidthInSeconds > 3600:
346                        binWidthName = '%r Hours' % round((binWidthInSeconds / 3600), 1)
347                    else:
348                        binWidthName = '%r Hour' % round((binWidthInSeconds / 3600), 2)
349                       
350                elif timeUnits == u'days':
351                    binWidthInSeconds = TotalSeconds(datetime.timedelta(binWidth))
352                    if binWidthInSeconds == 86400*365:
353                        binWidthName = 'Year'
354                    elif binWidthInSeconds == 86400*7:
355                        binWidthName = 'Week'
356                    elif binWidthInSeconds == 86400:
357                        binWidthName = 'Day'
358                    elif binWidthInSeconds > 86400:
359                        binWidthName = '%r Days' % round((binWidthInSeconds / 3600), 1)
360                    else:
361                        binWidthName = '%r Day' % round((binWidthInSeconds / 3600), 2)
362
363                else:
364                    raise ValueError(_(u'Programming error in this tool: Unknown timeUnits %r. Please contact the author of this tool for assistance.') % timeUnits)
365
366            # Determine the start date for the first bin. For display
367            # purposes, we want this to be rounded to a whole hour,
368            # day, or year.
369
370            if binWidthName.endswith('Hour') or binWidthName.endswith('Hours'):
371                firstBinStartDate = datetime.datetime(minDate.year, minDate.month, minDate.day, minDate.hour)
372            elif binWidthName.endswith('Year'):
373                firstBinStartDate = datetime.datetime(minDate.year, 1, 1)
374            else:
375                firstBinStartDate = datetime.datetime(minDate.year, minDate.month, minDate.day)
376
377            # Define a function that returns the start date of the bin
378            # that a given date falls within.
379
380            BinStartDate = lambda d: firstBinStartDate + datetime.timedelta(seconds=math.floor(TotalSeconds(d - firstBinStartDate) / binWidthInSeconds) * binWidthInSeconds)
381
382            # Calculate the values of the bins.
383
384            binStartDates = []
385            binValues = []
386
387            for binStartDate, datesInBin in itertools.groupby(dates, BinStartDate):
388                binStartDates.append(binStartDate)
389
390                if valueField is None:
391                    binValues.append(sum([countsForDates[d] for d in datesInBin]))
392
393                elif aggregationMethod == u'sum':
394                    if normalizationField is None:
395                        binValues.append(sum([sum(valuesForDates[d]) for d in datesInBin]))
396                    else:
397                        raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
398
399                else:
400                    datesInBinList = list(datesInBin)                                           # This is necessary because datesInBin is an iterator and we need to access it multiple times
401                    sumOfValues = float(sum([sum(valuesForDates[d]) for d in datesInBinList]))
402
403                    if normalizationField is None:
404                        count = sum([countsForDates[d] for d in datesInBinList])
405                        if count > 0:
406                            binValues.append(sumOfValues / count)
407                        else:
408                            binValues.append(0.)
409
410                    else:
411                        sumOfNormalizeBy = float(sum([sum(normalizeByForDates[d]) for d in datesInBinList]))
412                        if sumOfNormalizeBy > 0:
413                            binValues.append(sumOfValues / sumOfNormalizeBy)
414                        else:
415                            binValues.append(0.)
416
417            # If the caller provided a scale factor, apply it.
418
419            if scaleFactor is not None:
420                binValues = [value * scaleFactor for value in binValues]
421                scaleFactorLabel = u' \xd7 %g' % scaleFactor
422            else:
423                scaleFactorLabel = u''
424
425            # Now create the plot
426
427            matplotlib.rc('xtick', direction='out')
428            matplotlib.rc('ytick', direction='out')
429
430            ax1 = plt.subplot(gs[0, :])
431            ax1.bar(binStartDates, binValues, width=binWidthInSeconds / 86400., color='black', linewidth=0)     # Convert seconds to days, which matplotlib uses as width of bars when x axis uses datetimes
432
433            if yAxisLabel is not None:
434                ax1.set_ylabel(yAxisLabel)
435            elif valueField is None:
436                ax1.set_ylabel(u'Count / ' + binWidthName + scaleFactorLabel)
437            elif aggregationMethod == u'sum':
438                if normalizationField is None:
439                    ax1.set_ylabel(valueField + u' / ' + binWidthName + scaleFactorLabel)
440                else:
441                    raise ValueError(_(u'A normalization field was specified and "sum" aggregation was requested. This is not allowed. "Sum" aggregation may only be requested if a normalization field is not specified.'))
442            elif normalizationField is None:
443                ax1.set_ylabel(u'Mean ' + valueField + scaleFactorLabel)
444            else:
445                ax1.set_ylabel(valueField + u' / ' + normalizationField + scaleFactorLabel)
446
447            # Rotate the dates on the x axis to make them easier to
448            # read. This code was adapted from matplotlib's
449            # autofmt_xdate().
450           
451            for label in ax1.get_xticklabels():
452                label.set_ha('right')
453                label.set_rotation(40)
454
455            # Subplot 2: the periodogram.
456            # ===========================
457
458            periodsToPlot = numpy.logical_and(periods >= samplingPeriod * 2, periods <= min(maxPeriod, max(periods)))
459
460            ax2 = plt.subplot(gs[1, :])
461            plt.plot(periods[periodsToPlot], powers[periodsToPlot], 'k')
462            ax2.set_xlabel('Period (days)')
463            ax2.set_ylabel('Spectral Power')
464            #ax2.set_xlim(right=min(maxPeriod, max(periods)))
465
466            matplotlib.rc('xtick', direction='in')
467            matplotlib.rc('ytick', direction='in')
468
469            # Subplot 3: day-of-year radial bar plot.
470            # =======================================
471
472            if dayOfYearPlot:
473
474                # Calculate the 0-indexed day of the year as 0 to 364
475                # (Jan 1 to Dec 31). On leap years, treat both Dec 30
476                # and 31 as day 364.
477
478                daysOfYear = [min(364, int(d.strftime('%j')) - 1) for d in dates]
479
480                # Classify the moon phase values into 73 bins, each 5
481                # days long.
482
483                binForDOY = [int(math.floor(doy / 5.)) for doy in daysOfYear]
484
485                # Compute the mean value for each bin.
486
487                binCount = numpy.zeros(73, int)
488                binSum = numpy.zeros(len(binCount))
489               
490                for i in range(len(values)):
491                    binCount[binForDOY[i]] += 1
492                    binSum[binForDOY[i]] += values[i]
493
494                binMean = numpy.zeros(len(binCount))
495                binMean[binCount > 0] = binSum[binCount > 0] / binCount[binCount > 0]
496
497                # Normalize the means.
498
499                binMean /= binSum.sum()
500
501                # Create a polar bar plot.
502
503                ax3 = plt.subplot(gs[2, 0], polar=True)
504                ax3.bar([float(i)/len(binCount) * 2 * math.pi for i in range(len(binCount))], binMean, 1./len(binCount) * 2 * math.pi, color='gray')
505                ax3.set_yticklabels([])
506
507                # Label the first days of the months.
508
509                ax3.set_theta_zero_location('S')
510                ax3.set_theta_direction(-1)
511
512                ax3.set_thetagrids([(int(datetime.datetime(2001, month, 1).strftime('%j')) - 1)/365. * 360. for month in range(1, 13)],
513                                   ['Jan 1', 'Feb 1', 'Mar 1', 'Apr 1', 'May 1', 'Jun 1', 'Jul 1', 'Aug 1', 'Sep 1', 'Oct 1', 'Nov 1', 'Dec 1']);
514
515                for label in ax3.get_xticklabels():
516                    if label.get_text() == 'Jan 1':
517                        label.set_va('top')
518                    elif label.get_text() in ['Feb 1', 'Mar 1', 'Apr 1', 'May 1', 'Jun 1']:
519                        label.set_ha('right')
520                    elif label.get_text() == 'Jul 1':
521                        label.set_va('bottom')
522                    else:
523                        label.set_ha('left')
524
525            # Subplot 4: moon-phase radial bar plot.
526            # ======================================
527
528            if moonPhasePlot:
529
530                # Calculate the moon phase as a number ranging from 0
531                # to 1, where 0 and 1 are new moon and 0.5 is full.
532
533                from GeoEco.AssimilatedModules.moon import MoonPhase
534
535                mp = [MoonPhase(d).phase for d in dates]
536
537                # Classify the moon phase values into 30 bins, each
538                # just under 1 day long.
539
540                binForMP = [int(math.floor(p * 30.)) for p in mp]
541
542                # Compute the mean value for each bin.
543
544                binCount = numpy.zeros(30, int)
545                binSum = numpy.zeros(len(binCount))
546               
547                for i in range(len(values)):
548                    binCount[binForMP[i]] += 1
549                    binSum[binForMP[i]] += values[i]
550
551                binMean = numpy.zeros(len(binCount))
552                binMean[binCount > 0] = binSum[binCount > 0] / binCount[binCount > 0]
553
554                # Normalize the means.
555
556                binMean /= binSum.sum()
557
558                # Create a polar bar plot.
559
560                ax4 = plt.subplot(gs[2, 0 + int(dayOfYearPlot)], polar=True)
561                ax4.bar([float(i)/len(binCount) * 2 * math.pi for i in range(len(binCount))], binMean, 1./len(binCount) * 2 * math.pi, color='gray')
562                ax4.set_yticklabels([])
563
564                # Label the quadrants with moon phase names.
565
566                ax4.set_theta_zero_location('W')
567                ax4.set_theta_direction(-1)
568               
569                ax4.set_thetagrids([0, 90, 180, 270], ['New\nMoon', 'First Quarter', 'Full\nMoon', 'Last Quarter']);
570
571                for label in ax4.get_xticklabels():
572                    if label.get_text() == 'New\nMoon':
573                        label.set_ha('right')
574                    elif label.get_text() == 'First Quarter':
575                        label.set_va('bottom')
576                    elif label.get_text() == 'Full\nMoon':
577                        label.set_ha('left')
578                    else:
579                        label.set_va('top')
580
581            # Subplot 5: day-of-year radial bar plot.
582            # =======================================
583
584            if timeOfDayPlot:
585
586                # Extract the hours from the datetime instances.
587
588                hours = [d.hour for d in dates]
589
590                # Compute the mean value for each bin.
591
592                binCount = numpy.zeros(24, int)
593                binSum = numpy.zeros(len(binCount))
594               
595                for i in range(len(values)):
596                    binCount[hours[i]] += 1
597                    binSum[hours[i]] += values[i]
598
599                binMean = numpy.zeros(len(binCount))
600                binMean[binCount > 0] = binSum[binCount > 0] / binCount[binCount > 0]
601
602                # Normalize the means.
603
604                binMean /= binSum.sum()
605
606                # Create a polar bar plot.
607
608                ax5 = plt.subplot(gs[2, -1], polar=True)
609                ax5.bar([float(i)/len(binCount) * 2 * math.pi for i in range(len(binCount))], binMean, 1./len(binCount) * 2 * math.pi, color='gray')
610                ax5.set_yticklabels([])
611
612                # Label the first days of the months.
613
614                ax5.set_theta_zero_location('S')
615                ax5.set_theta_direction(-1)
616
617                ax5.set_thetagrids([360 * hour/24. for hour in range(0, 24, 3)],
618                                   ['%02i:00' % hour for hour in range(0, 24, 3)]);
619
620                for label in ax5.get_xticklabels():
621                    if label.get_text() == '00:00':
622                        label.set_va('top')
623                    elif label.get_text() in ['03:00', '06:00', '09:00']:
624                        label.set_ha('right')
625                    elif label.get_text() == '12:00':
626                        label.set_va('bottom')
627                    else:
628                        label.set_ha('left')
629
630            # Use tight layout to allow dates to display properly.
631
632            plt.tight_layout()
633
634            # If the caller provided a plot title, add it. This must
635            # be done after calling plt.tight_layout().
636           
637            if title is not None:
638                plt.subplots_adjust(top=0.93)
639                fig.suptitle(title)
640
641            # If the caller provided an outputFile, save the figure to
642            # it. If not, display the figure interactively.
643
644            if outputFile is not None:
645                plt.savefig(outputFile, dpi=dpi)
646            else:
647                Logger.Info(_(u'Waiting for the interactive plot window to be closed...'))
648                plt.show()
649               
650        finally:
651            plt.close(fig)
652
653    @classmethod
654    def AnalyzeArcGISTable(cls, table, dateField, valueField=None, normalizationField=None, aggregationMethod=u'Mean', where=None, binWidth=None, timeUnits=None, yAxisLabel=None, scaleFactor=None, samplingPeriod=None, maxPeriod=400., periodogramMethod=u'Lomb-Scargle', oversamplingFactor=4., outputFile=None, title=None, width=4., dpi=300., fontSize=10., dayOfYearPlot=u'Automatic', moonPhasePlot=u'Automatic', timeOfDayPlot=u'Automatic', overwriteExisting=False):
655        cls.__doc__.Obj.ValidateMethodInvocation()
656        from GeoEco.Datasets.ArcGIS import ArcGISTable
657        cls.AnalyzeTable(ArcGISTable(table), dateField, valueField, normalizationField, aggregationMethod, where, binWidth, timeUnits, yAxisLabel, scaleFactor, samplingPeriod, maxPeriod, periodogramMethod, oversamplingFactor, outputFile, title, width, dpi, fontSize, dayOfYearPlot, moonPhasePlot, timeOfDayPlot, overwriteExisting)
658       
659
660###############################################################################
661# Metadata: module
662###############################################################################
663
664from GeoEco.ArcGIS import ArcGISDependency
665from GeoEco.Datasets import Table
666from GeoEco.Dependencies import MatplotlibDependency
667from GeoEco.Metadata import *
668from GeoEco.Types import *
669
670AddModuleMetadata(shortDescription=_(u'Classes for temporal analysis.'))
671
672###############################################################################
673# Metadata: Periodicity class
674###############################################################################
675
676AddClassMetadata(Periodicity,
677    shortDescription=_(u'Methods for analyzing temporal perodicity in time-series data.'),
678    isExposedAsCOMServer=True,
679    comIID=u'{CABE75DC-A0A4-410D-8698-9FEF7085232A}',
680    comCLSID=u'{8D268D4D-320B-4E07-8948-954D90F87544}')
681
682# Public method: Periodicity.AnalyzeTable
683
684AddMethodMetadata(Periodicity.AnalyzeTable,
685    shortDescription=_(u'Creates several plots that show the temporal periodicity of the records in a Table.'),
686    longDescription=_(
687u"""This tool creates several plots that show whether or not the
688records or the value of some field of them exhibit any temporal
689periodicity (i.e., patterns that repeat in time). What is analyzed and
690presented in the plots depends on what fields you select, if any, for
691analysis.
692
693The first plot is a bar plot showing values at different times over
694the temporal extent of the data. If you do not select a Value Field
695for analysis, then this plot will be a simple histogram showing the
696counts of records in a series of equal-duration time bins. If you do
697select a Value Field, then the plot will show the sum or mean of that
698field for each bin (the Aggregation Method parameter specifies whether
699the sum or mean is used). Finally, if you also specify a Normalization
700Field, the plot will show the sum of the Value Field divided by the
701sum of the Normalization Field, for each bin (the Aggregation Method
702parameter is ignored in this case).
703
704The second plot is a periodogram that indicates whether or not a
705repeating signal exists in the data at various frequencies. The second
706plot is produced by conducting Fourier analysis on the data plotted in
707the first plot. In Fourier analysis, the overall pattern in the data
708is decomposed into a series of sine waves having different frequencies
709and amplitudes. When summed together, these waves accurately
710reconstruct the overall pattern. The periodogram summarizes the
711collection of waves by plotting the period (the inverse of the
712frequency) and the spectral power of each one.
713
714The spectral power is a measure of the strength of the contribution of
715the wave's contribute to the overall pattern. Important waves appear
716as peaks in the periodogram. When you find a peak, the value of the x
717axis gives the period (in days) of the cycle. In marine ecological
718data, patterns are often observed in relation to solar, lunar, or
719tidal cycles. Solar cycles are indicated by peaks at 365 days (an
720annual cycle) and at 24 hours (a diurnal cycle). Lunar cycles are
721indicated by a peak at 29.5 days. Tidal cycles are more complicated to
722observe, and depend on tidal patterns in the region of interest.
723
724To interpret the periodogram, look for high spikes at 365 days, 29.5
725days, 1 day, with very low values elsewhere. If you see high spikes at
726one or more of those locations, a solar or lunar cycle is indicated.
727But if those spikes are not very high relative to other periods--if
728the periodogram displays a generally jittery or noisy pattern--then
729solar and lunar cycles may not exist.
730
731In order for Fourier analysis to detect such patterns, the data must
732span at least two cycles. For example, to detect an annual pattern,
733the data must span at least two years. The results will be much more
734reliable if many years of data are available.
735
736To help you better determine whether annual, lunar, or diurnal
737patterns exist, up to three optional plots will be created. Each of
738these is a radial bar plot, similar to the first bar plot but binning
739the data according to day of the year, phase of the lunar cycle, or
740hour of the day. If temporal cycles exist, the radial plots will show
741high values at some times and low values at others.
742
743By default, all three radial plots will be produced if there are
744sufficient data available, but you can manually enable or disable each
745one."""),
746    isExposedToPythonCallers=True,
747    dependencies=[MatplotlibDependency()])
748
749AddArgumentMetadata(Periodicity.AnalyzeTable, u'cls',
750    typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Periodicity),
751    description=_(u'Periodicity class or instance.'))
752
753AddArgumentMetadata(Periodicity.AnalyzeTable, u'table',
754    typeMetadata=ClassInstanceTypeMetadata(cls=Table),
755    description=_(
756u"""Table to analyze.
757
758The table must contain, at minimum, a date field. It may optionally
759contain an integer or floating point field of values to analyze, and
760another integer or floating point field used to normalize the
761values."""))
762
763AddArgumentMetadata(Periodicity.AnalyzeTable, u'dateField',
764    typeMetadata=TableFieldTypeMetadata(u'table', allowedFieldTypes=[u'date', u'datetime']),
765    description=_(
766u"""Field containing the dates of the records."""))
767
768AddArgumentMetadata(Periodicity.AnalyzeTable, u'valueField',
769    typeMetadata=TableFieldTypeMetadata(u'table', allowedFieldTypes=[u'int8', u'uint8', u'int16', u'uint16', u'int32', u'uint32', u'int64', u'uint64', u'float32', u'float64'], canBeNone=True),
770    description=_(
771u"""Field containing the values to analyze.
772
773If this field is not provided, the temporal analysis will be conducted
774only on the dates of the records. The first plot will be a simple
775histogram showing the counts of records for a series of equal-duration
776time bins. The periodogram will be based on a Fourier analysis of the
777counts.
778
779If this field is provided and the Normalization Field is not provided,
780the temporal analysis will be conducted on the values of this field
781for the dates they occur. The first plot will show the sum or mean of
782the values for each time bin (the Aggregation Method parameter
783specifies whether the sum or mean is used). The periodogram will be
784based on a Fourier analysis of the values.
785
786If this field is provided and the Normalization Field is also
787provided, the first plot will show the sum of values of this field
788divided by the sum of the values of the Normalization Field, for each
789time bin (the Aggregation Method parameter is ignored in this case).
790The periodogram will be based on a Fourier analysis of the normalized
791values."""))
792
793AddArgumentMetadata(Periodicity.AnalyzeTable, u'normalizationField',
794    typeMetadata=TableFieldTypeMetadata(u'table', allowedFieldTypes=[u'int8', u'uint8', u'int16', u'uint16', u'int32', u'uint32', u'int64', u'uint64', u'float32', u'float64'], mustBeDifferentThanArguments=[u'valueField'], canBeNone=True),
795    description=_(
796u"""Field for normalizing the Value Field. Please see the
797documentation for that parameter for more information.
798
799The Normalization Field is ignored if the Value Field is not
800provided."""))
801
802AddArgumentMetadata(Periodicity.AnalyzeTable, u'aggregationMethod',
803    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Mean', u'Sum'], makeLowercase=True),
804    description=_(
805u"""Specifies whether the analysis should use the mean or the sum of
806the Value Field. Please see the documentation for that parameter for
807more information.
808
809If a Value Field is not specified, this parameter is ignored. If a
810Value Field and Normalization Field both are provided, then this
811parameter must be set to Mean."""),
812    arcGISDisplayName=_(u'Aggregation method'))
813
814AddArgumentMetadata(Periodicity.AnalyzeTable, u'where',
815    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
816    description=_(
817u"""SQL WHERE clause expression that specifies the subset of points to
818use. If this parameter is not provided, all of the points will be
819used. The exact syntax of this expression depends on the underlying
820storage format of the input Table."""))
821
822AddArgumentMetadata(Periodicity.AnalyzeTable, u'binWidth',
823    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
824    description=_(
825u"""Width, in time units, of the bins used to produce the first plot.
826If you provide a value for this parmaeter, you must also specify the
827value for the Time Units parameter.
828
829If this parameter is omitted, a value that allows a large number of
830bins will be automatically selected, with the hope that fine temporal
831dynamics may be observable."""),
832    arcGISDisplayName=_(u'Bar plot bin width'),
833    arcGISCategory=_(u'Bar plot options'))
834
835AddArgumentMetadata(Periodicity.AnalyzeTable, u'timeUnits',
836    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Days', u'Hours'], makeLowercase=True, canBeNone=True),
837    description=_(
838u"""Specifies the time units of the Bar Plot Bin Width parameter. If
839you specify a Bar Plot Bin Width, you must also specify the time
840units."""),
841    arcGISDisplayName=_(u'Time units'),
842    arcGISCategory=_(u'Bar plot options'))
843
844AddArgumentMetadata(Periodicity.AnalyzeTable, u'yAxisLabel',
845    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
846    description=_(
847u"""Label for the y axis of the bar plot. If you do not provide a
848label, a generic label will be created for you."""),
849    arcGISDisplayName=_(u'Bar plot y axis label'),
850    arcGISCategory=_(u'Bar plot options'))
851
852AddArgumentMetadata(Periodicity.AnalyzeTable, u'scaleFactor',
853    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
854    description=_(
855u"""Scale factor. If provided, the values will be multiplied by this
856value prior to plotting.
857
858Use this value to scale the y axis values to a preferred range. For
859example, which calculating catch per unit effort (CPUE) for longline
860fishing gear, it is traditional to present catch per 1000 hooks. But
861your Value Field contains the amount of catch and your Normalization
862Field contains the number of hooks, the unscaled result will be catch
863per 1 hook. To plot catch per 1000 hooks, provide 1000 for the Scale
864Factor parameter."""),
865    arcGISDisplayName=_(u'Scale factor'),
866    arcGISCategory=_(u'Bar plot options'))
867
868AddArgumentMetadata(Periodicity.AnalyzeTable, u'samplingPeriod',
869    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
870    description=_(
871u"""Sampling period, in days, for the Fourier analysis used to produce
872the periodogram.
873
874If you do not supply a value, one will be selected for you. If the
875dates in your table include time components, 1 hour will be used. If
876they do not, 1 day will be used.
877
878The sampling period determines the period of cycles that can be
879detected. The shorter the sampling period (i.e. the higher the
880sampling frequency), the shorter the cycles that can be detected, with
881the shortest detectable cycle equal to twice the sampling period.
882
883If you specify a sampling period of less than 1 day but the dates in
884your table do not include time components, or all of the time values
885are the same, a sampling period of 1 day will be used. This is
886because, when all the times are the same, strong periodicity will be
887detected the period of 1 day (because no records will occur at
888fractional days) and the harmonics of 1 day (e.g. 1/2 day, 1/3 day,
8891/4 day, and so on). These strong but spurious cycles will produce
890spikes in the periodogram at those periods, obscuring the cycles of
891true interest that occur at higher periods."""),
892    arcGISDisplayName=_(u'Sampling period'),
893    arcGISCategory=_(u'Periodogram options'))
894
895AddArgumentMetadata(Periodicity.AnalyzeTable, u'maxPeriod',
896    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0., canBeNone=True),
897    description=_(
898u"""Maximum period, in days, to plot on the periodogram.
899
900By default, this parameter is set to 400 days, under the observation
901that natural ecological phenomena frequenty exhibit annual periodicity
902(which results in a spike in the periodogram at 365 days) but that
903most users of this tool will either not be interested in or have the
904data to detect cycles having longer periods. If you have sufficient
905data and wish to check for longer cycles (e.g. decadal oscillations),
906increase this parameter."""),
907    arcGISDisplayName=_(u'Maximum period to plot'),
908    arcGISCategory=_(u'Periodogram options'))
909
910AddArgumentMetadata(Periodicity.AnalyzeTable, u'periodogramMethod',
911    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Lomb-Scargle'], makeLowercase=True),
912    description=_(
913u"""Method to use for computing the periodogram. Currently only one
914method is available:
915
916* Lomb-Scargle - the Lomb (1976) and Scargle (1976) method is designed
917  to perform spectral analysis on unevenly sampled data, making it
918  more suitable for analysis of ecological data than the most common
919  method for spectral analysis, the Fast Fourier transform (FFT). This
920  tool uses a Python implementation of Press and Rybicki's (1989)
921  algorithm for the Lomb-Scargle method.
922
923References
924
925Lomb, N (1976). Least-Squares Frequency Analysis of Unequally Spaced
926Data. Astrophysics and Space Science 39: 447-462.
927
928Press, WH and GB Rybicki (1989). Fast algorithm for spectral analysis
929of unevenly sampled data. Astrophysical Journal, Part 1, vol. 338, p. 277-280.
930
931Scargle, J. (1982). Studies in Astronomical Time Series Analysis II.
932Statistical Aspects of Spectral Analysis of Unevenly Spaced Data.
933Astrophysical Journal, Part 1, vol. 263, p. 835-853."""),
934    arcGISDisplayName=_(u'Periodogram method'),
935    arcGISCategory=_(u'Periodogram options'))
936
937AddArgumentMetadata(Periodicity.AnalyzeTable, u'oversamplingFactor',
938    typeMetadata=FloatTypeMetadata(minValue=1., canBeNone=True),
939    description=_(
940u"""Oversampling factor for the Lomb-Scargle method.
941
942In order to produce accurate values for the peaks in the peridogram,
943it is necessary to sample the input data at a higher frequency than is
944necessary to merely detect a short-period cycle. This is called
945oversampling. In practice, an oversampling factor of 4 or higher is
946recommended for the Lomb-Scargle method. (The effective sampling
947period is the Sampling Period parameter divided by the Oversampling
948Factor parameter.)"""),
949    arcGISDisplayName=_(u'Oversampling factor'),
950    arcGISCategory=_(u'Periodogram options'))
951
952AddArgumentMetadata(Periodicity.AnalyzeTable, u'outputFile',
953    typeMetadata=FileTypeMetadata(deleteIfParameterIsTrue=u'overwriteExisting', createParentDirectories=True, canBeNone=True),
954    description=_(
955u"""Output plot file.
956
957If this parameter is not provided (the default), a plot will be
958created in an interactive window.
959
960If this parameter is provided, a plot will be written to the file and
961an interactive window will not be created. The extension of the file
962determines its format and may be:
963
964* .emf - Enhanced Metafile
965* .eps - Encapsulated Postscript
966* .pdf - Portable Document Format
967* .png - Portable Network Graphics
968* .ps - Postscript
969* .raw - Raw RGBA bitmap
970* .rgba - Raw RGBA bitmap
971* .svg - Scalable Vector Graphics
972* .svgz - Scalable Vector Graphics
973"""),
974    direction=u'Output',
975    arcGISDisplayName=_(u'Output plot file'),
976    arcGISCategory=_(u'Other plotting options'))
977
978AddArgumentMetadata(Periodicity.AnalyzeTable, u'title',
979    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
980    description=_(
981u"""Title of the plot, to appear at the very top."""),
982    arcGISDisplayName=_(u'Plot title'),
983    arcGISCategory=_(u'Other plotting options'))
984
985AddArgumentMetadata(Periodicity.AnalyzeTable, u'width',
986    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0.),
987    description=_(
988u"""Width of the output plot file, in inches. This parameter is
989ignored if the plot is not written to an output file."""),
990    arcGISDisplayName=_(u'Plot width'),
991    arcGISCategory=_(u'Other plotting options'))
992
993AddArgumentMetadata(Periodicity.AnalyzeTable, u'dpi',
994    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0.),
995    description=_(
996u"""Dots per inch (DPI) of the output plot file. This parameter is
997ignored if the plot is not written to an output file, and may not
998apply for vector-based output formats."""),
999    arcGISDisplayName=_(u'Plot DPI'),
1000    arcGISCategory=_(u'Other plotting options'))
1001
1002AddArgumentMetadata(Periodicity.AnalyzeTable, u'fontSize',
1003    typeMetadata=FloatTypeMetadata(mustBeGreaterThan=0.),
1004    description=_(
1005u"""Size of the font for plotting text, in points."""),
1006    arcGISDisplayName=_(u'Font size'),
1007    arcGISCategory=_(u'Other plotting options'))
1008
1009AddArgumentMetadata(Periodicity.AnalyzeTable, u'dayOfYearPlot',
1010    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Automatic', u'No', u'Yes'], makeLowercase=True),
1011    description=_(
1012u"""Specifies whether or not a radial bar plot should be generated
1013showing the distribution of values according day of the year. If
1014'Automatic' is specified, a plot will be generated if the data span
1015335 days or more.
1016
1017Each bar of the plot encompasses 5 days (the first bin is January 1-5,
1018the second is January 6-10, and so on). On leap years, the last bar
1019encompasses 6 days.
1020
1021The values of the bars are calculated similar to the main bar plot: if
1022a Value Field is not specified, each 5-day bar shows the count of
1023records for those days. If a Value Field is specified but a
1024Normalization Field is not, each bar shows the sum or mean of the
1025values for those days (the Aggregation Method parameter specifies
1026whether the sum or mean is used). If both a Value Field and
1027Normalization Field are specified, each bar shows the sum of the Value
1028Field divided by the sum of the Normalization Field for those
1029days."""),
1030    arcGISDisplayName=_(u'Create day of year plot'),
1031    arcGISCategory=_(u'Other plotting options'))
1032
1033AddArgumentMetadata(Periodicity.AnalyzeTable, u'moonPhasePlot',
1034    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Automatic', u'No', u'Yes'], makeLowercase=True),
1035    description=_(
1036u"""Specifies whether or not a radial bar plot should be generated
1037showing the distribution of values according to the phase of the moon
1038calculated from the records' dates. If 'Automatic' is specified, a
1039plot will be generated if the data span 28 days or more.
1040
1041The plot has 30 equal-width bars, each corresponding to about 1 day
1042(the moon phase is actually about 29.53 days long, so each bar is
1043about 29.53/30 days long).
1044
1045The values of the bars are calculated similar to the main bar plot: if
1046a Value Field is not specified, each bar shows the count of records
1047for that fraction of the moon phase. If a Value Field is specified but
1048a Normalization Field is not, each bar shows the sum or mean of the
1049values for that fraction (the Aggregation Method parameter specifies
1050whether the sum or mean is used). If both a Value Field and
1051Normalization Field are specified, each bar shows the sum of the Value
1052Field divided by the sum of the Normalization Field for that
1053fraction."""),
1054    arcGISDisplayName=_(u'Create moon phase plot'),
1055    arcGISCategory=_(u'Other plotting options'))
1056
1057AddArgumentMetadata(Periodicity.AnalyzeTable, u'timeOfDayPlot',
1058    typeMetadata=UnicodeStringTypeMetadata(allowedValues=[u'Automatic', u'No', u'Yes'], makeLowercase=True),
1059    description=_(
1060u"""Specifies whether or not a radial bar plot should be generated
1061showing the distribution of values according to the time of the day.
1062If 'Automatic' is specified, a plot will be generated if the data span
106323 hours or more.
1064
1065The plot has 24 hour-long bars.
1066
1067The values of the bars are calculated similar to the main bar plot: if
1068a Value Field is not specified, each bar shows the count of records
1069for that hour of the day. If a Value Field is specified but a
1070Normalization Field is not, each bar shows the sum or mean of the
1071values for that hour (the Aggregation Method parameter specifies
1072whether the sum or mean is used). If both a Value Field and
1073Normalization Field are specified, each bar shows the sum of the Value
1074Field divided by the sum of the Normalization Field for that
1075hour."""),
1076    arcGISDisplayName=_(u'Create time of day plot'),
1077    arcGISCategory=_(u'Other plotting options'))
1078
1079AddArgumentMetadata(Periodicity.AnalyzeTable, u'overwriteExisting',
1080    typeMetadata=BooleanTypeMetadata(),
1081    description=_(
1082u"""If True, the output plot file will be overwritten, if it exists.
1083If False, a ValueError will be raised if the output plot file
1084exists."""))
1085
1086# Public method: Periodicity.AnalyzeArcGISTable
1087
1088AddMethodMetadata(Periodicity.AnalyzeArcGISTable,
1089    shortDescription=_(u'Creates several plots that show the temporal periodicity of the records in a table.'),
1090    longDescription=Periodicity.AnalyzeTable.__doc__.Obj.LongDescription,
1091    isExposedToPythonCallers=True,
1092    isExposedByCOM=True,
1093    isExposedAsArcGISTool=True,
1094    arcGISDisplayName=_(u'Analyze Temporal Periodocity in Table'),
1095    arcGISToolCategory=_(u'Spatial and Temporal Analysis\\Analyze Temporal Periodocity'),
1096    dependencies=[ArcGISDependency(9, 1), MatplotlibDependency()])
1097
1098CopyArgumentMetadata(Periodicity.AnalyzeTable, u'cls', Periodicity.AnalyzeArcGISTable, u'cls')
1099
1100AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'table',
1101    typeMetadata=ArcGISTableViewTypeMetadata(mustExist=True),
1102    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[1].Description,
1103    arcGISDisplayName=_(u'Table'))
1104
1105AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'dateField',
1106    typeMetadata=ArcGISFieldTypeMetadata(mustExist=True, allowedFieldTypes=[u'date']),
1107    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[2].Description,
1108    arcGISDisplayName=_(u'Date field'),
1109    arcGISParameterDependencies=[u'table'])
1110
1111AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'valueField',
1112    typeMetadata=ArcGISFieldTypeMetadata(mustExist=True, allowedFieldTypes=[u'SHORT', u'LONG', u'FLOAT', u'DOUBLE'], canBeNone=True),
1113    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[3].Description,
1114    arcGISDisplayName=_(u'Value field'),
1115    arcGISParameterDependencies=[u'table'])
1116
1117AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'normalizationField',
1118    typeMetadata=ArcGISFieldTypeMetadata(mustExist=True, allowedFieldTypes=[u'SHORT', u'LONG', u'FLOAT', u'DOUBLE'], canBeNone=True),
1119    description=Periodicity.AnalyzeTable.__doc__.Obj.Arguments[4].Description,
1120    arcGISDisplayName=_(u'Normalization field'),
1121    arcGISParameterDependencies=[u'table'])
1122
1123CopyArgumentMetadata(Periodicity.AnalyzeTable, u'aggregationMethod', Periodicity.AnalyzeArcGISTable, u'aggregationMethod')
1124
1125AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'where',
1126    typeMetadata=SQLWhereClauseTypeMetadata(canBeNone=True),
1127    description=_(
1128u"""SQL WHERE clause expression that specifies the subset of rows to
1129use. If this parameter is not provided, all of the rows will be used.
1130
1131The exact syntax of this expression depends on the type of feature
1132class you're using. ESRI recommends you reference fields using the
1133following syntax:
1134
1135* For shapefiles, ArcInfo coverages, or feature classes stored in file
1136  geodatabases, ArcSDE geodatabases, or ArcIMS, enclose field names in
1137  double quotes: "MY_FIELD"
1138
1139* For feature classes stored in personal geodatabases, enclose field
1140  names in square brackets: [MY_FIELD].
1141"""),
1142    arcGISDisplayName=_(u'Where clause'),
1143    arcGISParameterDependencies=[u'table'])
1144
1145CopyArgumentMetadata(Periodicity.AnalyzeTable, u'binWidth', Periodicity.AnalyzeArcGISTable, u'binWidth')
1146CopyArgumentMetadata(Periodicity.AnalyzeTable, u'timeUnits', Periodicity.AnalyzeArcGISTable, u'timeUnits')
1147CopyArgumentMetadata(Periodicity.AnalyzeTable, u'yAxisLabel', Periodicity.AnalyzeArcGISTable, u'yAxisLabel')
1148CopyArgumentMetadata(Periodicity.AnalyzeTable, u'scaleFactor', Periodicity.AnalyzeArcGISTable, u'scaleFactor')
1149CopyArgumentMetadata(Periodicity.AnalyzeTable, u'samplingPeriod', Periodicity.AnalyzeArcGISTable, u'samplingPeriod')
1150CopyArgumentMetadata(Periodicity.AnalyzeTable, u'maxPeriod', Periodicity.AnalyzeArcGISTable, u'maxPeriod')
1151CopyArgumentMetadata(Periodicity.AnalyzeTable, u'periodogramMethod', Periodicity.AnalyzeArcGISTable, u'periodogramMethod')
1152CopyArgumentMetadata(Periodicity.AnalyzeTable, u'oversamplingFactor', Periodicity.AnalyzeArcGISTable, u'oversamplingFactor')
1153CopyArgumentMetadata(Periodicity.AnalyzeTable, u'outputFile', Periodicity.AnalyzeArcGISTable, u'outputFile')
1154CopyArgumentMetadata(Periodicity.AnalyzeTable, u'title', Periodicity.AnalyzeArcGISTable, u'title')
1155CopyArgumentMetadata(Periodicity.AnalyzeTable, u'width', Periodicity.AnalyzeArcGISTable, u'width')
1156CopyArgumentMetadata(Periodicity.AnalyzeTable, u'dpi', Periodicity.AnalyzeArcGISTable, u'dpi')
1157CopyArgumentMetadata(Periodicity.AnalyzeTable, u'fontSize', Periodicity.AnalyzeArcGISTable, u'fontSize')
1158CopyArgumentMetadata(Periodicity.AnalyzeTable, u'dayOfYearPlot', Periodicity.AnalyzeArcGISTable, u'dayOfYearPlot')
1159CopyArgumentMetadata(Periodicity.AnalyzeTable, u'moonPhasePlot', Periodicity.AnalyzeArcGISTable, u'moonPhasePlot')
1160CopyArgumentMetadata(Periodicity.AnalyzeTable, u'timeOfDayPlot', Periodicity.AnalyzeArcGISTable, u'timeOfDayPlot')
1161
1162AddArgumentMetadata(Periodicity.AnalyzeArcGISTable, u'overwriteExisting',
1163    typeMetadata=BooleanTypeMetadata(),
1164    description=_(
1165u"""If True, the output plot file will be overwritten, if it exists.
1166If False, a ValueError will be raised if the output plot file
1167exists."""),
1168    initializeToArcGISGeoprocessorVariable=u'OverwriteOutput')
1169
1170###############################################################################
1171# Names exported by this module
1172###############################################################################
1173
1174__all__ = ['Periodicity']
Note: See TracBrowser for help on using the browser.