NCCOOS Trac Projects: Top | Web | Platforms | Processing | Viz | Sprints | Sandbox | (Wind)

root/raw2proc/trunk/raw2proc/raw2proc.py

Revision 490 (checked in by haines, 12 years ago)

update both proc_cr1000_ctd and proc_sbe37_ctd to recover more data for b2

  • Property svn:executable set to
Line 
1 #!/usr/bin/env python
2 # Last modified:  Time-stamp: <2012-05-01 11:58:23 haines>
3 """Process raw data to monthly netCDF data files
4
5 This module processes raw ascii- or binary-data from different NCCOOS
6 sensors (ctd, adcp, waves-adcp, met) based on manual or automated
7 operation.  If automated processing, add raw data (level0) from all
8 active sensors to current month's netcdf data files (level1) with the
9 current configuration setting.  If manual processing, determine which
10 configurations to use for requested platform, sensor, and month.
11
12 :Processing steps:
13   0. raw2proc auto or manual for platform, sensor, month
14   1. list of files to process
15   2. parse data
16   3. create, update netcdf
17
18   to-do
19   3. qc (measured) data
20   4. process derived data (and regrid?)
21   5. qc (measured and derived) data flags
22
23 """
24
25 __version__ = "v0.1"
26 __author__ = "Sara Haines <sara_haines@unc.edu>"
27
28 import sys
29 import os
30 import re
31 import traceback
32
33 # for production use:
34 # defconfigs='/home/haines/nccoos/raw2proc'
35 # for testing use:
36 # defconfigs='/home/haines/nccoos/test/r2p'
37
38 # define config file location to run under cron
39 defconfigs='/opt/env/haines/dataproc/raw2proc'
40
41 import numpy
42
43 from procutil import *
44 from ncutil import *
45
46 REAL_RE_STR = '\\s*(-?\\d(\\.\\d+|)[Ee][+\\-]\\d\\d?|-?(\\d+\\.\\d*|\\d*\\.\\d+)|-?\\d+)\\s*'
47 NAN_RE_STR = '[Nn][Aa][Nn]'
48
49 def load_data(inFile):
50     lines=None
51     if os.path.exists(inFile):
52         f = open(inFile, 'r')
53         lines = f.readlines()
54         f.close()
55         if len(lines)<=0:
56             print 'Empty file: '+ inFile           
57     else:
58         print 'File does not exist: '+ inFile
59     return lines
60
61 def import_parser(name):
62     mod = __import__('parsers')
63     parser = getattr(mod, name)
64     return parser
65
66 def import_processors(mod_name):
67     mod = __import__(mod_name)
68     parser = getattr(mod, 'parser')
69     creator = getattr(mod, 'creator')
70     updater = getattr(mod, 'updater')
71     return (parser, creator, updater)
72    
73
74 def get_config(name):
75     """Usage Example >>>sensor_info = get_config('bogue_config_20060918.sensor_info')"""
76     components = name.split('.')
77     mod = __import__(components[0])
78     for comp in components[1:]:
79         attr = getattr(mod, comp)
80     return attr
81
82 def get_config_dates(pi):
83     """ Get datetime of both start and end setting within config file
84
85     Example
86     -------
87         >>> pi = get_config(cn+'.platform_info')
88         >>> (config_start_dt, config_end_dt) = get_config_dates(pi)
89
90     """
91     now_dt = datetime.utcnow()
92     now_dt.replace(microsecond=0)
93     if pi['config_start_date']:
94         config_start_dt = filt_datetime(pi['config_start_date'])
95     elif pi['config_start_date'] == None:
96         config_start_dt = now_dt
97     if pi['config_end_date']:
98         config_end_dt = filt_datetime(pi['config_end_date'])
99     elif pi['config_end_date'] == None:
100         config_end_dt = now_dt
101     return (config_start_dt, config_end_dt)
102
103 def find_configs(platform, yyyy_mm, config_dir=''):
104     """Find which configuration files for specified platform and month
105
106     :Parameters:
107        platform : string
108            Platfrom id to process (e.g. 'bogue')
109        yyyy_mm : string
110            Year and month of data to process (e.g. '2007_07')
111
112     :Returns:
113        cns : list of str
114            List of configurations that overlap with desired month
115            If empty [], no configs were found
116     """
117     import glob
118     # list of config files based on platform
119     configs = glob.glob(os.path.join(config_dir, platform + '_config_*.py'))
120     configs.sort()
121     # determine when month starts and ends
122     (prev_month, this_month, next_month) = find_months(yyyy_mm)
123     month_start_dt = this_month
124     month_end_dt = next_month - timedelta(seconds=1)
125     # print month_start_dt; print month_end_dt
126     #
127     cns = []
128     for config in configs:
129         cn = os.path.splitext(os.path.basename(config))[0]
130         pi = get_config(cn+'.platform_info')
131         (config_start_dt, config_end_dt) = get_config_dates(pi)
132         if (config_start_dt <= month_start_dt or config_start_dt <= month_end_dt) and \
133                (config_end_dt >= month_start_dt or config_end_dt >= month_end_dt):
134             cns.append(cn)
135     return cns
136
137
138 def find_active_configs(config_dir=defconfigs):
139     """Find which configuration files are active
140
141     :Returns:
142        cns : list of str
143            List of configurations that overlap with desired month
144            If empty [], no configs were found
145     """
146     import glob
147     # list of all config files
148     configs = glob.glob(os.path.join(config_dir, '*_config_*.py'))
149     cns = []
150     for config in configs:
151         # datetime from filename
152         cn = os.path.splitext(os.path.basename(config))[0]
153         pi = get_config(cn+'.platform_info')
154         if pi['config_end_date'] == None:
155             cns.append(cn)
156     return cns
157
158
159 def uniqify(seq):
160     seen = {}
161     result = []
162     for item in seq:
163         # in old Python versions:
164         # if seen.has_key(item)
165         # but in new ones:
166         if item in seen: continue
167         seen[item] = 1
168         result.append(item)
169     return result
170                                                        
171
172 def get_all_platforms(config_dir=defconfigs):
173     """Get all platform ids
174
175     :Returns:
176        pids : list of str
177            Sorted list of all the platforms
178     """
179     import glob
180     # list of all config files
181     configs = glob.glob(os.path.join(config_dir, '*_config_*.py'))
182     configs.sort()
183     pids = []
184     for config in configs:
185         # datetime from filename
186         cn = os.path.splitext(os.path.basename(config))[0]
187         pi = get_config(cn+'.platform_info')
188         if pi['id']:
189             pids.append(pi['id'])
190     pids = uniqify(pids)
191     pids.sort()
192     return pids
193
194 def get_all_packages(platform, config_dir=defconfigs):
195     """Get all package ids -- all defined packages in sensor_info{} from all configs for the platform
196
197     :Returns:
198        sids : list of str
199            Sorted list of all the sensor ids for package
200     """
201     import glob
202     # list of all config files
203     configs = glob.glob(os.path.join(config_dir, platform + '_config_*.py'))
204     configs.sort()
205     #
206     sids = []
207     for config in configs:
208         cn = os.path.splitext(os.path.basename(config))[0]
209         pi = get_config(cn+'.platform_info')
210         sids.extend(list(pi['packages']))
211     sids = uniqify(sids)
212     sids.sort()
213     return sids
214
215 def get_all_platform_configs(platform, config_dir=defconfigs):
216     """Get all the config files for a platform
217
218     :Returns:
219        cns : list of config names
220            Sorted list of all the sensor ids for package
221     """
222     import glob
223     # list of all config files
224     configs = glob.glob(os.path.join(config_dir, platform + '_config_*.py'))
225     configs.sort()
226     #
227     cns = []
228     for config in configs:
229         cn = os.path.splitext(os.path.basename(config))[0]
230         cns.append(cn)
231     return cns
232
233 def get_config_packages(cn):
234     """ Get active packages set in platform_info{} from specific config file
235
236     :Returns:
237        sids : list of str
238            Sorted (default) or unsorted list of all the sensor ids for package
239            If empty [], no platform ids were found
240     """
241     pi = get_config(cn+'.platform_info')
242     sids = list(pi['packages'])
243     return sids
244
245 def list_months(dts, dte):
246     """ list of datetimes for all months inclusively within given date range
247    
248     """
249     lom = []
250     if type(dts) == type(dte) == type(datetime.utcnow()) and dts <= dte:
251         years = range(dts.year,dte.year+1)
252         for yyyy in years:
253             if yyyy > dts.year:
254                 a = 1
255             else:
256                 a = dts.month
257             if yyyy < dte.year:
258                 b = 12
259             else:
260                 b = dte.month
261             months = range(a, b+1)
262             for mm in months:
263                 lom.append(datetime(yyyy, mm, 1).strftime('%Y_%m'))
264     else:
265         print "list_months requires two inputs type datetime.datetime and dts<dte"
266     return lom
267    
268
269 def create_spin_list(plats, packs, dates, config_dir=defconfigs):
270     """ create list of params needed to run manual() mutiple ways
271
272     :Returns:
273        spin_list : list of three-tuple each tuple with form (platform, package, yyyy_mm)
274
275     Notes
276     -----
277
278     1. plats -- 'ALL' or ['b1', 'b2']
279     2. packs -- 'ALL' or ['ctd1', 'ctd2']
280     3. dates -- 'ALL' or ['2011_11', '2011_12'] or [dt.datetime(2006,1,1), dt.nowutc()]
281
282     For each platform determin packages for given dates
283     also a good way to get listing platforms and packages for specified dates
284
285     """
286     result = []
287     platforms = []
288     if type(plats) == str:
289         if plats.upper() == 'ALL':
290             platforms = get_all_platforms()
291         else:
292             platforms = [plats] # make one platform iterable
293        
294     print ' Expanded lists for creating spin_list:'
295     print ' ...  platform ids : %s' % platforms
296
297     for platform in platforms:
298         if len(platforms)>1:
299             print '------------------------------------'
300             print ' ... ... platform : %s ' % platform
301         packages = []
302         if type(packs) == str:
303             if packs.upper() == 'ALL':
304                 packages = get_all_packages(platform)
305             else:
306                 packages = [packs] # make one package iterable
307
308         print ' ... ... packages : %s' % packages
309         for package in packages:
310             # dates is a string 'ALL' or format 'YYYY_MM'
311             months = []
312             if type(dates) == str:
313                 if dates.upper() == 'ALL':
314                     cns = get_all_platform_configs(platform)
315                     months = []
316                     for cn in cns:
317                         pi = get_config(cn+'.platform_info')
318                         (dts, dte) = get_config_dates(pi)
319                         if package in pi['packages']:
320                             months.extend(list_months(dts, dte))
321                 else:
322                     months = [dates] # make on date iterable
323             # dates is a list
324             if type(dates) == type([]):
325                 # if dates has two datetime types
326                 if type(dates[0]) == type(dates[1]) == type(datetime.utcnow()):
327                     dt1, dt2 = dates
328                     cns = get_all_platform_configs(platform)
329                     months = []
330                     for cn in cns:
331                         pi = get_config(cn+'.platform_info')
332                         (dts, dte) = get_config_dates(pi)
333
334                         if dts<=dt1 and dt1<=dte: a = dt1
335                         elif dt1<=dts and dt1<=dte: a = dts
336
337                         if dts<dt2 and dt2<=dte: b = dt2
338                         elif dts<dt2 and dte<=dt2: b = dte
339
340                         if dte<dt1 or dt2<dts:
341                             continue
342                         # list only months that are in configs for wide date range
343                         if package in pi['packages']:
344                             months.extend(list_months(a,b))
345                 # else if string in list
346                 elif type(dates[0]) == str:
347                     months = dates
348             print ' ... ...   months : %s' % months
349             for month in months:
350                 # print '... ... %s %s %s' % (platform, package, month)
351                 result.append((platform, package, month))
352                
353     return result
354                
355 def find_raw(si, yyyy_mm):
356     """Determine which list of raw files to process for month """
357     import glob
358
359     months = find_months(yyyy_mm)
360     # list all the raw files in prev-month, this-month, and next-month
361     all_raw_files = []
362     m = re.search('\d{4}_\d{2}$', si['raw_dir'])
363     if m:
364         # look for raw_file_glob in specific directory ending in YYYY_MM
365         # but look no further. 
366         gs = os.path.join(si['raw_dir'], si['raw_file_glob'])
367         all_raw_files.extend(glob.glob(gs))
368     else:
369         # no YYYY_MM at end of raw_dir then look for files
370         # in prev-month, this-month, and next-month
371         for mon in months:
372             mstr = mon.strftime('%Y_%m')
373             gs = os.path.join(si['raw_dir'], mstr, si['raw_file_glob'])
374             all_raw_files.extend(glob.glob(gs))
375            
376     all_raw_files.sort()
377        
378     #
379     dt_start = si['proc_start_dt']-timedelta(days=1)
380     dt_end = si['proc_end_dt']+timedelta(days=1)
381     raw_files = []; raw_dts = []
382     # compute datetime for each file
383     for fn in all_raw_files:
384         (fndt, granularity) = filt_datetime(os.path.basename(fn), gran=True)
385         if granularity == 4:
386             # change dt_start to before monthly filename filt_datetime() date
387             # for filenames with just YYYY_MM or YYYYMM add or substract 30 days to
388             # see if it falls within config range.  It won't hurt to add names to files
389             # parsed.
390             dt_start = si['proc_start_dt']-timedelta(days=31)
391             # print dt_start
392         if fndt:
393             if dt_start <= fndt <= dt_end or m:
394                 raw_files.append(fn)
395                 raw_dts.append(fndt)
396     return (raw_files, raw_dts)
397
398 def which_raw(pi, raw_files, dts):
399     """Further limit file names based on configuration file timeframe """
400     (config_start_dt, config_end_dt) = get_config_dates(pi)
401
402     for idx, fn in enumerate(raw_files):
403         (fndt, granularity) = filt_datetime(os.path.basename(fn), gran=True)
404         if granularity == 4:
405             if fndt < config_start_dt:
406                 dts[idx] = config_start_dt
407             if fndt > config_end_dt:
408                 dts[idx] = config_end_dt
409
410     new_list = [raw_files[i] for i in range(len(raw_files)) \
411                      if config_start_dt <= dts[i] <= config_end_dt]
412
413     if not new_list:
414         new_list = [raw_files[i] for i in range(len(raw_files)) \
415                     if dts[i] <= config_end_dt]
416        
417     return new_list
418        
419
420 def raw2proc(proctype, platform=None, package=None, yyyy_mm=None):
421     """
422     Process data either in auto-mode or manual-mode
423
424     If auto-mode, process newest data for all platforms, all
425     sensors. Otherwise in manual-mode, process data for specified
426     platform, sensor package, and month.
427
428     :Parameters:
429        proctype : string
430            'auto' or 'manual' or 'spin'
431
432        platform : string
433            Platfrom id to process (e.g. 'bogue')
434        package : string
435            Sensor package id to process (e.g. 'adcp')
436        yyyy_mm : string
437            Year and month of data to process (e.g. '2007_07')
438
439     Examples
440     --------
441     >>> raw2proc(proctype='manual', platform='bogue', package='adcp', yyyy_mm='2007_06')
442     >>> raw2proc('manual', 'bogue', 'adcp', '2007_06')
443
444     Spin
445     ----
446     platform can be list of platforms or 'ALL'
447     package can be list packages or 'ALL'
448     yyyy_mm can be list of months, or datetime range
449    
450     >>> raw2proc('spin', ['b1','b2'], ['ctd1', 'ctd2'], ['2011_11'])
451     >>> raw2proc('spin', ['b1','b2'], ['ctd1', 'ctd2'], 'ALL')
452     >>> raw2proc('spin', ['b1','b2'], ['ctd1', 'ctd2'], [datetime(2011,11,1), datetime(2012,4,1)])
453     >>> raw2proc('spin', ['b1','b2'], 'ALL', 'ALL')
454    
455     Not a good idea but this will reprocess all the data from level0
456     >>> raw2proc('spin', 'ALL', 'ALL', 'ALL')
457          
458     """
459     print '\nStart time for raw2proc: %s\n' % start_dt.strftime("%Y-%b-%d %H:%M:%S UTC")
460
461     if proctype == 'auto':
462         print 'Processing in auto-mode, all platforms, all packages, latest data'
463         auto()
464     elif proctype == 'manual':
465         if platform and package and yyyy_mm:
466             print 'Processing in manual-mode ...'
467             print ' ...  platform id : %s' % platform
468             print ' ... package name : %s' % package
469             print ' ...        month : %s' % yyyy_mm
470             print ' ...  starting at : %s' % start_dt.strftime("%Y-%m-%d %H:%M:%S UTC")
471             manual(platform, package, yyyy_mm)
472         else:
473             print 'raw2proc: Manual operation requires platform, package, and month'
474             print "   >>> raw2proc(proctype='manual', platform='bogue', package='adcp', yyyy_mm='2007_07')"
475     elif proctype == 'spin':
476         if platform and package and yyyy_mm:
477             print 'Processing in spin-mode ...'
478             print ' ...  platform ids : %s' % platform
479             print ' ... package names : %s' % package
480             print ' ...        months : %s' % yyyy_mm
481             print ' ...   starting at : %s' % start_dt.strftime("%Y-%m-%d %H:%M:%S UTC")
482             spin_list = create_spin_list(platform, package, yyyy_mm)
483             spin(spin_list)
484         else:
485             print "raw2proc: Spin operation requires platform(s), package(s), and month(s)"
486             print "   >>> raw2proc(proctype='spin', platform='b1', package='ALL', yyyy_mm='ALL')"
487             print "   >>> raw2proc(proctype='spin', platform='ALL', package='met', yyyy_mm='2011_11')"
488             print "   >>> raw2proc('spin', ['b1','b2'], ['ctd1', 'ctd2'], [datetime(2011,11,1), datetime(2012,4,1)])"
489
490     else:
491         print 'raw2proc: requires either auto or manual operation'
492
493
494 def auto():
495     """Process all platforms, all packages, latest data
496
497     Notes
498     -----
499    
500     1. determine which platforms (all platforms with currently active
501        config files i.e. config_end_date is None
502     2. for each platform
503          get latest config
504          for each package
505            (determine process for 'latest' data) copy to new area when grabbed
506            parse recent data
507            yyyy_mm is the current month
508            load this months netcdf, if new month, create this months netcdf
509            update modified date and append new data in netcdf
510            
511     """
512     yyyy_mm = this_month()
513     months = find_months(yyyy_mm)
514     month_start_dt = months[1]
515     month_end_dt = months[2] - timedelta(seconds=1)
516
517     configs = find_active_configs(config_dir=defconfigs)
518     if configs:
519         # for each configuration
520         for cn in configs:
521             print ' ... config file : %s' % cn
522             pi = get_config(cn+'.platform_info')
523             asi = get_config(cn+'.sensor_info')
524             platform = pi['id']
525             (pi['config_start_dt'], pi['config_end_dt']) = get_config_dates(pi)
526
527             # for each sensor package
528             for package in asi.keys():
529                 try: # if package files, try next package
530                     print ' ... package name : %s' % package
531                     si = asi[package]
532                     si['proc_filename'] = '%s_%s_%s.nc' % (platform, package, yyyy_mm)
533                     ofn = os.path.join(si['proc_dir'], si['proc_filename'])
534                     si['proc_start_dt'] = month_start_dt
535                     si['proc_end_dt'] = month_end_dt
536                     if os.path.exists(ofn):
537                         # get last dt from current month file
538                         (es, units) = nc_get_time(ofn)
539                         last_dt = es2dt(es[-1])
540                         # if older than month_start_dt use it instead to only process newest data
541                         if last_dt>=month_start_dt:
542                             si['proc_start_dt'] = last_dt
543
544                     (raw_files, raw_dts) = find_raw(si, yyyy_mm)
545                     raw_files = which_raw(pi, raw_files, raw_dts)
546                     if raw_files:
547                         process(pi, si, raw_files, yyyy_mm)
548                     else:
549                         print ' ... ... NOTE: no new raw files found'
550
551                     # update latest data for SECOORA commons
552                     if 'latest_dir' in si.keys():
553                         # print ' ... ... latest : %s ' % si['latest_dir']
554                         proc2latest(pi, si, yyyy_mm)
555
556                     if 'csv_dir' in si.keys():
557                         proc2csv(pi, si, yyyy_mm)
558                 except:
559                     traceback.print_exc()
560     #
561     else:
562         print ' ... ... NOTE: No active platforms'
563
564 def spin(spin_list):
565     """ wrapper to run manual() for multiple months"""
566     for item in spin_list:
567         platform, package, yyyy_mm = item
568         raw2proc('manual',platform, package, yyyy_mm)
569
570 def manual(platform, package, yyyy_mm):
571     """Process data for specified platform, sensor package, and month
572
573     Notes
574     -----
575    
576     1. determine which configs
577     2. for each config for specific platform
578            if have package in config
579                which raw files
580     """
581     months = find_months(yyyy_mm)
582     month_start_dt = months[1]
583     month_end_dt = months[2] - timedelta(seconds=1)
584    
585     configs = find_configs(platform, yyyy_mm, config_dir=defconfigs)
586
587     if configs:
588         # for each configuration
589         for index in range(len(configs)):
590             cn = configs[index]
591             print ' ... config file : %s' % cn
592             pi = get_config(cn+'.platform_info')
593             (pi['config_start_dt'], pi['config_end_dt']) = get_config_dates(pi)
594             # month start and end dt to pi info
595             asi = get_config(cn+'.sensor_info')
596             if package in pi['packages']:
597                 si = asi[package]
598                 if si['utc_offset']:
599                     print ' ... ... utc_offset : %g (hours)' % si['utc_offset']
600                 si['proc_start_dt'] = month_start_dt
601                 si['proc_end_dt'] = month_end_dt
602                 si['proc_filename'] = '%s_%s_%s.nc' % (platform, package, yyyy_mm)
603                 ofn = os.path.join(si['proc_dir'], si['proc_filename'])
604                 (raw_files, raw_dts) = find_raw(si, yyyy_mm)
605                 print raw_files
606                 print raw_dts
607                 raw_files = which_raw(pi, raw_files, raw_dts)
608                 print raw_files
609                 print raw_dts
610                 # remove any previous netcdf file (platform_package_yyyy_mm.nc)
611                 if index==0  and os.path.exists(ofn):
612                     os.remove(ofn)
613                 # this added just in case data repeated in data files
614                 if os.path.exists(ofn):
615                     # get last dt from current month file
616                     (es, units) = nc_get_time(ofn)
617                     last_dt = es2dt(es[-1])
618                     # if older than month_start_dt use it instead to only process newest data
619                     if last_dt>=month_start_dt:
620                         si['proc_start_dt'] = last_dt
621
622                 if raw_files:
623                     process(pi, si, raw_files, yyyy_mm)
624                 else:
625                     print ' ... ... NOTE: no raw files found for %s %s for %s' % (package, platform, yyyy_mm)
626                
627             else:
628                 print ' ... ... NOTE: %s not operational on %s for %s' % (package, platform, yyyy_mm)               
629     else:
630         print ' ... ... ... NOTE: %s not operational for %s' % (platform, yyyy_mm)
631    
632
633 def process(pi, si, raw_files, yyyy_mm):
634     # tailored data processing for different input file formats and control over output
635     (parse, create, update) = import_processors(si['process_module'])
636     for fn in raw_files:
637         # sys.stdout.write('... %s ... ' % fn)
638         # attach file name to sensor info so parser can use it, if needed
639         si['fn'] = fn
640         lines = load_data(fn)
641         if lines:
642             data = parse(pi, si, lines)
643             # determine which index of data is within the specified timeframe (usually the month)
644             n = len(data['dt'])
645             data['in'] = numpy.array([False for i in range(n)])
646
647             for index, val in enumerate(data['dt']):
648                 if val>=pi['config_start_dt'] and \
649                        val>=si['proc_start_dt'] and \
650                        val<=si['proc_end_dt'] and \
651                        val<=pi['config_end_dt']:
652                     data['in'][index] = True
653                    
654             # if any records are in the month then write to netcdf
655             if data['in'].any():
656                 sys.stdout.write(' ... %s ... ' % fn)
657                 sys.stdout.write('%d\n' % len(data['in'].nonzero()[0]))
658                 ofn = os.path.join(si['proc_dir'], si['proc_filename'])
659                 # update or create netcdf
660                 if os.path.exists(ofn):
661                     ut = update(pi,si,data)
662                     nc_update(ofn, ut)
663                 else:
664                     ct = create(pi,si,data)
665                     nc_create(ofn, ct)
666         else:
667             # if no lines, file was empty
668             print " ... skipping file %s" % (fn,)
669
670    
671 # globals
672 start_dt = datetime.utcnow()
673 start_dt.replace(microsecond=0)
674
675 if __name__ == "__main__":
676     import optparse
677     raw2proc('auto')
678
679     # for testing
680     # proctype='manual'; platform='bogue'; package='adcp'; yyyy_mm='2007_07'
681     # raw2proc(proctype='manual', platform='bogue', package='adcp', yyyy_mm='2007_07')
Note: See TracBrowser for help on using the browser.