PageRenderTime 34ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/NetCDF/exportNetCDF.m

http://imos-toolbox.googlecode.com/
Objective C | 438 lines | 371 code | 67 blank | 0 comment | 51 complexity | f931432a28b9aa4ec3368a5006072abc MD5 | raw file
  1. function filename = exportNetCDF( sample_data, dest )
  2. %EXPORTNETCDF Export the given sample data to a NetCDF file.
  3. %
  4. % Export the given sample and calibration data to a NetCDF file. The file is
  5. % saved to the given destination directory. The file name is generated by the
  6. % genIMOSFileName function.
  7. %
  8. % Inputs:
  9. % sample_data - a struct containing sample data for one process level.
  10. %
  11. % dest - Destination directory to save the file.
  12. %
  13. % Outputs:
  14. % filename - String containing the absolute path of the saved NetCDF file.
  15. %
  16. % Author: Paul McCarthy <paul.mccarthy@csiro.au>
  17. %
  18. %
  19. % Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated
  20. % Marine Observing System (IMOS).
  21. % All rights reserved.
  22. %
  23. % Redistribution and use in source and binary forms, with or without
  24. % modification, are permitted provided that the following conditions are met:
  25. %
  26. % * Redistributions of source code must retain the above copyright notice,
  27. % this list of conditions and the following disclaimer.
  28. % * Redistributions in binary form must reproduce the above copyright
  29. % notice, this list of conditions and the following disclaimer in the
  30. % documentation and/or other materials provided with the distribution.
  31. % * Neither the name of the eMII/IMOS nor the names of its contributors
  32. % may be used to endorse or promote products derived from this software
  33. % without specific prior written permission.
  34. %
  35. % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  36. % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  37. % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  38. % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  39. % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  40. % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  41. % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  42. % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  43. % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  44. % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  45. % POSSIBILITY OF SUCH DAMAGE.
  46. %
  47. error(nargchk(2, 2, nargin));
  48. if ~isstruct(sample_data), error('sample_data must be a struct'); end
  49. if ~ischar(dest), error('dest must be a string'); end
  50. % check that destination is a directory
  51. [stat atts] = fileattrib(dest);
  52. if ~stat || ~atts.directory || ~atts.UserWrite
  53. error([dest ' does not exist, is not a directory, or is not writeable']);
  54. end
  55. % generate the filename
  56. filename = genIMOSFileName(sample_data, 'nc');
  57. filename = [dest filesep filename];
  58. fid = netcdf.create(filename, 'NC_NOCLOBBER');
  59. if fid == -1, error(['could not create ' filename]); end
  60. dateFmt = readProperty('exportNetCDF.dateFormat');
  61. qcSet = str2double(readProperty('toolbox.qc_set'));
  62. qcType = imosQCFlag('', qcSet, 'type');
  63. qcDimId = [];
  64. try
  65. %
  66. % the file is created in the following order
  67. %
  68. % 1. global attributes
  69. % 2. dimensions / coordinate variables
  70. % 3. variable definitions
  71. % 4. data
  72. %
  73. globConst = netcdf.getConstant('NC_GLOBAL');
  74. %
  75. % global attributes
  76. %
  77. globAtts = sample_data;
  78. globAtts = rmfield(globAtts, 'meta');
  79. globAtts = rmfield(globAtts, 'variables');
  80. globAtts = rmfield(globAtts, 'dimensions');
  81. if ~isempty(sample_data.meta.log)
  82. globAtts.history = cellfun(@(x)(sprintf('%s\n', x)), ...
  83. sample_data.meta.log, 'UniformOutput', false);
  84. globAtts.history = [globAtts.history{:}];
  85. globAtts.history = globAtts.history(1:end-1);
  86. end
  87. putAtts(fid, globConst, globAtts, 'global', dateFmt);
  88. % if the QC flag values are characters, we must define
  89. % a dimension to force the maximum value length to 1
  90. if strcmp(qcType, 'char')
  91. qcDimId = netcdf.defDim(fid, 'qcStrLen', 1);
  92. end
  93. %
  94. % define string lengths
  95. % dimensions and variables of cell type contain strings
  96. % define stringNN dimensions when NN is a power of 2 to hold strings
  97. %
  98. dims = sample_data.dimensions;
  99. vars = sample_data.variables;
  100. str(1) = 0;
  101. for m = 1:length(dims)
  102. stringlen = 0;
  103. if iscell(dims{m}.data)
  104. stringlen = ceil(log2(max(cellfun('length', dims{m}.data))));
  105. str(stringlen) = 1; %#ok<AGROW>
  106. end
  107. sample_data.dimensions{m}.stringlen = stringlen;
  108. end
  109. for m = 1:length(vars)
  110. stringlen = 0;
  111. if iscell(vars{m}.data)
  112. stringlen = ceil(log2(max(cellfun('length', vars{m}.data))));
  113. str(stringlen) = 1; %#ok<AGROW>
  114. end
  115. sample_data.variables{m}.stringlen = stringlen;
  116. end
  117. stringd = zeros(length(str));
  118. for m = 1:length(str)
  119. if str(m)
  120. len = 2 ^ m;
  121. stringd(m) = netcdf.defDim(fid, [ 'string' int2str(len) ], len);
  122. end
  123. end
  124. %
  125. % dimension and coordinate variable definitions
  126. %
  127. dims = sample_data.dimensions;
  128. for m = 1:length(dims)
  129. dims{m}.name = upper(dims{m}.name);
  130. dimAtts = dims{m};
  131. dimAtts = rmfield(dimAtts, 'data');
  132. dimAtts = rmfield(dimAtts, 'flags');
  133. dimAtts = rmfield(dimAtts, 'stringlen');
  134. % add the QC variable (defined below)
  135. % to the ancillary variables attribute
  136. dimAtts.ancillary_variables = [dims{m}.name '_quality_control'];
  137. % create dimension
  138. did = netcdf.defDim(fid, dims{m}.name, length(dims{m}.data));
  139. % create coordinate variable and attributes
  140. if iscell(dims{m}.data)
  141. vid = netcdf.defVar(fid, dims{m}.name, 'char', ...
  142. [ stringd(dims{m}.stringlen) did ]);
  143. else
  144. vid = netcdf.defVar(fid, dims{m}.name, 'double', did);
  145. end
  146. putAtts(fid, vid, dimAtts, lower(dims{m}.name), dateFmt);
  147. % create the ancillary QC variable
  148. qcvid = addQCVar(...
  149. fid, sample_data, m, [qcDimId did], 'dim', qcType, dateFmt);
  150. % save the netcdf dimension and variable IDs
  151. % in the dimension struct for later reference
  152. sample_data.dimensions{m}.did = did;
  153. sample_data.dimensions{m}.vid = vid;
  154. sample_data.dimensions{m}.qcvid = qcvid;
  155. end
  156. %
  157. % variable (and ancillary QC variable) definitions
  158. %
  159. dims = sample_data.dimensions;
  160. vars = sample_data.variables;
  161. for m = 1:length(vars)
  162. varname = vars{m}.name;
  163. % get the dimensions for this variable
  164. dids = [];
  165. dimIdxs = vars{m}.dimensions;
  166. for n = 1:length(dimIdxs), dids(n) = dims{dimIdxs(n)}.did; end
  167. % reverse dimension order - matlab netcdf.defvar requires
  168. % dimensions in order of fastest changing to slowest changing.
  169. % The time dimension is always first in the variable.dimensions
  170. % list, and is always the slowest changing.
  171. dids = fliplr(dids);
  172. % create the variable
  173. if iscell(vars{m}.data)
  174. vid = netcdf.defVar(fid, varname, 'char', ...
  175. [ stringd(vars{m}.stringlen) did ]);
  176. else
  177. vid = netcdf.defVar(fid, varname, 'double', dids);
  178. end
  179. varAtts = vars{m};
  180. varAtts = rmfield(varAtts, 'data');
  181. varAtts = rmfield(varAtts, 'dimensions');
  182. varAtts = rmfield(varAtts, 'flags');
  183. varAtts = rmfield(varAtts, 'stringlen');
  184. % add the QC variable (defined below)
  185. % to the ancillary variables attribute
  186. varAtts.ancillary_variables = [varname '_quality_control'];
  187. % add the attributes
  188. putAtts(fid, vid, varAtts, 'variable', dateFmt);
  189. % create the ancillary QC variable
  190. qcvid = addQCVar(...
  191. fid, sample_data, m, [qcDimId dids], 'var', qcType, dateFmt);
  192. % save variable IDs for later reference
  193. sample_data.variables{m}.vid = vid;
  194. sample_data.variables{m}.qcvid = qcvid;
  195. end
  196. % we're finished defining dimensions/attributes/variables
  197. netcdf.endDef(fid);
  198. %
  199. % coordinate variable (and ancillary variable) data
  200. %
  201. dims = sample_data.dimensions;
  202. % translate time from matlab serial time (days since 0000-00-00 00:00:00Z)
  203. % to IMOS mandated time (days since 1950-01-01T00:00:00Z)
  204. if strcmpi(dims{1}.name, 'TIME')
  205. dims{1}.data = dims{1}.data - datenum('1950-01-01 00:00:00');
  206. end
  207. for m = 1:length(dims)
  208. % variable data
  209. vid = dims{m}.vid;
  210. qcvid = dims{m}.qcvid;
  211. data = dims{m}.data;
  212. stringlen = dims{m}.stringlen;
  213. % replace NaN's with fill value
  214. if isnumeric(data)
  215. data(isnan(data)) = dims{m}.FillValue_;
  216. netcdf.putVar(fid, vid, data);
  217. elseif iscell(data)
  218. data = char(data);
  219. netcdf.putVar(fid, vid, zeros(ndims(data), 1), fliplr(size(data)), data);
  220. end
  221. % ancillary QC variable data
  222. data = dims{m}.flags;
  223. netcdf.putVar(fid, qcvid, data);
  224. end
  225. %
  226. % variable (and ancillary variable) data
  227. %
  228. vars = sample_data.variables;
  229. for m = 1:length(vars)
  230. % variable data
  231. data = vars{m}.data;
  232. vid = vars{m}.vid;
  233. qcvid = vars{m}.qcvid;
  234. stringlen = vars{m}.stringlen;
  235. % transpose required for multi-dimensional data, as matlab
  236. % requires the fastest changing dimension to be first.
  237. % of more than two dimensions.
  238. nDims = length(vars{m}.dimensions);
  239. if nDims > 1, data = permute(data, nDims:-1:1); end
  240. if isnumeric(data)
  241. % replace NaN's with fill value
  242. data(isnan(data)) = vars{m}.FillValue_;
  243. netcdf.putVar(fid, vid, data);
  244. elseif iscell(data)
  245. data = char(data);
  246. netcdf.putVar(fid, vid, zeros(ndims(data), 1), fliplr(size(data)), data);
  247. end
  248. % ancillary QC variable data
  249. data = vars{m}.flags;
  250. if nDims > 1, data = permute(data, nDims:-1:1); end
  251. netcdf.putVar(fid, qcvid, data);
  252. end
  253. %
  254. % and we're done
  255. %
  256. netcdf.close(fid);
  257. % ensure that the file is closed in the event of an error
  258. catch e
  259. try netcdf.close(fid); catch ex, end
  260. if exist(filename, 'file'), delete(filename); end
  261. rethrow(e);
  262. end
  263. end
  264. function vid = addQCVar(...
  265. fid, sample_data, varIdx, dims, type, outputType, dateFmt)
  266. %ADDQCVAR Adds an ancillary variable for the variable with the given index.
  267. %
  268. % Inputs:
  269. % fid - NetCDF file identifier
  270. % sample_data - Struct containing entire data set
  271. % varIdx - Index into sample_data.variables, specifying the
  272. % variable.
  273. % dims - Vector of NetCDF dimension identifiers.
  274. % type - either 'dim' or 'var', to differentiate between
  275. % coordinate variables and data variables.
  276. % outputType - The matlab type in which the flags should be output.
  277. % dateFmt - Date format in which date attributes should be output.
  278. %
  279. % Outputs:
  280. % vid - NetCDF variable identifier of the QC variable that was
  281. % created.
  282. %
  283. switch(type)
  284. case 'dim'
  285. var = sample_data.dimensions{varIdx};
  286. template = 'qc_coord';
  287. case 'var'
  288. var = sample_data.variables{varIdx};
  289. template = 'qc';
  290. otherwise
  291. error(['invalid type: ' type]);
  292. end
  293. path = readProperty('toolbox.templateDir');
  294. if isempty(path) || ~exist(path, 'dir')
  295. path = fullfile(pwd, 'NetCDF', 'template');
  296. end
  297. varname = [var.name '_quality_control'];
  298. qcAtts = parseNetCDFTemplate(...
  299. fullfile(path, [template '_attributes.txt']), sample_data, varIdx);
  300. % get qc flag values
  301. qcFlags = imosQCFlag('', sample_data.quality_control_set, 'values');
  302. qcDescs = {};
  303. % get flag descriptions
  304. for k = 1:length(qcFlags)
  305. qcDescs{k} = ...
  306. imosQCFlag(qcFlags(k), sample_data.quality_control_set, 'desc');
  307. end
  308. % if all flag values are equal, add the
  309. % quality_control_indicator attribute
  310. minFlag = min(var.flags(:));
  311. maxFlag = max(var.flags(:));
  312. if minFlag == maxFlag
  313. if strcmp(outputType, 'char'), minFlag = char(minFlag); end
  314. qcAtts.quality_control_indicator = minFlag;
  315. end
  316. % force fill value to correct type
  317. if strcmp(outputType, 'byte')
  318. qcAtts.FillValue_ = uint8(qcAtts.FillValue_);
  319. qcFlags = uint8(qcFlags);
  320. end
  321. % if the flag values are characters, turn the flag values
  322. % attribute into a string of comma separated characters
  323. if strcmp(outputType, 'char')
  324. qcFlags(2,:) = ',';
  325. qcFlags = reshape(qcFlags, 1, numel(qcFlags));
  326. qcFlags = qcFlags(1:end-1);
  327. qcFlags = strrep(qcFlags, ',', ', ');
  328. end
  329. qcAtts.flag_values = qcFlags;
  330. % turn descriptions into space separated string
  331. qcDescs = cellfun(@(x)(sprintf('%s ', x)), qcDescs, 'UniformOutput', false);
  332. qcAtts.flag_meanings = [qcDescs{:}];
  333. vid = netcdf.defVar(fid, varname, outputType, dims);
  334. putAtts(fid, vid, qcAtts, template, dateFmt);
  335. end
  336. function putAtts(fid, vid, template, templateFile, dateFmt)
  337. %PUTATTS Puts all the attributes from the given template into the given NetCDF
  338. % variable.
  339. %
  340. % This code is repeated a number of times, so it made sense to enclose it in a
  341. % separate function. Takes all the fields from the given template struct, and
  342. % writes them to the NetCDF file specified by fid, in the variable specified by
  343. % vid.
  344. %
  345. % Inputs:
  346. % fid - NetCDF file identifier
  347. % vid - NetCDF variable identifier
  348. % template - Struct containing attribute names/values.
  349. % templateFile - name of the template file from where the attributes
  350. % originated.
  351. % dateFmt - format to use for writing date attributes.
  352. %
  353. % each att is a struct field
  354. atts = fieldnames(template);
  355. for k = 1:length(atts)
  356. name = atts{k};
  357. val = template.(name);
  358. if isempty(val), continue; end;
  359. type = 'S';
  360. try, type = templateType(name, templateFile);
  361. catch e, end
  362. switch type
  363. case 'D', val = datestr(val, dateFmt);
  364. end
  365. % matlab-no-support-leading-underscore kludge
  366. if name(end) == '_', name = ['_' name(1:end-1)]; end
  367. % add the attribute
  368. %disp([' ' name ': ' val]);
  369. netcdf.putAtt(fid, vid, name, val);
  370. end
  371. end