Home > fvcom_prepro > get_FVCOM_rivers.m

get_FVCOM_rivers

PURPOSE ^

Extract river discharges from the supplied river positions for the FVCOM

SYNOPSIS ^

function Mobj = get_FVCOM_rivers(Mobj, dist_thresh)

DESCRIPTION ^

 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.

==========================================================================

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SOURCE CODE ^

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

Generated on Wed 20-Feb-2019 16:06:01 by m2html © 2005