


Extract river discharges from the supplied river positions for the FVCOM
grid in Mobj.
get_FVCOM_rivers(Mobj, dist_thresh)
DESCRIPTION:
For the positions in Mobj.rivers.positions, find the nearest
unstructured grid node and extract the river discharge from
Mobj.rivers.discharge. If dist_thresh is specified, the river positions
must fall within the specified distance. If multiple rivers are
assigned to the same node, their discharges are summed. The resulting
river name is generated from the contributing rives, separated by a
hyphen.
INPUT:
Mobj - MATLAB mesh object containing:
* have_lonlat - boolean to check for spherical coordinates.
* lon, lat - positions for the unstructured grid.
* tri - triangulation table for the unstructured grid.
* nVerts - number of nodes in the grid.
* read_obc_nodes - open boundary node IDs.
* rivers - river data struct with the following fields:
- year - start year of the river data time series.
- positions - river positions in lon, lat.
- names - list of river names (whose order must match the
positions in xy).
- discharge - river discharge data (again, order of columns
must match the positions in Mobj.rivers.positions).
dist_thresh - [optional] maximum distance away from a river node beyond
which the search for an FVCOM node is abandoned. Units in degrees.
OUTPUT:
Mobj.river_flux - volume flux at the nodes within the model domain.
Mobj.river_nodes - node IDs for the rivers. At the moment, these are
point sources only. Eventually, some rivers may have to be split
over several nodes.
Mobj.river_names - river names which fall within the model domain. For
rivers where the discharge has been summed, the name is compoud,
with each contributing name separated by a hyphen (-).
Mobj.river_time - time series for the river discharge data
EXAMPLE USAGE:
Mobj = get_FVCOM_rivers(Mobj, 0.025)
Author(s):
Pierre Cazenave (Plymouth Marine Laboratory)
Karen Amoudry (National Oceanography Centre, Liverpool)
Revision history:
2013-03-27 - First version.
2013-04-15 - Removed the code to load the positions and discharge data
into separate functions so that this function can be purely about
finding the closest locations and the summing of discharges (if
necessary). The downside is the order of the discharges (columns) must
match the position arrays.
2013-05-21 - Add check to avoid setting nodes as river nodes when that
node is part of only one element. If this is not added, then the model
fills up the element without moving the water out of that element,
eventually leading to a crash, which is obviously not ideal. The fix
searches for another node in the element which is part of at least two
elements, thereby avoiding the "element filling" issue. Also updated
the help to list all the required fields in the Mobj.
2013-12-10 - Change the unique call to preserve the order by replacing
'first' with 'stable'. This requires a relatively modern MATLAB
(post-2011b).
2014-05-20 Set boolean flag to true to indicate rivers and add number
of fields to the Mobj.
==========================================================================

