


Read sms mesh files into Matlab mesh object.
[Mobj] = function read_fvcom_mesh(varargin)
DESCRIPTION:
Read SMS 2dm file and bathymetry file
Store in a matlab mesh object
INPUT [keyword pairs]:
'2dm' = sms 2dm file [e.g. tst_grd.dat]
[optional] 'bath' = sms bathymetry file [e.g. tst_dep.dat]
[optional] 'coordinate' = coordinate system [spherical; cartesian (default)]
[optional] 'project' = generate (x,y) coordinates if input is (lon,lat)
generate (lon,lat) coordinates if input is (x,y)
['true' ; 'false' (default)], see myproject.m
[optional] 'addCoriolis' = calculate Coriolis param (f), requires [lon,lat]
OUTPUT:
Mobj = matlab structure containing mesh data
EXAMPLE USAGE
Mobj = read_sms_mesh('2dm','skagit.2dm','bath','bathy.dat')
Author(s):
Geoff Cowles (University of Massachusetts Dartmouth)
Pierre Cazenave (Plymouth Marine Laboratory)
Rory O'Hara Murray (Marine Scotland Science)
Simon Waldman (Marine Scotland Science / Heriot-Watt University)
Revision history
2012-06-20 Add support for reading nodestrings from SMS meshes.
2012-06-26 Added more resilient support for reading in SMS files.
2012-06-29 Further improved ability to read files with variable length
headers.
2013-07-31 Added some performance improvements to speed up loading mesh
files (from ~70s to ~30s on a 250,000 node grid). There's probably more
gains to be had by saving the values of tri, x, y and h when first
parsing the input file (lines 132-152). My brief testing would suggest
the overhead of converting from strings to doubles shouldn't be
underestimated.
2013-10-01 Further improved ability to read files with variable length
headers (ROM).
2013-12-11 Closed the sms_2dm file using fclose (ROM).
2014-04-10 Fix bugs when not using bathymetry (i.e. only reading the
grid data in).
2015-03-19 Add spherical coordinates on element centres.
2015-09-24 Populate the alternative coordinate system with zeros rather
than repeating the values. Also add element centre coordinates for
cartesian coordinates. This is somewhat redundant given setup_metrics
does this anyway.
2016-07-28 Fix behaviour if grid has no open boundaries so we can rely
on have_strings existing in either case.
[the next few revisions are listed out of order because of rebasing
a branch that had been separate for a long time]
2014-05-29 Changed the way the header is read and skipped (ROM).
2014-05-29 Changed the way the nodestrings are read, taking into
account the possibility that SMS adds exatra 'name' number to each
nodestring after the -ve indicator (ROM).
2018-05-16 Rewrote nodestring parsing. It's far less elegant, but now
it still works if the number of nodes in a string is a multiple of 10.
(SW)
2018-05-16 If we have bathymetry in the .2dm file *and* a separate
bathymetry file provided, use the bathymetry in the file (with a
warning) rather than ignoring it.
==============================================================================

