


Fix unstructured grid points inside the given boundary.
[adjx, adjy] = fix_inside_boundary(x, y, node_ids, ang_thresh)
DESCRIPTION:
Find the coordinates of points which are normal to the open boundary
described by the coordinates (x(node_ids), y(node_ids)). The distance
from the boundary is determined from the mean of the length of the two
adjacent boundary element lengths. Once the 'ideal' position has been
identified, find the closest existing nodal position and change it to
the 'ideal' position.
The resulting x2 and y2 coordinates can be exported to 2dm to be
checked in SMS for mesh quality with the fvcom-toolbox function
write_SMS_2dm.
INPUT:
x, y - Unstructured grid coordinates.
node_ids - List of IDs of the nodes within the grid which are on the
open boundary of interest.
ang_thresh - [optional] Specify a minimum angle in degrees between the
two adjacent nodal positions to deal with corners better.
OUTPUT:
adjx, adjy - New unstructured grid coordinate pairs in which the points
just inside the open boundary have been adjusted so as to
bisect the angle formed by the two adjacent boundary
faces.
EXAMPLE USAGE:
[adjx, adjy] = fix_inside_boundary(Mobj.x, Mobj.y, Mobj.read_obc_nodes{1}, 90)
NOTES:
This works best with cartesian coordinates but will work with spherical
too, although the angles for large elements will be incorrect.
Secondly, this will sometimes place put points outside the model domain
(though I'm not yet sure why). The net result is that you have to
re-edit the grid SMS by deleting that particular node and recreating
the triangulation manually.
Author(s):
Pierre Cazenave (Plymouth Marine Laboratory)
Revision history:
2013-03-11 First version.
2013-03-19 Add optional minimum angle support.
==========================================================================

0001 function [adjx, adjy] = fix_inside_boundary(x, y, node_ids, ang_thresh) 0002 % Fix unstructured grid points inside the given boundary. 0003 % 0004 % [adjx, adjy] = fix_inside_boundary(x, y, node_ids, ang_thresh) 0005 % 0006 % DESCRIPTION: 0007 % Find the coordinates of points which are normal to the open boundary 0008 % described by the coordinates (x(node_ids), y(node_ids)). The distance 0009 % from the boundary is determined from the mean of the length of the two 0010 % adjacent boundary element lengths. Once the 'ideal' position has been 0011 % identified, find the closest existing nodal position and change it to 0012 % the 'ideal' position. 0013 % 0014 % The resulting x2 and y2 coordinates can be exported to 2dm to be 0015 % checked in SMS for mesh quality with the fvcom-toolbox function 0016 % write_SMS_2dm. 0017 % 0018 % INPUT: 0019 % x, y - Unstructured grid coordinates. 0020 % node_ids - List of IDs of the nodes within the grid which are on the 0021 % open boundary of interest. 0022 % ang_thresh - [optional] Specify a minimum angle in degrees between the 0023 % two adjacent nodal positions to deal with corners better. 0024 % 0025 % OUTPUT: 0026 % adjx, adjy - New unstructured grid coordinate pairs in which the points 0027 % just inside the open boundary have been adjusted so as to 0028 % bisect the angle formed by the two adjacent boundary 0029 % faces. 0030 % 0031 % EXAMPLE USAGE: 0032 % [adjx, adjy] = fix_inside_boundary(Mobj.x, Mobj.y, Mobj.read_obc_nodes{1}, 90) 0033 % 0034 % NOTES: 0035 % This works best with cartesian coordinates but will work with spherical 0036 % too, although the angles for large elements will be incorrect. 0037 % Secondly, this will sometimes place put points outside the model domain 0038 % (though I'm not yet sure why). The net result is that you have to 0039 % re-edit the grid SMS by deleting that particular node and recreating 0040 % the triangulation manually. 0041 % 0042 % Author(s): 0043 % Pierre Cazenave (Plymouth Marine Laboratory) 0044 % 0045 % Revision history: 0046 % 2013-03-11 First version. 0047 % 2013-03-19 Add optional minimum angle support. 0048 % 0049 %========================================================================== 0050 0051 subname = 'fix_inside_boundary'; 0052 0053 global ftbverbose 0054 if ftbverbose 0055 fprintf('\n'); fprintf(['begin : ' subname '\n']); 0056 end 0057 0058 % Check the inputs 0059 if length(x) ~= length(y) 0060 error('Size of inputs (x, y) do not match.') 0061 else 0062 % Set the number of points in the boundary 0063 np = length(x(node_ids)); 0064 end 0065 0066 if nargin == 4 0067 minAng = true; 0068 else 0069 minAng = false; 0070 end 0071 0072 normx = nan(np, 1); 0073 normy = nan(np, 1); 0074 0075 % Order the boundary points in clockwise order. 0076 [boundx, boundy] = poly2cw(x(node_ids), y(node_ids)); 0077 0078 % Create an array for the list of indices which have been moved which are 0079 % adjacent to the open boundary. 0080 seen = nan(np, 1); 0081 0082 % Create the output arrays from the input data. These values will be 0083 % adjusted for the nodes adjacent to the open boundary defined by node_ids. 0084 adjx = x; 0085 adjy = y; 0086 0087 for pp = 1:np 0088 % For each node, find the two closest nodes to it and use those as the 0089 % adjacent vectors. Doing this is slower than just assuming the nodes 0090 % are provided in a sorted order, but means we don't have to worry too 0091 % much about any irregularities from the sorting above (poly2cw). 0092 [~, idx] = sort(sqrt((boundx(pp) - boundx).^2 + (boundy(pp) - boundy).^2)); 0093 0094 % Get the coordinates of the two nearest points (skip the first closest 0095 % because that's the current point). 0096 [px1, py1] = deal(boundx(idx(2)), boundy(idx(2))); 0097 [px2, py2] = deal(boundx(idx(3)), boundy(idx(3))); 0098 0099 % Find the length of the edges of the triangle formed from the three 0100 % points we're currently considering. 1 and 2 are those adjacent and 3 0101 % is the remaining side. 0102 ln1 = sqrt((boundx(pp) - px1)^2 + (boundy(pp) - py1)^2); 0103 ln2 = sqrt((boundx(pp) - px2)^2 + (boundy(pp) - py2)^2); 0104 ln3 = sqrt((px1 - px2)^2 + (py1 - py2)^2); 0105 0106 % Find the angle between the two element edges and the current node 0107 % (cosine rule). Use the real component only for cases where the three 0108 % points lie on a straight line (in which case the angle should be 0109 % 180 degrees). 0110 ang1 = real(acosd((ln1^2 + ln2^2 - ln3^2) / (2 * ln1 * ln2))); 0111 ang1b = ang1 / 2; % bisect the angle 0112 0113 % Check if we've been given a threshold minimum angle and skip this 0114 % open boundary point if we have. 0115 if minAng 0116 if ang1 < ang_thresh 0117 continue 0118 end 0119 end 0120 0121 % Find the angle to the horizontal for the current node and one of the 0122 % other points. 0123 ang2 = atan2((py1 - boundy(pp)), (px1 - boundx(pp))) * (180 / pi); 0124 0125 % Find the difference between the two. 0126 ang3 = ang2 - ang1b; 0127 0128 % Now get the mean length of the two closest element edges and use that 0129 % to create the new point inside the boundary. Scale it to 90% of the 0130 % value to make a cleaner transition when we're shifting points below. 0131 ml = 0.9 * mean([ln1, ln2]); 0132 dy = ml * sind(ang3); 0133 dx = ml * cosd(ang3); 0134 0135 % Add the offsets to the current node to get the new node's position. 0136 [xx(1), yy(1)] = deal(boundx(pp) + dx, boundy(pp) + dy); 0137 [xx(2), yy(2)] = deal(boundx(pp) - dx, boundy(pp) - dy); 0138 0139 % Check which of the two sets above is inside the polygon defined by 0140 % the open boundary. 0141 if inpolygon(xx(1), yy(1), boundx, boundy) == 1 0142 [normx(pp, 1), normy(pp, 1)] = deal(xx(1), yy(1)); 0143 elseif inpolygon(xx(2), yy(2), boundx, boundy) == 1 0144 [normx(pp, 1), normy(pp, 1)] = deal(xx(2), yy(2)); 0145 else 0146 warning('Both versions of the calculated point are outside the model domain. Skipping.') 0147 continue 0148 end 0149 0150 % OK, so now we have a new point approximately orthogonal to the 0151 % current node, we can use its position to find the nearest existing 0152 % node inside the domain and replace its coordinates with the new 0153 % node's. 0154 [~, idx2] = sort(sqrt((normx(pp, 1) - x).^2 + (normy(pp, 1) - y).^2)); 0155 0156 % We need to check we haven't seen this node before (in 'seen') and 0157 % also that it's not an open boundary point. 0158 c = 1; 0159 while true 0160 if ismember(idx2(c), node_ids) || ismember(idx2(c), seen) 0161 % Keep going until we find one that's not been used before. 0162 c = c + 1; 0163 else 0164 break 0165 end 0166 end 0167 % Append to the list of coordinates we've seen so we don't move the 0168 % same node twice. 0169 seen(pp) = idx2(c); 0170 0171 % Replace the coordinates. 0172 adjx(idx2(c)) = normx(pp, 1); 0173 adjy(idx2(c)) = normy(pp, 1); 0174 0175 end 0176 0177 if ftbverbose 0178 fprintf('end : %s\n', subname) 0179 end 0180 0181 % Do a figure to see the effect 0182 % close all 0183 % h = figure(1); 0184 % 0185 % % Original node positions 0186 % plot(x, y, 'o', 'MarkerEdgeColor', [0.9, 0.1, 0.1], ... 0187 % 'MarkerFaceColor', [1, 0.1, 0.1]) 0188 % hold on 0189 % axis('equal', 'tight') 0190 % % Adjusted node positions 0191 % plot(adjx, adjy, 'o', 'MarkerEdgeColor', [0.1, 0.1, 0.8], ... 0192 % 'MarkerFaceColor', [0.1, 0.1, 0.9]) 0193 % 0194 % % Original triangulation 0195 % patch('Vertices', [x, y], 'Faces', Mobj.tri, 'CData', [], ... 0196 % 'edgecolor', [0.9, 0.1, 0.1], 'FaceColor', 'w', 'LineWidth', 1); 0197 % % New triangulation 0198 % patch('Vertices', [adjx, adjy], 'Faces', Mobj.tri, 'CData', [], ... 0199 % 'edgecolor', [0.1, 0.1, 0.8], 'FaceColor', 'w', 'LineWidth', 1); 0200 % 0201 % % Open boundary nodes 0202 % % plot(x(node_ids), y(node_ids), 'ko', 'MarkerFaceColor', 'k') 0203 % 0204 % legend('Original nodes', 'Adjusted nodes') 0205 % legend('BoxOff') 0206 % 0207 % xlabel('Eastings') 0208 % ylabel('Northings')