0001 function Mobj = get_FVCOM_rivers(Mobj, dist_thresh) 0002 % Extract river discharges from the supplied river positions for the FVCOM 0003 % grid in Mobj. 0004 % 0005 % get_FVCOM_rivers(Mobj, dist_thresh) 0006 % 0007 % DESCRIPTION: 0008 % For the positions in Mobj.rivers.positions, find the nearest 0009 % unstructured grid node and extract the river discharge from 0010 % Mobj.rivers.discharge. If dist_thresh is specified, the river positions 0011 % must fall within the specified distance. If multiple rivers are 0012 % assigned to the same node, their discharges are summed. The resulting 0013 % river name is generated from the contributing rives, separated by a 0014 % hyphen. 0015 % 0016 % INPUT: 0017 % Mobj - MATLAB mesh object containing: 0018 % * have_lonlat - boolean to check for spherical coordinates. 0019 % * lon, lat - positions for the unstructured grid. 0020 % * tri - triangulation table for the unstructured grid. 0021 % * nVerts - number of nodes in the grid. 0022 % * read_obc_nodes - open boundary node IDs. 0023 % * rivers - river data struct with the following fields: 0024 % - year - start year of the river data time series. 0025 % - positions - river positions in lon, lat. 0026 % - names - list of river names (whose order must match the 0027 % positions in xy). 0028 % - discharge - river discharge data (again, order of columns 0029 % must match the positions in Mobj.rivers.positions). 0030 % dist_thresh - [optional] maximum distance away from a river node beyond 0031 % which the search for an FVCOM node is abandoned. Units in degrees. 0032 % 0033 % OUTPUT: 0034 % Mobj.river_flux - volume flux at the nodes within the model domain. 0035 % Mobj.river_nodes - node IDs for the rivers. At the moment, these are 0036 % point sources only. Eventually, some rivers may have to be split 0037 % over several nodes. 0038 % Mobj.river_names - river names which fall within the model domain. For 0039 % rivers where the discharge has been summed, the name is compoud, 0040 % with each contributing name separated by a hyphen (-). 0041 % Mobj.river_time - time series for the river discharge data 0042 % 0043 % EXAMPLE USAGE: 0044 % Mobj = get_FVCOM_rivers(Mobj, 0.025) 0045 % 0046 % Author(s): 0047 % Pierre Cazenave (Plymouth Marine Laboratory) 0048 % Karen Amoudry (National Oceanography Centre, Liverpool) 0049 % 0050 % Revision history: 0051 % 2013-03-27 - First version. 0052 % 2013-04-15 - Removed the code to load the positions and discharge data 0053 % into separate functions so that this function can be purely about 0054 % finding the closest locations and the summing of discharges (if 0055 % necessary). The downside is the order of the discharges (columns) must 0056 % match the position arrays. 0057 % 2013-05-21 - Add check to avoid setting nodes as river nodes when that 0058 % node is part of only one element. If this is not added, then the model 0059 % fills up the element without moving the water out of that element, 0060 % eventually leading to a crash, which is obviously not ideal. The fix 0061 % searches for another node in the element which is part of at least two 0062 % elements, thereby avoiding the "element filling" issue. Also updated 0063 % the help to list all the required fields in the Mobj. 0064 % 2013-12-10 - Change the unique call to preserve the order by replacing 0065 % 'first' with 'stable'. This requires a relatively modern MATLAB 0066 % (post-2011b). 0067 % 2014-05-20 Set boolean flag to true to indicate rivers and add number 0068 % of fields to the Mobj. 0069 % 0070 %========================================================================== 0071 0072 subname = 'get_FVCOM_rivers'; 0073 0074 global ftbverbose 0075 if ftbverbose 0076 fprintf('\nbegin : %s \n', subname) 0077 end 0078 0079 % Check inputs 0080 if ~Mobj.have_lonlat 0081 error('Require unstructured grid positions in lon/lat format to compare against supplied river positions.') 0082 end 0083 0084 % Separate the inputs into separate arrays. 0085 fvcom_name = Mobj.rivers.names; 0086 fvcom_xy = Mobj.rivers.positions; 0087 polcoms_flow = Mobj.rivers.discharge; 0088 0089 % We have to be careful because POLCOMS has duplicate river names. 0090 % 0091 % "This has made a lot of people very angry and has been widely regarded 0092 % as a bad move." 0093 % 0094 % For duplicates, we need, therefore, to work out a way to handle them 0095 % elegantly. We will assume that rivers with the same name are close to one 0096 % another. As such, we'll sum their discharges. 0097 [~, di] = unique(fvcom_name, 'stable'); % stable preserves order. 0098 fv_dupes = 1:length(fvcom_name); 0099 fv_dupes(di) = []; % index of duplicates (does this work with more than two?) 0100 0101 % Iterate through the list of duplicates and clear them out in the names, 0102 % positions and discharge. We have to ensure that the order of the 0103 % deduplicated positions and discharge are the same, otherwise we'll end up 0104 % with the wrong discharge for a given river position, which would be bad. 0105 for i = 1:length(fv_dupes) 0106 dup_name = fvcom_name(fv_dupes(i)); 0107 didx = strmatch(dup_name, fvcom_name, 'exact'); 0108 0109 % Sum the duplicate rivers' data. 0110 dup_discharge = sum(polcoms_flow(:, didx), 2); 0111 0112 % Remove the original values and put the summed data at the end. 0113 polcoms_flow(:, didx) = []; 0114 polcoms_flow = [polcoms_flow, dup_discharge]; 0115 0116 % Now remove the duplicates from the FVCOM data. 0117 fvcom_name{length(fvcom_name) + 1} = fvcom_name{fv_dupes(i)}; 0118 fvcom_name(didx) = []; 0119 fvcom_xy = [fvcom_xy; fvcom_xy(fv_dupes(i), :)]; 0120 fvcom_xy(didx, :) = []; 0121 end 0122 0123 % Get number of times and rivers from the deduplicated data. 0124 fv_nr = length(fvcom_name); 0125 [pc_nt, ~] = size(polcoms_flow); 0126 0127 clear didx dup_discharge 0128 0129 0130 % Check each location in the FVCOM rivers file against the grid in Mobj and 0131 % for the indices within the dist_thresh, extract the relevant time series 0132 % data. 0133 0134 vc = 0; % valid FVCOM boundary node counter 0135 0136 % We need to find the unstructured grid boundary nodes and exclude the open 0137 % boundary nodes from them. This will be our list of potential candidates 0138 % for the river nodes (i.e. the land coastline). 0139 [~, ~, ~, bnd] = connectivity([Mobj.lon, Mobj.lat], Mobj.tri); 0140 boundary_nodes = 1:Mobj.nVerts; 0141 boundary_nodes = boundary_nodes(bnd); 0142 coast_nodes = boundary_nodes(~ismember(boundary_nodes, [Mobj.read_obc_nodes{:}])); 0143 tlon = Mobj.lon(coast_nodes); 0144 tlat = Mobj.lat(coast_nodes); 0145 0146 fv_obc = nan; 0147 fvcom_names = cell(0); 0148 fv_riv_idx = nan; 0149 0150 for ff = 1:fv_nr 0151 % Find the open boundary node closest to this river. 0152 fv_dist = sqrt( ... 0153 (fvcom_xy(ff, 1) - Mobj.lon(coast_nodes)).^2 + ... 0154 (fvcom_xy(ff, 2) - Mobj.lat(coast_nodes)).^2); 0155 [c, idx] = min(fv_dist); 0156 if c > dist_thresh && dist_thresh ~= -1 % -1 is for no distance check 0157 if ftbverbose 0158 fprintf('\tskipping river %s (%f, %f)\n', fvcom_name{ff}, fvcom_xy(ff, 1), fvcom_xy(ff, 2)) 0159 end 0160 continue 0161 else 0162 if ftbverbose 0163 fprintf('candidate river %s found (%f, %f)... ', fvcom_name{ff}, fvcom_xy(ff, 1), fvcom_xy(ff, 2)) 0164 end 0165 end 0166 0167 vc = vc + 1; 0168 0169 % We need to make sure the element in which this node occurs does not 0170 % have two land boundaries (otherwise the model sometimes just fills up 0171 % that element without releasing the water into the adjacent element). 0172 0173 % Find the other nodes which are joined to the node we've just found. 0174 % We don't need the column to get the other nodes in the element, only 0175 % the row is required. 0176 [row, ~] = find(Mobj.tri == coast_nodes(idx)); 0177 0178 if length(row) == 1 0179 % This is a bad node because it is a part of only one element. The 0180 % rivers need two adjacent elements to work reliably (?). So, we 0181 % need to repeat the process above until we find a node that's 0182 % connected to two elements. We'll try the other nodes in the 0183 % current element before searching the rest of the coastline (which 0184 % is computationally expensive). 0185 0186 % Remove the current node index from the list of candidates (i.e. 0187 % leave only the two other nodes in the element). 0188 mask = Mobj.tri(row, :) ~= coast_nodes(idx); 0189 n_tri = Mobj.tri(row, mask); 0190 0191 % Remove values which aren't coastline values (we don't want to set 0192 % the river node to an open water node). 0193 n_tri = intersect(n_tri, coast_nodes); 0194 0195 % Of the remaining nodes in the element, find the closest one to 0196 % the original river location (in fvcom_xy). 0197 [~, n_idx] = sort(sqrt( ... 0198 (fvcom_xy(ff, 1) - Mobj.lon(n_tri)).^2 ... 0199 + (fvcom_xy(ff, 2) - Mobj.lon(n_tri)).^2)); 0200 0201 [row_2, ~] = find(Mobj.tri == n_tri(n_idx(1))); 0202 if length(n_idx) > 1 0203 [row_3, ~] = find(Mobj.tri == n_tri(n_idx(2))); 0204 end 0205 % Closest first 0206 if length(row_2) > 1 0207 idx = find(coast_nodes == n_tri(n_idx(1))); 0208 % The other one (only if we have more than one node to consider). 0209 elseif length(n_idx) > 1 && length(row_3) > 1 0210 idx = find(coast_nodes == n_tri(n_idx(2))); 0211 % OK, we need to search across all the other coastline nodes. 0212 else 0213 % TODO: Implement a search of all the other coastline nodes. 0214 % My testing indicates that we never get here (at least for the 0215 % grids I've tested). I'd be interested to see the mesh which 0216 % does get here... 0217 continue 0218 end 0219 0220 end 0221 0222 % Add it to the list of valid rivers 0223 fv_obc(vc) = coast_nodes(idx); 0224 0225 % We are assuming that the river discharge data array y-dimension is 0226 % ordered the same as the positions in fvcom_xy. If they are not, then 0227 % the discharges for the rivers will be incorrect (i.e. you might put 0228 % the Severn discharge somewhere in the Baltic). 0229 fvcom_names{vc} = fvcom_name{ff}; 0230 fv_riv_idx(vc) = ff; 0231 fv_flow(:, vc) = polcoms_flow(:, ff); 0232 if ftbverbose 0233 fprintf('added (%f, %f).\n', Mobj.lon(fv_obc(vc)), Mobj.lat(fv_obc(vc))) 0234 end 0235 end 0236 0237 % Now we've got a list and some of the nodes will be duplicates. Sum the 0238 % discharge values assigned to those nodes. 0239 fv_uniq_obc = unique(fv_obc); 0240 0241 fv_uniq_flow = nan(pc_nt, length(fv_uniq_obc)); 0242 fv_uniq_names = cell(length(fv_uniq_obc), 1); 0243 0244 fv_idx = 1:length(fvcom_names); 0245 for nn = 1:length(fv_uniq_obc) 0246 0247 dn = fv_idx(fv_obc == fv_uniq_obc(nn)); 0248 0249 fv_uniq_flow(:, nn) = sum(fv_flow(:, dn), 2); 0250 % Concatenate the river names so we know at least which rivers' 0251 % discharges have been summed. 0252 s = fvcom_names(dn); 0253 s = [sprintf('%s-', s{1:end-1}, s{end})]; 0254 fv_uniq_names{nn} = s(1:end-1); % lose the trailing -. 0255 0256 end 0257 0258 % Assign the relevant arrays to the Mobj. 0259 Mobj.river_nodes = fv_uniq_obc; 0260 Mobj.river_flux = fv_uniq_flow; 0261 Mobj.river_names = fv_uniq_names; 0262 Mobj.have_rivers = true; 0263 Mobj.nRivers = length(fv_uniq_obc); 0264 0265 % Create a Modified Julian Day time series starting at January 1st for the 0266 % year in Mobj.rivers.year. 0267 rtimes = datevec( ... 0268 datenum([Mobj.rivers.year, 1, 1, 0, 0, 0]): ... 0269 datenum([Mobj.rivers.year, 1, 1, 0, 0, 0]) + pc_nt - 1 ... 0270 ); 0271 Mobj.river_time = nan(pc_nt, 1); 0272 for tt = 1:pc_nt 0273 Mobj.river_time(tt) = greg2mjulian( ... 0274 rtimes(tt, 1), rtimes(tt, 2), rtimes(tt, 3), ... 0275 rtimes(tt, 4), rtimes(tt, 5), rtimes(tt, 6) ... 0276 ); 0277 end 0278 0279 % Figure to check what's going on with identifying river nodes 0280 % figure 0281 % plot(fvcom_xy(:, 1), fvcom_xy(:, 2), 'o', 'MarkerFaceColor', 'b') 0282 % hold on 0283 % plot(Mobj.lon(bnd), Mobj.lat(bnd), 'go', 'MarkerFaceColor', 'g') 0284 % axis('equal', 'tight') 0285 % plot(Mobj.lon(coast_nodes), Mobj.lat(coast_nodes), 'ro') 0286 % plot(Mobj.lon(Mobj.river_nodes), Mobj.lat(Mobj.river_nodes), 'ko', 'MarkerFaceColor', 'k') 0287 % text(Mobj.lon(Mobj.river_nodes) + 0.025, Mobj.lat(Mobj.river_nodes) + 0.025, Mobj.river_names) 0288 % axis([min(Mobj.lon), max(Mobj.lon), min(Mobj.lat), max(Mobj.lat)]) 0289 % legend('POLCOMS nodes', 'Grid boundary', 'Land nodes', 'Selected nodes', 'Location', 'NorthOutside', 'Orientation', 'Horizontal') 0290 % legend('BoxOff') 0291 0292 0293 if ftbverbose 0294 fprintf(['end : ' subname '\n']) 0295 end