; Copyright 2006 The Johns Hopkins University/Applied Physics Laboratory.
; All rights reserved.
;-------------------------------------------------------------
; $Id: read_ncdf.pro,v 1.5 2012/05/07 17:58:02 romeog1 Exp $
;+
; NAME:
; READ_NCDF
;
; PURPOSE:
; Open a netCDF file and read data from it. The data is
; returned as a structure whose tag names are the names of
; the variables with blanks etc. replaced. If no variables
; are specified with the VARIABLES keyword, only dimensional
; information is returned. All variables are loaded by default.
; Other keyword options include OFFSET, COUNT, STRIDE,
; NO_DIMENSIONS, NO_STRUCT, DIMNAMES, VARNAMES, VARDIMS, ATTRIBUTES.
; Thus, this program includes ncdump functionality.
;
; If no filename is given, a file selection dialog is opened with the
; default mask '*.L*,*.nc*,*.NC*'. The name of the selected file is
; returned in the TRUENAME keyword. A file selection dialog also appears
; when the file cannot be found (see OPEN_FILE.PRO). This can be turned
; off with the NO_DIALOG keyword. The VERBOSE keyword provides
; information while analyzing and reading the file.
;
; AUTHOR:
; Dr. Martin Schultz
; Max-Planck-Institut fuer Meteorologie
; Bundesstr. 55, D-20146 Hamburg
; email: martin.schultz@dkrz.de
;
; CATEGORY:
; File I/O
;
; CALLING SEQUENCE:
; READ_NCDF, result, filename=<string>, truename=<string>,
; variables=<stringarray>, all=<flag>, varnames=<stringarray>,
; vardimid=<structure>, vardims=<structure>, attributes=<structure>,
; count=<integerarray>, offset=<integerarray>, stride=<integerarray>,
; dimnames=<stringarray>, dims=<longarray>, no_dimensions=<flag>,
; no_struct=<flag>, no_dialog=<flag>, verbose=<flag>, title=<string>
;
; ARGUMENTS:
; RESULT(out) -> a structure containing all the variable data
; from the netCDF file. If only one variable is specified
; and the NO_STRUCT keyword set, then RESULT will be an
; array instead of a structure.
; Note, that the COUNT, OFFSET,
; and STRIDE keywords can affect the size of RESULT.
; RESULT is set to -1L if an error occurs before the structure
; has been built. You can use CHKSTRU.PRO to test for this.
;
; KEYWORD PARAMETERS:
; FILENAME(in) -> the name of the netCDF file to be opened.
; NCDF_READ uses OPEN_FILE to check the validity of
; the file first. You can specify a search mask
; instead of a filename in which case a file selection
; dialog is displayed (unless you set the NO_DIALOG
; keyword). The TRUENAME keyword contains the name
; of the selected file or an empty string if the
; file selection was canceled.
;
; TRUENAME(out) -> the (fully qualified) name of the file selected
; with the file selection dialog or an unaltered copy
; of FILENAME if FILENAME is a valid filename.
;
; VARIABLES(in) -> a string array containing the names of variables
; for which data shall be read. Default is to read
; only the dimensional information from the file.
; (Currently) no warning is issued if a variable is not in the file.
;
; ALL(in) -> set this keyword to load all variables stored in the
; netCDF file. Generally, you cannot usethis keyword together with
; COUNT, OFFSET, and STRIDE.
;
; ASPOINTER(in) -> set this keyword to return the data and
; attributes as structures of pointers rather than data
; structures. This is more efficient if you read large
; data sets (factor of 2 easily) and facilitates
; manipulation of attribute values. If you use this
; option, make sure to free the pointers at some point.
;
; VARNAMES(out) -> a string array containing the names of all variables
; as stored in the file. Note, that the tag names of e.g. the
; result structure are filtered with the Valid_TagName function.
;
; VARDIMID(out) -> a structure with integer arrays containing the
; dimension ID's for all variables. See also VARDIMS which returns
; the dimensions themselves.
;
; VARDIMS(out) -> a structure with integer arrays containing the
; dimensions for all variables in the netCDF file. These are not
; kept in sync with potential COUNT, OFFSET, and STRIDE values,
; but reflect the data sizes as stored in the file.
;
; ATTRIBUTES(out) -> a structure holding the variable and global
; attributes stored in the netCDF file (global attributes
; are stored in tag name GLOBAL).
;
; COUNT(in) -> an integer array containing the number of values to
; be read for each dimension of the variables. Mapping of the
; COUNT dimensions to the variable dimensions is achieved via
; the first entry in the VARIABLES list and the COUNT parameter
; will be applied to all variables that have that dimension.
; Example: The first variable has dimensions LON, LAT, LEV,
; the second variable has dimensions LON, LAT, and the third
; variable has LAT, LEV. A COUNT of [40,20,10] would lead to
; result dimensions of [40,20,10], [40,20], and [20,10].
;
; OFFSET(in) -> an integer array containing the offsets for each
; dimension of the variables to be read. Dimension mapping
; is the same as for COUNT.
;
; STRIDE(in) -> an integer array containing the stride for each
; dimension of the variables to be read. Dimension mapping
; is the same as for COUNT.
;
; DIMNAMES(out) -> a string array containing the names of the
; dimensions stored in the netCDF file.
;
; DIMS(out) -> a long array containing the dimension values. Purely
; for convenience. Use VARDIMS to retrieve the dimensions of
; the variables.
;
; TITLE(in) -> A title for the file selection dialog if an
; incomplete or incorrect filename is specified. This
; keyword is ignored if the no_dialog keyword is set.
;
; NO_DIMENSIONS(in) -> set this keyword if you do not want to store
; the dimensional variables from the file in the result structure.
; DIMNAMES and DIMS will still be retrieved.
;
; NO_STRUCT(in) -> if only one variable is selected with the
; VARIABLE keyword, you can set this keyword to return only
; the data for this variable as an array. This keyword implies
; the functionality of NO_DIMENSIONS.
;
; NO_DIALOG(in) -> set this keyword if you do not want interactive
; behaviour when a file mask is given instead of a filename or
; when the specified file does not exist.
;
; VERBOSE(in) -> set this keyword to get detailed information while
; reading the netCDF file.
;
; SUBROUTINES:
; STRREPL (function)
; Valid_TagName : replaces invalid characters in variable names so that
; a structure can be built.
;
; ncdf_attributes : retrieves global and variable attributes from netcdf
; file and stores them as structure.
;
; ncdf_dimensions : retrieves size and name for all dimensions in netcdf file.
;
; ncdf_varnames : retrieves names and dimension information for all variables
; in the netCDF file.
;
; ncdf_mapdims : map dimension indices for COUNT, OFFSET, and STRIDE with
; dimensions of first selected variable.
;
; ncdf_TestDimensions : compute the COUNT, OFFSET, and STRIDE vectors that
; must be applied for retrieving the data of one variable.
; REQUIREMENTS:
; uses OPEN_FILE and STRREPL.
;
; NOTES:
; Correct handling of dimensional information requires that the variables
; storing the dimension values have the same name as the dimensions
; themselves - a common feature in most netCDF standards.
;
; I am still working on a netcdf file object which will be even
; more powerful. At some point ncdf_read will only be a
; procedure interface to this objec!
;
; EXAMPLE:
; ncdf_read,result,/All
; ; plot ozone vs. temperature
; plot,result.temperature,result.ozone
;
; MODIFICATION HISTORY:
; mgs, 18 Sep 1999: VERSION 1.00
; mgs, 29 Feb 2000: - added variables keyword
; - added CATCH error handler
; mgs, 21 Mar 2000: - bug fix for tag names
; mgs, 09 May 2000: VERSION 2.00
; - now only reads dimensions as default
; - added ALL keyword to compensate
; - returns dimnames and attributes
; (makes ncdf_detail obsolete)
; - added COUNT, OFFSET and STRIDE keywords
; - added NO_DIMENSIONS and NO_DIALOG
; keywords and more
; mgs, 22 Aug 2000: - added title keyword
; mgs, 23 Nov 2001: - bug fix: attributes of type CHAR were
; returned as byte arrays.
; mgs, 01 Dec 2001: - added aspointer keyword
; 06 Jun 2003: - fixed variable naming error to convert variables
; beginning with numbers D. Morrison
;
;-
;-------------------------------------------------------------
;
; LICENSE
;
; This software is OSI Certified Open Source Software.
; OSI Certified is a certification mark of the Open Source Initiative.
;
; Copyright � 1999-2000, Martin Schultz, Max-Planck Institut fuer
; Meteorologie, Hamburg
;
; This software is provided "as-is", without any express or
; implied warranty. In no event will the authors be held liable
; for any damages arising from the use of this software.
;
; Permission is granted to anyone to use this software for any
; purpose, including commercial applications, and to alter it and
; redistribute it freely, subject to the following restrictions:
;
; 1. The origin of this software must not be misrepresented; you must
; not claim you wrote the original software. If you use this software
; in a product, an acknowledgment in the product documentation
; would be appreciated, but is not required.
;
; 2. Altered source versions must be plainly marked as such, and must
; not be misrepresented as being the original software.
;
; 3. This notice may not be removed or altered from any source distribution.
;
; For more information on Open Source Software, visit the Open Source
; web site: http://www.opensource.org.
;
;-------------------------------------------------------------
; STRREPL (function)
;
; PURPOSE:
; Replace all occurences of one character in a string
; with another character. The character to be replaced
; can either be given as string of length 1 or as an
; index array containing the character positions
; (see strwhere). This function also works for string arrays.
function strrepl,str,fromchar,tochar, $
IGNORECASE=ignorecase, FOLD_CASE=fold_CASE, $
SILENT=silent
ON_ERROR,2 ; return to caller
; argument testing
if n_params() lt 3 then begin
message,'Usage: strrepl,str,fromchar,tochar[,/IGNORECASE])'
endif
; make working copy of string and convert to a byte array
bstr = byte(string(str))
; fromchar is given as character
if size(fromchar,/TYPE) eq 7 then begin
; ignore case?
if keyword_set(ignorecase) OR keyword_set(fold_case) then begin
; call strrepl recursively w/o the IGNORE_CASE keyword
res1 = strrepl(str,strupcase(fromchar),tochar)
res2 = strrepl(res1,strlowcase(fromchar),tochar)
return,res2
endif else begin
; find all character occurences
; must be a single character - use the first
bfc = (byte(fromchar))[0]
; go and search
w = where(bstr eq bfc,count)
; if not found, return original string
if count eq 0 then return,str
endelse
endif else begin
; fromchar is already an index array
w = long(fromchar)
endelse
; make sure index is in range
test = where(w lt 0 OR w ge n_elements(bstr),tcount)
if tcount gt 0 then begin
IF (keyword_set(silent) EQ 0 ) THEN message,'WARNING: Index out of range!',/Continue
; restrict to valid index values
test = where(w ge 0 AND w lt n_elements(bstr),tcount)
if tcount gt 0 then begin
w = w[test]
endif else begin
; no valid indices: return original string
return,str
endelse
endif
; convert tochar to a byte value
btc = (byte(tochar))[0]
; replace
bstr[w] = btc
; return result as string
return,string(bstr)
end
;=============================================================
; Valid_TagName: replace invalid characters in netCDF variable names
; to allow building of a structure.
function Valid_TagName, arg
; If the name starts with a number, prepend it with an underscore.
if (strpos(arg, '0') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '1') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '2') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '3') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '4') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '5') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '6') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '7') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '8') EQ 0) then arg = strcompress("_"+arg)
if (strpos(arg, '9') EQ 0) then arg = strcompress("_"+arg)
t = strrepl(arg,'-','_')
t = strrepl(t,'+','P')
t = strrepl(t,'$','D')
t = strrepl(t,'*','S')
t = strrepl(t,'&','_')
t = strrepl(t,' ','_')
t = strrepl(t,'@','_')
t = strrepl(t,'.','_')
return, t
end
;=============================================================
; ncdf_attributes: retrieve global or variable attributes from
; netCDF file. If keyword GLOBAL is not set, a variable ID must
; be supplied.
; The result is a structure with all global attributes or all
; attributes for one variable.
function ncdf_attributes, ncid, varid, global=global, verbose=verbose
result = ''
; a little error checking
if n_elements(varid) eq 0 and not keyword_set(global) then begin
message,'Must supply VARID if keyword GLOBAL not set.',/Continue
return, result
endif
; get basic information about netCDF file
nstru = NCDF_INQUIRE(ncid)
; determine number of attributes to be read
if keyword_set(global) then begin
natts = nstru.ngatts
endif else begin
vardesc = NCDF_VARINQ(ncid, varid)
natts = vardesc.natts
endelse
if keyword_set(verbose) then begin
if not keyword_set(global) then begin
print, 'Attributes for variable ',vardesc.name
prefix = ' '
endif else begin
prefix = 'Global attribute'
endelse
endif
for i=0L,natts-1 do begin
if keyword_set(global) then begin
aname = NCDF_ATTNAME(ncid,/GLOBAL,i)
NCDF_ATTGET,ncid, /GLOBAL, aname, avalue
ainfo = NCDF_ATTINQ(ncid, /GLOBAL, aname)
atype = ainfo.datatype
endif else begin
aname = NCDF_ATTNAME(ncid, varid, i)
NCDF_ATTGET, ncid, varid, aname, avalue
atype = size(avalue, /TNAME)
endelse
; take care of IDL bug: CHAR is stored as BYTE
; assume all BYTES are CHARS (bug fixed in IDL5.3)
if (atype eq 'BYTE' OR atype EQ 'CHAR') then $
avalue = string(avalue)
; build or add to result structure
newname = Valid_TagName(aname)
if (i eq 0) then $
result = create_struct( newname, avalue ) $
else $
result = create_struct( result, newname, avalue )
if keyword_set(verbose) then begin
print,prefix+aname+': '+avalue
endif
endfor
return, result
end
;=============================================================
; ncdf_dimensions: retrieve dimension sizes and names from a
; netCDF file.
function ncdf_dimensions, ncid, names=names, verbose=verbose
dims = -1L
names = ''
; get basic information about netCDF file
nstru = NCDF_INQUIRE(ncid)
; print,' ID of unlimited dimension : ',nstru.recdim
; print,'ID of time dimension : ',NCDF_DIMID(ncid, 'time')
dims = lonarr(nstru.ndims)
names = strarr(nstru.ndims)
for i=0L,nstru.ndims-1 do begin
NCDF_DIMINQ,ncid,i,dname,dsize
names[i] = dname
dims[i] = dsize
if keyword_set(verbose) then $
print,'Dimension '+strtrim(i,2)+': ',dname,dsize
endfor
return, dims
end
;=============================================================
; ncdf_varnames: retrieve variable names from a netCDF file.
; this function also returns the variable dimension id's.
function ncdf_varnames, ncid, VarDimId=vardimid
dims = -1L
names = ''
; get basic information about netCDF file
nstru = NCDF_INQUIRE(ncid)
for i=0L,nstru.nvars-1 do begin
vardesc = NCDF_VARINQ(ncid, i)
if keyword_set(verbose) then print,'Variable '+vardesc.name+' Dim-IDs: ' $
+ string(vardesc.dim,format='(12i6)')
if i eq 0 then begin
names = vardesc.name
vardimid = create_struct( Valid_TagName(vardesc.name), vardesc.dim )
endif else begin
names = [ names, vardesc.name ]
vardimid = create_struct( vardimid, Valid_TagName(vardesc.name), vardesc.dim )
endelse
endfor
return, names
end
;=============================================================
; ncdf_mapdims: check compatibility of variable dimensions
; with count, offset, and stride values. The dimensionality
; for each of count, offset, and stride must be same as for
; the variable (the first one asked for) or it can be 0,
; i.e. the respective parameter is undefined.
; If these conditions are met, the variable's vardimid array
; establishes the link between the parameter dimension and the
; physical dimension, otherwise a scalar -1L is returned.
function ncdf_mapdims,varname, vardimid, $
count=count, offset=offset, stride=stride
result = -1L
nvdim = n_elements(vardimid) ; dimensionality of variable
nc = n_elements(count)
no = n_elements(offset)
ns = n_elements(stride)
testc = (nc eq 0 OR nc eq nvdim)
testo = (no eq 0 OR no eq nvdim)
tests = (ns eq 0 OR ns eq nvdim)
testnull = ( (nc > no > ns ) eq 0 )
ok = ( testc AND testo AND tests ) AND not testnull
if ok then result = vardimid
return, result
end
;=============================================================
; ncdf_TestDimensions: check compatibility of variable dimensions
; with count, offset, and stride values. The this... keyword
; return valid entries for these parameters while the original
; parameters remain unchanged.
function ncdf_TestDimensions, ncid, index, dims, mapdims, $
count=count, offset=offset, stride=stride, $
thiscount=thiscount, thisoffset=thisoffset, thisstride=thisstride
; catch,/cancel
result = 0 ; not compatible
; get variable information
vardesc = NCDF_VARINQ(ncid, index)
; create default values
ndims = n_elements(vardesc.dim)
thiscount = dims[vardesc.dim]
thisoffset = lonarr(ndims)
thisstride = lonarr(ndims)+1L
; print,'variable ',vardesc.name
; print,'default thiscount=',thiscount
; print,'default thisoffset=',thisoffset
; print,'default thisstride=',thisstride
for i=0L, n_elements(offset)-1 do begin
w = where(vardesc.dim eq mapdims[i])
; print,'offset dimension ',i,' matches data dimension ',w
if w[0] ge 0 then begin
thisoffset[w] = offset[i] < (thiscount[w]-1)
; print,'new thisoffset=',thisoffset
result = 1
endif
endfor
for i=0L, n_elements(stride)-1 do begin
w = where(vardesc.dim eq mapdims[i])
; print,'stride dimension ',i,' matches data dimension ',w
if w[0] ge 0 then begin
thisstride[w] = stride[i] > 1
; print,'new thisstride=',thisstride
result = 1
endif
endfor
for i=0L, n_elements(count)-1 do begin
w = where(vardesc.dim eq mapdims[i])
; print,'count dimension ',i,' matches data dimension ',w
if w[0] ge 0 then begin
thiscount[w] = count[i] < ( (thiscount[w]-thisoffset[w])/thisstride[w] )
thiscount[w] = thiscount[w] > 1
; print,'new thiscount=',thiscount
result = 1
endif
endfor
return, result
end
FUNCTION RSTRPOS, Expr, SubStr, Pos
ON_ERROR, 2
N = N_PARAMS()
if (n lt 2) then message, 'Incorrect number of arguments.'
; Is expr an array or a scalar? In either case, make a result
; that matches.
if (size(expr, /n_dimensions) eq 0) then result = 0 $
else result = make_array(dimension=size(expr,/dimensions), /INT)
RSubStr = STRING(REVERSE(BYTE(SubStr))) ; Reverse the substring
for i = 0L, n_elements(expr) - 1 do begin
Len = STRLEN(Expr[i])
IF (N_ELEMENTS(Pos) EQ 0) THEN Start=0 ELSE Start = Len - Pos
RString = STRING(REVERSE(BYTE(Expr[i]))) ; Reverse the string
SubPos = STRPOS(RString, RSubStr, Start)
IF SubPos NE -1 THEN SubPos = Len - SubPos - STRLEN(SubStr)
result[i] = SubPos
endfor
RETURN, result
END
;-------------------------------------------------------------
function extract_filename,fullname,filepath=thisfilepath
; determine path delimiter
if (!version.os_family eq 'Windows') then sdel = '\' else sdel = '/'
filename = ''
thisfilepath = ''
retry:
; look for last occurence of sdel and split string fullname
p = rstrpos(fullname,sdel)
; extra Windows test: if p=-1 but fullname contains '/', retry
if (p lt 0 AND strpos(fullname,'/') ge 0) then begin
sdel = '/'
goto,retry
endif
if (p ge 0) then begin
thisfilepath = strmid(fullname,0,p+1)
filename = strmid(fullname,p+1,strlen(fullname)-1)
endif else $
filename = fullname
return,filename
end
;-------------------------------------------------------------
pro open_file,filemask,lun,filename=filename, $
write=write, update=update, $
result=result, $
pickfile=pickfile,title=title,defaultmask=defaultmask, $
no_pickfile=no_pickfile,verbose=verbose, $
F77_Unformatted=F77_Unformatted, $
Swap_Endian=Swap_endian,_EXTRA=e
FORWARD_FUNCTION extract_filename
; fail safe : set result to error condition first
result = -1
; reset error state
message,/reset
filename = ''
if (n_elements(lun) eq 0) then lun = -1
; error check
ON_ERROR,2
if (n_params() lt 2) then begin
message,'Procedure must be called with 2 parameters (FILENAME,ILUN)!'
endif
; ============================================================
; set standard search mask and see if DIALOG_PICKFILE shall
; be called
; ============================================================
if (n_elements(defaultmask) gt 0) then $
stdmask = defaultmask[0] $
else begin
stdmask = '*'
if (strupcase(!version.os_family) eq 'WINDOWS') then stdmask = '*.*'
endelse
if (n_elements(filemask) eq 0) then filemask = stdmask
if (filemask eq '') then filemask = stdmask
; if filemask contains wildcards, always use pickfile dialog
; Abort if NO_PICKFILE is set
if (strpos(filemask,'*') ge 0 OR strpos(filemask,'?') ge 0) then begin
if (keyword_set(NO_PICKFILE)) then begin
message,'Filename must not contain wildcards when '+ $
'NO_PICKFILE option is set!',/CONT
lun = -1 ; yet another error indicator
return
endif
pickfile = 1
endif
; make working copy of filemask (will be overwritten by PICKFILE dialog)
thisfilename = filemask
; ============================================================
; set up parameters for DIALOG_PICKFILE
; ============================================================
if (keyword_set(pickfile)) then begin
; seperate filename from filepath
fname = extract_filename(filemask,filepath=path)
; if filename contains wildcards, put them to filemask and
; set filename to empty string
; if not (pickfile keyword set), then set standard search mask
if (strpos(fname,'*') ge 0 OR strpos(fname,'?') ge 0) then begin
fmask = fname
fname = ''
endif else begin
fmask = stdmask
endelse
; set dialog title
if (n_elements(title) eq 0) then title = 'Choose a file'
; print,'### fname, path=>',fname,'<>',path,'<>',expand_path(path),'<'
; call pickfile dialog
thisfilename = dialog_pickfile(file=fname,path=expand_path(path), $
filter=fmask, $
title=title, $
must_exist=(1 - keyword_set(WRITE)) )
if (thisfilename eq '') then begin ; cancel button pressed (?)
lun = -1 ; yet another error indicator
; note that !error_state.code should be 0
return
endif
endif
; print,'#DEBUG: little_endian = ',little_endian()
; Now try to open the file
if (lun le 0) then get_lun,lun
on_ioerror, openerr
if (keyword_set(WRITE)) then $
openw,lun,thisfilename,F77=F77_Unformatted, $
Swap_Endian=Swap_Endian,_EXTRA=e $
else if keyword_set(update) then $
openu,lun,thisfilename,F77=F77_Unformatted, $
Swap_Endian=Swap_Endian,_EXTRA=e $
else $
openr,lun,thisfilename,F77=F77_Unformatted, $
Swap_Endian=Swap_Endian,_EXTRA=e
; return parameters
filename = expand_path(thisfilename)
result = 0
return
openerr:
result = !Error_State.Code
lun = -1
if (keyword_set(Verbose) OR not keyword_set(NO_PICKFILE)) then begin
; display error message
dum=dialog_message(['Error opening file ',thisfilename+'!', $
!Error_State.sys_msg],/ERROR)
; try again
if (not keyword_set(NO_PICKFILE) and not keyword_set(PICKFILE)) then $
open_file,filemask,lun,filename=filename,result=result, $
update=update,/pickfile,title=title, $
defaultmask=defaultmask,_EXTRA=e
endif
return
end
;=============================================================
pro READ_NCDF, result, filename=filename, truename=truename, $
variables=variables, all=all, attributes=attributes, $
varnames=varnames, vardimid=vardimid, vardims=vardims, $
dimnames=dimnames, dims=dims, title=title, $
no_dimensions=no_dimensions, no_struct=no_struct, $
no_dialog=no_dialog, aspointer=aspointer, verbose=verbose, $
dimensions=dimensions
; count=count, offset=offset, stride=stride, $
; initialize
load_any = Arg_Present(result) ;; avoid unnecessary pointers
result = -1L
ilun = -1
truename = ''
dimnames = ''
dims = -1L
ErrMsg = '<unknown error>'
IF N_Elements(title) EQ 0 THEN title = 'Open NetCDF file:'
; attributes always read
attributes = 0L
; error handler
catch, theError
if theError ne 0 then begin
catch,/Cancel
if ilun gt 0 then free_lun,ilun
if n_elements(truename) eq 0 then begin
if n_elements(filename) gt 0 then $
truename = filename $
else $
truename = '<unknown>'
endif
Message,ErrMsg,/Continue
return
endif
; argument and keyword checking
if (keyword_set(no_struct) AND n_elements(variables) ne 1) then begin
message,'Keyword NO_STRUCT only valid if 1 variable selected.',/Continue
return
endif
; if no filename is passed we set '*.nc' as file mask
;Change this to '*.nc*; *.L*' as default - D. Morrison 6/25/03
;Changed to '*.nc*;*.NC*;*.L*' as default - B. Wolven 2009-03-04
if (n_elements(filename) eq 0) then $
filename = '*.nc*;*.NC*;*.L*'
; for safety, we open the file first with OPEN_FILE
ErrMsg = 'Error opening netCDF file '+filename
open_file,filename,ilun,/BINARY,filename=truename, $
title=title,no_pickfile=no_dialog
IF ilun le 0 THEN return
if keyword_set(verbose) then $
print,'Selected file ',truename
free_lun,ilun
; now we know filename exists, so we can use NCDF_OPEN:
ErrMsg = 'Error opening netCDF file '+truename
id = NCDF_OPEN(truename)
ErrMsg = 'Error reading netCDF file '+truename
; first find out about the file contents
nstru = NCDF_INQUIRE(id)
; read dimensions from netCDF file
dims = ncdf_dimensions(id, names=dimnames, verbose=verbose)
; retrieve the variable names and their dimension indices
varnames = ncdf_varnames(id, vardimid=vardimid)
; return dimension names and sizes in a structure D. Morrison
dimensions=create_struct("Names",dimnames,"Sizes",dims)
; service function: convert dimids to variable dimensions
vardims = vardimid
for i=0L, n_tags(vardimid)-1 do begin
vardims.(i) = dims[vardimid.(i)]
if keyword_set(verbose) then $
print,'Variable '+varnames[i]+' : '+string(vardims.(i),format='(12i6)')
endfor
; retrieve global and variable attributes (but only if requested)
;if arg_present(attributes) then begin
if n_elements(attributes) gt 0L then begin
gattr = ncdf_attributes(id, /Global, verbose=verbose)
IF Keyword_Set(aspointer) THEN BEGIN
attributes = create_struct( 'GLOBAL', Ptr_New(gattr) )
ENDIF ELSE BEGIN
attributes = create_struct( 'GLOBAL', gattr)
ENDELSE
for i=0L,nstru.nvars-1 do begin
vattr = ncdf_attributes(id, i, verbose=verbose)
IF Keyword_Set(aspointer) THEN BEGIN
attributes = create_struct( attributes, Valid_TagName(varnames[i]), Ptr_New(vattr))
ENDIF ELSE BEGIN
attributes = create_struct( attributes, Valid_TagName(varnames[i]), vattr)
ENDELSE
endfor
endif
; return variables names and sizes in a structure D. Morrison
vars=create_struct("Names",varnames,"Dimensions",vardims,"Dimension_ID",vardimid)
; if at least one variable name is specified, use the first one to map
; potential offset, count, stride parameters to physical dimensions.
; This caused me some headache because I hadn't expected to see the dimensions
; shuffled around but they do ;-)
; set default (disallow use of offset, count, and stride)
mapdims = -1L
if n_elements(variables) gt 0 then begin
w = where( StrUpCase(varnames) eq StrUpCase(variables[0]), wcnt )
if wcnt eq 1 then begin ; found it
w = w[0]
mapdims = ncdf_mapdims(varnames[w], vardimid.(w), $
count=count, offset=offset, stride=stride)
endif
endif
; initialize variable counter and result variable
vcount = 0L
result = { nothing : -1L }
; start building result structure with dimension variables
; unless NO_DIMENSIONS is set
if keyword_set(no_dimensions) EQ 0 AND load_any then begin
for i=0L,n_elements(dimnames)-1 do begin
w = where(StrUpCase(varnames) eq StrUpCase(dimnames[i]), wcnt)
if wcnt eq 1 then begin
w = w[0]
if keyword_set(verbose) then $
print,'Loading data for variable '+varnames[w]+' ...'
; decide whether simple read is feasible
if mapdims[0] lt 0 then begin
NCDF_VARGET, id, w, data
endif else begin
; need to find out how to offset, count, and stride the data
ok = ncdf_TestDimensions(id, w, dims, mapdims, $
count=count, offset=offset, stride=stride, $
thiscount=thiscount, thisoffset=thisoffset, thisstride=thisstride)
NCDF_VARGET,id, w, data, count=thiscount, offset=thisoffset, stride=thisstride
endelse
; create structure or add to it
IF vcount EQ 0L THEN BEGIN
IF Keyword_Set(aspointer) THEN BEGIN
result = create_struct( Valid_Tagname(varnames[w]), Ptr_New(data) )
ENDIF ELSE BEGIN
result = create_struct( Valid_Tagname(varnames[w]), data )
ENDELSE
ENDIF ELSE BEGIN
IF Keyword_Set(aspointer) THEN BEGIN
result = create_struct( result, Valid_Tagname(varnames[w]), Ptr_New(data) )
ENDIF ELSE BEGIN
result = create_struct( result, Valid_Tagname(varnames[w]), data )
ENDELSE
ENDELSE
data = 0
vcount = vcount + 1L
if keyword_set(verbose) then $
print,'OK. Data dimensions are '+string(size(data,/Dimensions),format='(12i6)')
endif
endfor
endif
; see which variables were requested and go through them
if n_elements(variables) gt 0 AND load_any then dovars = variables else dovars = varnames
;if keyword_set(all) AND load_any then dovars = varnames
for i=0L,n_elements(dovars)-1 do begin
; check if variable has already been added
validname = Valid_TagName(dovars[i])
test = where(StrUpCase(Tag_Names(result)) eq StrUpCase(validname))
if test[0] lt 0 then begin
w = where(StrUpCase(varnames) eq StrUpCase(dovars[i]), wcnt)
if wcnt eq 1 then begin
w = w[0]
if keyword_set(verbose) then $
print,'Loading data for variable '+varnames[w]+' ...'
; decide whether simple read is feasible
if mapdims[0] lt 0 then begin
NCDF_VARGET, id, w, data
endif else begin
; need to find out how to offset, count, and stride the data
ok = ncdf_TestDimensions(id, w, dims, mapdims, $
count=count, offset=offset, stride=stride, $
thiscount=thiscount, thisoffset=thisoffset, thisstride=thisstride)
NCDF_VARGET,id, w, data, count=thiscount, offset=thisoffset, stride=thisstride
endelse
; create structure or add to it
IF vcount EQ 0L THEN BEGIN
IF Keyword_Set(aspointer) THEN BEGIN
result = create_struct( validname, Ptr_New(data) )
ENDIF ELSE BEGIN
result = create_struct( validname, data )
ENDELSE
ENDIF ELSE BEGIN
IF Keyword_Set(aspointer) THEN BEGIN
result = create_struct( result, validname, Ptr_New(data) )
ENDIF ELSE BEGIN
result = create_struct( result, validname, data )
ENDELSE
ENDELSE
vcount = vcount + 1L
data = 0
if keyword_set(verbose) then $
print,'OK. Data dimensions are '+string(size(data,/Dimensions),format='(12i6)')
endif
endif
endfor
; add attributes structure and dimension names and variables onto result
IF Keyword_Set(aspointer) THEN BEGIN
result = create_struct( result, 'Attributes', Ptr_New(attributes), 'Dimensions', Ptr_New(dimensions), 'Variables', Ptr_New(vars))
ENDIF ELSE BEGIN
result = create_struct( result, 'Attributes', attributes, 'Dimensions',dimensions, 'Variables', vars )
ENDELSE
; close netCDF file
NCDF_CLOSE,id
if keyword_set(verbose) then $
print, "File read"
; extract the only requested variable if the no_struct keyword is set
if keyword_set(no_struct) then begin
nt = tag_names(result)
test = strupcase(Valid_TagName(variables[0]))
w = where(nt eq test,wcnt)
if wcnt eq 1 then begin
result = result.(w[0])
endif else begin
result = -999.
endelse
endif
return
end
Page Last Modified: December 9, 2014