


Interpolate regularly gridded surface forcing data onto a given FVCOM
grid.
fvcom = grid2fvcom(Mobj,vars,data)
DESCRIPTION:
Takes a given NCEP reanalysis grid file and interpolates the U10 and
V10 values onto the specified FVCOM grid file.
INPUT:
Mobj - MATLAB mesh object with the following fields:
x, y, lon, lat - cartesian and spherical node coordinates. These
are transferred to the NetCDF file only and are not used in the
interpolation at all.
nVerts - number of vertices (nodes) in the unstructured grid.
nElems - number of elements in the unstructured grid.
vars - a cell array of the variable names to be interpolated on the
FVCOM grid in Mobj (e.g. uwnd, U10, vwnd, V10 etc.).
data - a struct which contains the following arrays:
x - x data (probably best in cartesian for the interpolation)
y - y data (probably best in cartesian for the interpolation)
The struct must also contain all the variables defined in vars.
time - time vector (in Modified Julian Days). If you're using some
of the NCEP surface products (e.g. relative humitidy, sea level
pressure), you need to supply x and y coordinates for their grids
as .xalt and .yalt).
varargin - keyword/argument pairs:
add_elems - true/false - set to true to enable interpolation of
field onto both nodes and elements (defaults to both). This is
useful if you run into memory issues as this can effectively halve
the memory requirements.
OUTPUT:
fvcom - struct of the interpolated data values at the model nodes and
element centres (unless add_elems is false, in which case, only
nodes). Also includes any variables which were in the input struct
but which have not been interpolated (e.g. time).
EXAMPLE USAGE:
interpfields = {'uwnd', 'vwnd', 'slp', 'nshf', 'nlwrs', 'nswrs', ...
'P_E', 'Et', 'time', 'lon', 'lat', 'x', 'y'};
forcing_interp = grid2fvcom(Mobj, interpfields, forcing);
NOTE:
The shape of the returned arrays for rhum and slp (via
get_NCEP_forcing.m) have sometimes differed from the other vairables
(they appear to be projected onto a different grid). Give data.xalt and
data.yalt to specify the alternative grid.
Author(s):
Pierre Cazenave (Plymouth Marine Laboratory)
Revision history:
2012-10-15 First version based on ncep2fvcom_U10V10.m in the
fvcom-toolbox.
2012-10-16 Removed the code to read the NCEP file. Instead, farmed that
out to a new function (read_NCEP_wind) so that the relevant section can
be more readily extracted (rather than using the entire globe's data:
it's easier to subsample and provide the subsampled data here).
2012-10-17 Add outputs to the function for use in visualisation.
2012-10-19 Add wind struct as input rather than separate u, v, time and
lat/long arrays. Makes invocation a bit cleaner.
2012-11-01 Farmed out the creation of the NetCDF file to
write_FVCOM_forcing.m and made this purely an interpolation script.
2013-02-14 Add support for interpolating data on two different grids
through the .xalt and .yalt fields in the input data structure. This is
handy if you've got data from NCEP from both the Surface and Gaussian
products, each of which are on different grids.
2013-05-16 Add parallel for loops if the Parallel Computing Toolbox is
available (MATLAB parfor loops fail gracefully to for loops if it is
not available, in which case no harm, no foul).
2013-07-18 Add more elegant case statement rather than using string
comparisons.
2013-08-06 Fix fairly significant bug in which the position arrays were
transposed relative to the data arrays. The code now checks for the
dimensions and warns if they have been flipped to match. There is no
checking that the flip has worked because the xalt and yalt arrays
complicate things too much for me to figure out today. If you want to
implement that functionality, please do so! I also added commented-out
figure at the end to check the interpolation has worked properly,
should you wish to check manually.
2013-12-04 Check for the presence of the input fields being requested
in the input struct to avoid finding out that the last field in vars
doesn't exist in data. Change the way the alternative coordinate arrays
are used to accommodate subtleties in the parallel code in MATLAB.
2015-05-20 Update the parallel processing commands.
2015-05-22 Add option to disable output on element centres.
2016-06-02 Fix the alternative grid handling in the parallel loop.
Remove commented out code too.
==========================================================================

0001 function fvcom = grid2fvcom(Mobj, vars, data, varargin) 0002 % Interpolate regularly gridded surface forcing data onto a given FVCOM 0003 % grid. 0004 % 0005 % fvcom = grid2fvcom(Mobj,vars,data) 0006 % 0007 % DESCRIPTION: 0008 % Takes a given NCEP reanalysis grid file and interpolates the U10 and 0009 % V10 values onto the specified FVCOM grid file. 0010 % 0011 % INPUT: 0012 % Mobj - MATLAB mesh object with the following fields: 0013 % x, y, lon, lat - cartesian and spherical node coordinates. These 0014 % are transferred to the NetCDF file only and are not used in the 0015 % interpolation at all. 0016 % nVerts - number of vertices (nodes) in the unstructured grid. 0017 % nElems - number of elements in the unstructured grid. 0018 % vars - a cell array of the variable names to be interpolated on the 0019 % FVCOM grid in Mobj (e.g. uwnd, U10, vwnd, V10 etc.). 0020 % data - a struct which contains the following arrays: 0021 % x - x data (probably best in cartesian for the interpolation) 0022 % y - y data (probably best in cartesian for the interpolation) 0023 % The struct must also contain all the variables defined in vars. 0024 % time - time vector (in Modified Julian Days). If you're using some 0025 % of the NCEP surface products (e.g. relative humitidy, sea level 0026 % pressure), you need to supply x and y coordinates for their grids 0027 % as .xalt and .yalt). 0028 % varargin - keyword/argument pairs: 0029 % add_elems - true/false - set to true to enable interpolation of 0030 % field onto both nodes and elements (defaults to both). This is 0031 % useful if you run into memory issues as this can effectively halve 0032 % the memory requirements. 0033 % 0034 % OUTPUT: 0035 % fvcom - struct of the interpolated data values at the model nodes and 0036 % element centres (unless add_elems is false, in which case, only 0037 % nodes). Also includes any variables which were in the input struct 0038 % but which have not been interpolated (e.g. time). 0039 % 0040 % EXAMPLE USAGE: 0041 % interpfields = {'uwnd', 'vwnd', 'slp', 'nshf', 'nlwrs', 'nswrs', ... 0042 % 'P_E', 'Et', 'time', 'lon', 'lat', 'x', 'y'}; 0043 % forcing_interp = grid2fvcom(Mobj, interpfields, forcing); 0044 % 0045 % NOTE: 0046 % The shape of the returned arrays for rhum and slp (via 0047 % get_NCEP_forcing.m) have sometimes differed from the other vairables 0048 % (they appear to be projected onto a different grid). Give data.xalt and 0049 % data.yalt to specify the alternative grid. 0050 % 0051 % Author(s): 0052 % Pierre Cazenave (Plymouth Marine Laboratory) 0053 % 0054 % Revision history: 0055 % 2012-10-15 First version based on ncep2fvcom_U10V10.m in the 0056 % fvcom-toolbox. 0057 % 2012-10-16 Removed the code to read the NCEP file. Instead, farmed that 0058 % out to a new function (read_NCEP_wind) so that the relevant section can 0059 % be more readily extracted (rather than using the entire globe's data: 0060 % it's easier to subsample and provide the subsampled data here). 0061 % 2012-10-17 Add outputs to the function for use in visualisation. 0062 % 2012-10-19 Add wind struct as input rather than separate u, v, time and 0063 % lat/long arrays. Makes invocation a bit cleaner. 0064 % 2012-11-01 Farmed out the creation of the NetCDF file to 0065 % write_FVCOM_forcing.m and made this purely an interpolation script. 0066 % 2013-02-14 Add support for interpolating data on two different grids 0067 % through the .xalt and .yalt fields in the input data structure. This is 0068 % handy if you've got data from NCEP from both the Surface and Gaussian 0069 % products, each of which are on different grids. 0070 % 2013-05-16 Add parallel for loops if the Parallel Computing Toolbox is 0071 % available (MATLAB parfor loops fail gracefully to for loops if it is 0072 % not available, in which case no harm, no foul). 0073 % 2013-07-18 Add more elegant case statement rather than using string 0074 % comparisons. 0075 % 2013-08-06 Fix fairly significant bug in which the position arrays were 0076 % transposed relative to the data arrays. The code now checks for the 0077 % dimensions and warns if they have been flipped to match. There is no 0078 % checking that the flip has worked because the xalt and yalt arrays 0079 % complicate things too much for me to figure out today. If you want to 0080 % implement that functionality, please do so! I also added commented-out 0081 % figure at the end to check the interpolation has worked properly, 0082 % should you wish to check manually. 0083 % 2013-12-04 Check for the presence of the input fields being requested 0084 % in the input struct to avoid finding out that the last field in vars 0085 % doesn't exist in data. Change the way the alternative coordinate arrays 0086 % are used to accommodate subtleties in the parallel code in MATLAB. 0087 % 2015-05-20 Update the parallel processing commands. 0088 % 2015-05-22 Add option to disable output on element centres. 0089 % 2016-06-02 Fix the alternative grid handling in the parallel loop. 0090 % Remove commented out code too. 0091 % 0092 %========================================================================== 0093 0094 if nargin ~= 3 && isempty(varargin) 0095 error('Incorrect number of arguments') 0096 end 0097 0098 do_elems = false; 0099 for v = 1:2:length(varargin) 0100 switch varargin{v} 0101 case 'add_elems' 0102 if varargin{v + 1} 0103 do_elems = true; 0104 end 0105 end 0106 end 0107 0108 subname = 'grid2fvcom'; 0109 0110 global ftbverbose; 0111 if ftbverbose 0112 fprintf('\nbegin : %s \n', subname) 0113 end 0114 0115 % Before we go too far into this, check we have all the fields in the input 0116 % data that are being requested. 0117 for ff = 1:length(vars) 0118 assert(isfield(data, vars{ff}), ... 0119 'Missing field %s in the input data struct.', vars{ff}) 0120 end 0121 0122 % Run jobs on multiple workers if we have that functionality. Not sure if 0123 % it's necessary, but check we have the Parallel Toolbox first. 0124 if license('test', 'Distrib_Computing_Toolbox') 0125 % We have the Parallel Computing Toolbox, so launch a bunch of workers. 0126 if isempty(gcp('nocreate')) 0127 % Force pool to be local in case we have remote pools available. 0128 parpool('local'); 0129 end 0130 end 0131 0132 %-------------------------------------------------------------------------- 0133 % Get the relevant bits from the FVCOM mesh object 0134 %-------------------------------------------------------------------------- 0135 x = Mobj.x; 0136 y = Mobj.y; 0137 nVerts = Mobj.nVerts; 0138 nElems = Mobj.nElems; 0139 if ftbverbose 0140 fprintf('info for FVCOM domain\n'); 0141 fprintf('number of nodes: %d\n', nVerts); 0142 fprintf('number of elems: %d\n', nElems); 0143 end 0144 0145 xc = nodes2elems(x, Mobj); 0146 yc = nodes2elems(y, Mobj); 0147 0148 try 0149 ntimes = numel(data.time); 0150 catch 0151 ntimes = numel(data.(vars{1}).time); 0152 end 0153 0154 % Interpolate supplied regularly gridded data to FVCOM mesh. Use 0155 % TriScatteredInterp to do the interpolation instead of griddata (should be 0156 % faster). 0157 for vv = 1:length(vars) 0158 switch vars{vv} 0159 case 'time' 0160 fprintf('transferring variable %s as is\n', vars{vv}) 0161 fvcom.(vars{vv}) = data.(vars{vv}); 0162 continue 0163 0164 case {'lat', 'lon', 'x', 'y'} 0165 fprintf('reassigning variable %s from unstructured grid\n', vars{vv}) 0166 fvcom.(vars{vv}) = Mobj.(vars{vv}); 0167 0168 case {'xalt', 'yalt'} 0169 % Only exist for the interpolation of some data on an 0170 % alternative grid. 0171 fprintf('skipping %s\n', vars{vv}) 0172 0173 otherwise 0174 % Preallocate the output arrays. Also create temporary arrays 0175 % for the inner loop to be parallelisable (is that a word?): 0176 tmp_fvcom_data = zeros(nElems, ntimes); 0177 tmp_fvcom_node = zeros(nVerts, ntimes); 0178 try 0179 tmp_data_data = data.(vars{vv}).data; % input to the interpolation 0180 catch msg 0181 fprintf('Trying for alternative data structure. (%s) ', ... 0182 msg.message) 0183 tmp_data_data = data.(vars{vv}); % input to the interpolation 0184 fprintf('success!\n') 0185 end 0186 0187 xx = data.x(:); 0188 yy = data.y(:); 0189 0190 % Check the shapes of the input data match those of the 0191 % position arrays. 0192 [fvx, fvy] = size(data.x); 0193 [ncx, ncy, ~] = size(tmp_data_data); 0194 0195 if isfield(data, 'xalt') 0196 [fvxalt, fvyalt] = size(data.xalt); 0197 if (ncx ~= fvx || ncy ~= fvy) || (ncx ~= fvxalt || ncy ~= fvyalt) 0198 % Flipping the input array so it hopefully matches the 0199 % position arrays. 0200 tmp_data_data = permute(tmp_data_data, [2, 1, 3]); 0201 warning('Transposed ''%s'' input data to match position array dimensions', vars{vv}) 0202 end 0203 else 0204 if (ncx ~= fvx || ncy ~= fvy) 0205 % Flipping the input array so it hopefully matches the 0206 % position arrays. 0207 tmp_data_data = permute(tmp_data_data, [2, 1, 3]); 0208 warning('Transposed ''%s'' input data to match position array dimensions', vars{vv}) 0209 end 0210 % If we have a land mask, mask off the coastal and land 0211 % points in the coordinates arrays. 0212 if isfield(data, 'lsm') 0213 xx(data.lsm ~= 0) = []; 0214 yy(data.lsm ~= 0) = []; 0215 end 0216 end 0217 0218 % Use a parallel loop for the number of time steps we're 0219 % interpolating. 0220 varname = vars{vv}; 0221 parfor i = 1:ntimes 0222 if ftbverbose 0223 fprintf('interpolating %s, frame %d of %d\n', varname, i, ntimes); 0224 end 0225 0226 currvar = tmp_data_data(:, :, i); 0227 0228 % TriScatteredInterp way (with natural neighbour 0229 % interpolation). Instead of the quite crude try/catch that 0230 % was here, count the number of elements in the coordinate 0231 % (xx and yy) and data (currvar) arrays: if they differ, 0232 % try the same thing with the xxalt and xyalt coordinate 0233 % arrays. If they still differ, then error out. The reason 0234 % for this different approach is that the parfor sometimes 0235 % failed for me when using non-NCEP data as the source for 0236 % the interpolation. 0237 ndata = numel(currvar(~isnan(currvar))); 0238 nxx = numel(xx); 0239 nyy = numel(yy); 0240 % Get the alternate grid, if we have it. 0241 if isfield(data, 'xalt') 0242 xxalt = data.xalt(:); 0243 yyalt = data.yalt(:); 0244 if isfield(data, 'lsmalt') 0245 xxalt(data.lsmalt ~= 0) = []; 0246 yyalt(data.lsmalt ~= 0) = []; 0247 end 0248 end 0249 assert(nxx == nyy, 'Inconsistent coordinate array sizes.') 0250 if nxx == ndata 0251 ftsin = TriScatteredInterp(... 0252 xx, ... 0253 yy, ... 0254 currvar(~isnan(currvar(:))), ... 0255 'natural'); 0256 elseif isfield(data, 'xalt') && numel(xxalt) == ndata 0257 ftsin = TriScatteredInterp(... 0258 xxalt, ... 0259 yyalt, ... 0260 currvar(~isnan(currvar(:))), ... 0261 'natural'); 0262 else 0263 error('Can''t interpolate the data: non-matching coordinate array sizes.') 0264 end 0265 0266 tmp_fvcom_node(:, i) = ftsin(x, y); 0267 nnans1 = sum(isnan(tmp_fvcom_node(:, i))); 0268 if nnans1 > 0 0269 warning('%i NaNs in the interpolated node data. This won''t work with FVCOM.', nnans1) 0270 end 0271 if do_elems 0272 tmp_fvcom_data(:, i) = ftsin(xc, yc); 0273 nnans2 = sum(isnan(tmp_fvcom_data(:, i))); 0274 if nnans2 > 0 0275 warning('%i NaNs in the interpolated element data. This won''t work with FVCOM.', nnans2) 0276 end 0277 end 0278 end 0279 % Transfer the temporary arrays back to the relevant struct and 0280 % clear out the temporary arrays. 0281 fvcom.(vars{vv}).node = tmp_fvcom_node; 0282 if do_elems 0283 fvcom.(vars{vv}).data = tmp_fvcom_data; 0284 end 0285 clear nnans* tmp_* 0286 0287 if ftbverbose 0288 fprintf('interpolation of %s complete\n', vars{vv}); 0289 end 0290 end 0291 end 0292 0293 cleaner = onCleanup(@() delete(gcp('nocreate'))); 0294 0295 if ftbverbose 0296 fprintf('end : %s \n', subname) 0297 end 0298 0299 %% Debugging figure to check the interpolation makes sense. 0300 0301 % close all 0302 % 0303 % figure 0304 % 0305 % vartoplot = 'nswrf'; 0306 % tidx = 12; % time index 0307 % 0308 % subplot(2, 1, 1) 0309 % try 0310 % pcolor(data.lon, data.lat, data.(vartoplot).data(:, :, tidx)') 0311 % catch 0312 % pcolor(data.lon, data.lat, data.(vartoplot)(:, :, tidx)') 0313 % end 0314 % shading flat 0315 % axis('equal', 'tight') 0316 % title([vartoplot, ' (regularly gridded)']) 0317 % colorbar 0318 % try 0319 % caxis([min(fvcom.(vartoplot).data(:, tidx)), max(fvcom.(vartoplot).data(:, tidx))]) 0320 % catch 0321 % caxis([min(fvcom.(vartoplot)(:, tidx)), max(fvcom.(vartoplot)(:, tidx))]) 0322 % end 0323 % axis([min(Mobj.lon), max(Mobj.lon), min(Mobj.lat), max(Mobj.lat)]) 0324 % 0325 % subplot(2, 1, 2) 0326 % try 0327 % patch('Vertices', [Mobj.lon, Mobj.lat], 'Faces', Mobj.tri, 'cData', fvcom.(vartoplot).data(:, tidx)); 0328 % catch 0329 % patch('Vertices', [Mobj.lon, Mobj.lat], 'Faces', Mobj.tri, 'cData', fvcom.(vartoplot)(:, tidx)); 0330 % end 0331 % shading flat 0332 % axis('equal') 0333 % axis([min(data.lon(:)), max(data.lon(:)), min(data.lat(:)), max(data.lat(:))]) 0334 % title([vartoplot, ' (interpolated)']) 0335 % colorbar 0336 % try 0337 % caxis([min(fvcom.(vartoplot).data(:, tidx)), max(fvcom.(vartoplot).data(:, tidx))]) 0338 % catch 0339 % caxis([min(fvcom.(vartoplot)(:, tidx)), max(fvcom.(vartoplot)(:, tidx))]) 0340 % end 0341 % axis([min(Mobj.lon), max(Mobj.lon), min(Mobj.lat), max(Mobj.lat)]) 0342