0001 function [Mobj] = read_sms_mesh(varargin) 0002 % Read sms mesh files into Matlab mesh object. 0003 % 0004 % [Mobj] = function read_fvcom_mesh(varargin) 0005 % 0006 % DESCRIPTION: 0007 % Read SMS 2dm file and bathymetry file 0008 % Store in a matlab mesh object 0009 % 0010 % INPUT [keyword pairs]: 0011 % '2dm' = sms 2dm file [e.g. tst_grd.dat] 0012 % [optional] 'bath' = sms bathymetry file [e.g. tst_dep.dat] 0013 % [optional] 'coordinate' = coordinate system [spherical; cartesian (default)] 0014 % [optional] 'project' = generate (x,y) coordinates if input is (lon,lat) 0015 % generate (lon,lat) coordinates if input is (x,y) 0016 % ['true' ; 'false' (default)], see myproject.m 0017 % [optional] 'addCoriolis' = calculate Coriolis param (f), requires [lon,lat] 0018 % 0019 % OUTPUT: 0020 % Mobj = matlab structure containing mesh data 0021 % 0022 % EXAMPLE USAGE 0023 % Mobj = read_sms_mesh('2dm','skagit.2dm','bath','bathy.dat') 0024 % 0025 % Author(s): 0026 % Geoff Cowles (University of Massachusetts Dartmouth) 0027 % Pierre Cazenave (Plymouth Marine Laboratory) 0028 % Rory O'Hara Murray (Marine Scotland Science) 0029 % Simon Waldman (Marine Scotland Science / Heriot-Watt University) 0030 % 0031 % Revision history 0032 % 0033 % 2012-06-20 Add support for reading nodestrings from SMS meshes. 0034 % 2012-06-26 Added more resilient support for reading in SMS files. 0035 % 2012-06-29 Further improved ability to read files with variable length 0036 % headers. 0037 % 2013-07-31 Added some performance improvements to speed up loading mesh 0038 % files (from ~70s to ~30s on a 250,000 node grid). There's probably more 0039 % gains to be had by saving the values of tri, x, y and h when first 0040 % parsing the input file (lines 132-152). My brief testing would suggest 0041 % the overhead of converting from strings to doubles shouldn't be 0042 % underestimated. 0043 % 2013-10-01 Further improved ability to read files with variable length 0044 % headers (ROM). 0045 % 2013-12-11 Closed the sms_2dm file using fclose (ROM). 0046 % 2014-04-10 Fix bugs when not using bathymetry (i.e. only reading the 0047 % grid data in). 0048 % 2015-03-19 Add spherical coordinates on element centres. 0049 % 2015-09-24 Populate the alternative coordinate system with zeros rather 0050 % than repeating the values. Also add element centre coordinates for 0051 % cartesian coordinates. This is somewhat redundant given setup_metrics 0052 % does this anyway. 0053 % 2016-07-28 Fix behaviour if grid has no open boundaries so we can rely 0054 % on have_strings existing in either case. 0055 % [the next few revisions are listed out of order because of rebasing 0056 % a branch that had been separate for a long time] 0057 % 2014-05-29 Changed the way the header is read and skipped (ROM). 0058 % 2014-05-29 Changed the way the nodestrings are read, taking into 0059 % account the possibility that SMS adds exatra 'name' number to each 0060 % nodestring after the -ve indicator (ROM). 0061 % 2018-05-16 Rewrote nodestring parsing. It's far less elegant, but now 0062 % it still works if the number of nodes in a string is a multiple of 10. 0063 % (SW) 0064 % 2018-05-16 If we have bathymetry in the .2dm file *and* a separate 0065 % bathymetry file provided, use the bathymetry in the file (with a 0066 % warning) rather than ignoring it. 0067 % 0068 %============================================================================== 0069 0070 [~, subname] = fileparts(mfilename('fullpath')); 0071 global ftbverbose; 0072 if ftbverbose 0073 fprintf('\nbegin : %s \n', subname) 0074 end 0075 0076 userproject = false; 0077 have_bath = false; 0078 have_strings = false; 0079 0080 %-------------------------------------------------------------------------- 0081 % Create a blank mesh object 0082 %-------------------------------------------------------------------------- 0083 Mobj = make_blank_mesh; 0084 coordinate = 'cartesian'; 0085 0086 %-------------------------------------------------------------------------- 0087 % Parse input arguments 0088 %-------------------------------------------------------------------------- 0089 0090 if mod(length(varargin), 2) ~= 0 0091 error('incorrect usage of read_sms_mesh, use keyword pairs') 0092 end 0093 0094 for i = 1:2:length(varargin) - 1 0095 keyword = lower(varargin{i}); 0096 0097 if ~ischar(keyword) 0098 error('incorrect usage of read_sms_mesh') 0099 end 0100 0101 switch keyword 0102 case '2dm' 0103 sms_2dm = varargin{i + 1}; 0104 have_2dm = true; 0105 case 'bath' 0106 sms_bath = varargin{i + 1}; 0107 have_bath = true; 0108 case 'coordinate' 0109 coord = varargin{i + 1}; 0110 if strcmpi(coord, 'spherical') 0111 coordinate = 'spherical'; 0112 have_lonlat = true; 0113 elseif strcmpi(coord, 'cartesian') 0114 coordinate = 'cartesian'; 0115 have_xy = true; 0116 else 0117 warning('Unrecognised coordinate system (%s). Valid values are ''spherical'' and ''cartesian''.', coordinate) 0118 end 0119 case 'project' 0120 val = varargin{i + 1}; 0121 if val 0122 userproject = true; 0123 else 0124 userproject = false; 0125 end 0126 case 'addcoriolis' 0127 val = varargin{i + 1}; 0128 if val 0129 addCoriolis = true; 0130 else 0131 addCoriolis = false; 0132 end 0133 otherwise 0134 disp(varargin{i + 1}) 0135 error('Can''t understand property: %s', varargin{i + 1}); 0136 0137 end 0138 end 0139 0140 %-------------------------------------------------------------------------- 0141 % Read the mesh from the 2dm file 0142 %-------------------------------------------------------------------------- 0143 0144 fid = fopen(sms_2dm, 'rt'); 0145 if fid < 0 0146 error(['file: ' sms_2dm ' does not exist']); 0147 end 0148 0149 % Count number of elements and vertices 0150 if ftbverbose 0151 fprintf('reading from: %s\n', sms_2dm) 0152 fprintf('first pass to count number of nodes and vertices\n') 0153 end 0154 0155 nElems = 0; 0156 nVerts = 0; 0157 nStrings = 0; 0158 nHeader = 0; 0159 StillReading = true; 0160 while StillReading 0161 lin = fgetl(fid); 0162 if lin ~= -1 % EOF is -1 0163 switch lin(1:2) 0164 case 'E3' 0165 nElems = nElems + 1; 0166 case 'ND' 0167 nVerts = nVerts + 1; 0168 case 'NS' 0169 nStrings = nStrings + 1; 0170 case {'ME', 'NU'} 0171 nHeader = nHeader + 1; 0172 case 'E4' 0173 error('Quadrilateral elements are unsupported in FVCOM') 0174 otherwise 0175 StillReading = false; 0176 end 0177 else 0178 % Got to EOF 0179 StillReading = false; 0180 end 0181 end 0182 fclose(fid); 0183 0184 fid = fopen(sms_2dm, 'rt'); 0185 0186 if ftbverbose 0187 fprintf('nVerts: %d\n', nVerts); 0188 fprintf('nElems: %d\n', nElems); 0189 fprintf('reading in connectivity and grid points\n') 0190 end 0191 0192 % Allocate memory to hold mesh and connectivity 0193 tri = zeros(nElems,3); 0194 lon = zeros(nVerts,1); 0195 lat = zeros(nVerts,1); 0196 ts = zeros(nVerts,1); 0197 0198 % Skip the header 0199 for ii=1:nHeader 0200 lin = fgetl(fid); 0201 end 0202 0203 % Read the triangulation table 0204 C = textscan(fid, '%s %d %d %d %d %d', nElems); 0205 tri(:, 1) = C{3}; 0206 tri(:, 2) = C{4}; 0207 tri(:, 3) = C{5}; 0208 0209 % Read in the nodes and interpolated depths 0210 C = textscan(fid, '%s %d %f %f %f ', nVerts); 0211 x = C{3}; 0212 y = C{4}; 0213 h = C{5}; 0214 0215 % Check we don't have any NaNs anywhere 0216 if max(isnan(x)) == 1 0217 error('%d NaNs in the x data', sum(isnan(x))) 0218 end 0219 if max(isnan(y)) == 1 0220 error('%d NaNs in the y data', sum(isnan(x))) 0221 end 0222 if max(isnan(h)) == 1 0223 error('%d NaNs in the h data', sum(isnan(x))) 0224 end 0225 if max(isnan(tri(:))) == 1 0226 error('%d NaNs in the h data', sum(isnan(tri(:)))) 0227 end 0228 0229 %Read in nodestrings. 0230 tmp = textscan(fid, ['%s' repmat('%d', 1, 12) '%*[^\n]'], nStrings, 'delimiter', ' ', 'MultipleDelimsAsOne', 1, 'CollectOutput', 1); 0231 % this allows for up to 12 items on a NS line. It's normally 10, but can 0232 % sometimes be 11. If we hit 12, something's changed in SMS's output. 0233 % The second cell of the cell array returned by this should be a matrix of all the numeric 0234 % values. Columns that don't have values in the file will contain 0. 0235 mNSlines = tmp{2}; 0236 clear tmp; 0237 0238 % We'll work through the rows of this matrix and assemble the 0239 % nodestring(s). 0240 currentNSno = 1; 0241 currentNS = []; 0242 NSlengths = []; 0243 for r = 1:size(mNSlines,1) %rows 0244 for c = 1:size(mNSlines, 2) %columns 0245 if mNSlines(r,c)==0 %we've run out of values on this line. Skip to next line. 0246 break; 0247 elseif mNSlines(r,c) < 0 0248 %end of nodestring, marked by a negative node number. 0249 %Append positive value to the NS, do end-of-NS stuff, then ignore 0250 %rest of line. 0251 currentNS = [currentNS abs(mNSlines(r,c))]; 0252 % Remove duplicate nodestring IDs in case we have them. 0253 read_obc_nodes{currentNSno} = unique(currentNS, 'stable'); 0254 NSlengths = [NSlengths length(currentNS)]; 0255 currentNSno = currentNSno + 1; 0256 currentNS = []; 0257 break; 0258 else 0259 % append value to nodestring. If this is in a column higher 0260 % than 10, raise a warning, as SMS doesn't usually do this. 0261 currentNS = [currentNS mNSlines(r,c)]; 0262 if c > 10 0263 warning('Longer lines than expected when parsing nodestrings; SMS output may not be as expected. Run with ftbverbose=true and check that the number and length of nodestrings found is as expected.'); 0264 end 0265 end 0266 end 0267 end 0268 0269 if ftbverbose 0270 a = sprintf('%d ', NSlengths); 0271 fprintf('%i complete nodestrings found, of lengths %s. \n', currentNSno - 1, a); 0272 clear a 0273 end 0274 0275 if nStrings > 0 0276 have_strings = true; 0277 end 0278 0279 have_lonlat = false; 0280 have_xy = false; 0281 if strcmpi(coordinate, 'spherical') 0282 lon = x; 0283 lat = y; 0284 % Why reset everything to zero here? 0285 %x = x * 0.0; 0286 %y = y * 0.0; 0287 have_lonlat = true; 0288 % Just do a double check on the coordinates to make sure we don't 0289 % actually have cartesian 0290 if max(lon) > 360 0291 warning('You''ve specified spherical coordinates, but your upper longitude value exceeds 360 degrees. Are you sure you have spherical data?') 0292 end 0293 elseif strcmpi(coordinate, 'cartesian') 0294 have_xy = true; 0295 else 0296 warning('Unrecognised coordinate system (%s). Valid values are ''spherical'' and ''cartesian''.', coordinate) 0297 end 0298 0299 fclose(fid); 0300 0301 %-------------------------------------------------------------------------- 0302 % Read the topography from the bathymetry file 0303 %-------------------------------------------------------------------------- 0304 0305 bath_range = max(h) - min(h); 0306 if have_bath 0307 if bath_range ~= 0 0308 warning(['Bathymetry is present in the .2dm file, but a bathymetry .dat file was also provided. '... 0309 'The .dat file will be used, and the depth info in the .2dm will be ignored.']); 0310 end 0311 fid = fopen(sms_bath, 'rt'); 0312 if fid < 0 0313 error('file: %s does not exist', sms_bath); 0314 else 0315 if ftbverbose; fprintf('reading sms bathymetry from: %s\n', sms_bath); end 0316 end 0317 lin = fgetl(fid); 0318 lin = fgetl(fid); 0319 lin = fgetl(fid); 0320 C = textscan(fid, '%s %d', 1); 0321 nVerts_tmp = C{2}; 0322 C = textscan(fid, '%s %d', 1); 0323 nElems_tmp = C{2}; 0324 if (nVerts - nVerts_tmp) * (nElems - nElems_tmp) ~= 0 0325 fprintf('dimensions of bathymetry file do not match 2dm file\n') 0326 fprintf('bathymetry nVerts: %d\n',nVerts_tmp) 0327 fprintf('bathymetry nElems: %d\n',nElems_tmp) 0328 error('stopping...') 0329 end 0330 lin = fgetl(fid); 0331 lin = fgetl(fid); 0332 lin = fgetl(fid); 0333 lin = fgetl(fid); % extra one for the new format from SMS 10.1, I think 0334 C2 = textscan(fid, '%f', nVerts); 0335 h = C2{1}; 0336 have_bath = true; 0337 0338 clear C2 0339 0340 fclose(fid); 0341 0342 elseif bath_range ~= 0 0343 have_bath = true; 0344 end 0345 0346 % Make sure we have positive depths 0347 if sum(h > 0) < sum(h < 0) 0348 h = -h; 0349 end 0350 0351 %-------------------------------------------------------------------------- 0352 % Project if desired by user 0353 %-------------------------------------------------------------------------- 0354 if userproject 0355 if strcmpi(coordinate, 'cartesian') 0356 fprintf('inverse projecting to get (lon,lat)\n') 0357 [lon, lat] = my_project(x, y, 'inverse'); 0358 have_lonlat = true; 0359 elseif strcmpi(coordinate, 'spherical') 0360 fprintf('forward projecting to get (x,y)\n') 0361 [x, y] = my_project(lon, lat, 'forward'); 0362 have_xy = true; 0363 else 0364 warning('Unrecognised coordinate system (%s). Valid values are ''spherical'' and ''cartesian''.', coordinate) 0365 end 0366 end 0367 0368 %-------------------------------------------------------------------------- 0369 % Transfer to Mesh structure 0370 %-------------------------------------------------------------------------- 0371 0372 Mobj.nVerts = nVerts; 0373 Mobj.nElems = nElems; 0374 Mobj.nativeCoords = coordinate; 0375 0376 Mobj.ts = ts; 0377 Mobj.h = h; 0378 Mobj.tri = tri; 0379 0380 if have_lonlat 0381 Mobj.have_lonlat = have_lonlat; 0382 Mobj.lon = lon; 0383 Mobj.lat = lat; 0384 if ~have_xy 0385 Mobj.x = zeros(size(lon)); 0386 Mobj.y = zeros(size(lat)); 0387 end 0388 % Add element spherical coordinates too. 0389 Mobj.lonc = nodes2elems(lon, Mobj); 0390 Mobj.latc = nodes2elems(lat, Mobj); 0391 end 0392 if have_xy 0393 Mobj.have_xy = have_xy; 0394 Mobj.x = x; 0395 Mobj.y = y; 0396 if ~have_lonlat 0397 Mobj.lon = zeros(size(x)); 0398 Mobj.lat = zeros(size(y)); 0399 end 0400 % Add element cartesian coordinates too. 0401 Mobj.xc = nodes2elems(x, Mobj); 0402 Mobj.yc = nodes2elems(y, Mobj); 0403 end 0404 if have_bath 0405 Mobj.have_bath = have_bath; 0406 end 0407 if have_strings 0408 Mobj.have_strings = have_strings; 0409 Mobj.read_obc_nodes = read_obc_nodes; 0410 else 0411 Mobj.have_strings = false; 0412 end 0413 if exist('addCoriolis', 'var') && addCoriolis 0414 Mobj.have_cor = true; 0415 end 0416 0417 assert(isfield(Mobj, 'x'), 'No coordinate data provided. Check your inputs and try again.') 0418 0419 % Make a depth array for the element centres. 0420 Mobj.hc = nodes2elems(h, Mobj); 0421 0422 % Add element spherical coordinates too. 0423 Mobj.lonc = nodes2elems(lon, Mobj); 0424 Mobj.latc = nodes2elems(lat, Mobj); 0425 0426 %-------------------------------------------------------------------------- 0427 % Add the Coriolis values 0428 %-------------------------------------------------------------------------- 0429 if exist('addCoriolis', 'var') && addCoriolis 0430 Mobj = add_coriolis(Mobj, 'uselatitude'); 0431 end 0432 0433 if ftbverbose 0434 fprintf('end : %s\n', subname) 0435 end