You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
13 KiB
386 lines
13 KiB
function [d_min, varargout] = p_poly_dist(xp, yp, xv, yv, varargin)
|
|
%p_poly_dist Find minimum distances from points to a polyline or to a
|
|
% closed polygon.
|
|
%
|
|
% Description:
|
|
% Compute the distances from each one of a set of np points p(1), p(2),...
|
|
% p(np) on a 2D plane to a polyline or a closed polygon. Polyline is
|
|
% defined as a set of nv-1 segments connecting nv ordered vertices v(1),
|
|
% v(2), ..., v(nv). The polyline can optionally be treated as a closed
|
|
% polygon.
|
|
% Distance from point j to a segment k is defined as a distance from this
|
|
% point to a straight line passing through vertices v(k) and v(k+1), when
|
|
% the projection of point j on this line falls INSIDE segment k; and to
|
|
% the closest of v(k) or v(k+1) vertices, when the projection falls OUTSIDE
|
|
% segment k. Distance from point j to a polyline is defined as a minimum of
|
|
% this point's distances to all segments.
|
|
% In a case when the projected point fall OUTSIDE of all polyline segments,
|
|
% the distance to a closest vertex of the polyline is returned
|
|
%
|
|
% Input arguments:
|
|
% Required:
|
|
% [d_min, varargout] = p_poly_dist(xp, yp, xv, yv)
|
|
% xp - vector of points X coordinates (1 X np)
|
|
% yp - vector of points Y coordinates (1 X np)
|
|
% xv - vector of polygon vertices' X coordinates (1 X nv)
|
|
% yv - vector of polygon vertices' Y coordinates (1 X nv)
|
|
%
|
|
% Optional:
|
|
% [d_min, varargout] = p_poly_dist(xp, yp, xv, yv, find_in_out)
|
|
%
|
|
% find_in_out - logical flag. When true, the polyline is treated as a
|
|
% closed polygon, and each input point is checked wether it is inside or
|
|
% outside of this polygon. In such case, an open polyline is automatically
|
|
% closed by adding a last point identical to a first one.
|
|
% Note: when this function is called with ver. 1.0 signature, i.e:
|
|
% d = p_poly_dist(xp, yp, xv, yv)
|
|
% the flag find_in_out is assumed to be true, to keep the functionality
|
|
% compatible with a previous version.
|
|
%
|
|
% Output arguments:
|
|
% Required:
|
|
% d_min = p_poly_dist(xp, yp, xv, yv, varargin)
|
|
%
|
|
% d_min - vector of distances (1 X np) from points to polyline. This is
|
|
% defined as either a distance from a point to it's projection on a line
|
|
% that passes through a pair of consecutive polyline vertices, when this
|
|
% projection falls inside a segment; or as a distance from a point to a
|
|
% closest vertex, when the projection falls outside of a segment. When
|
|
% find_in_out input flag is true, the polyline is assumed to be a closed
|
|
% polygon, and distances of points INSIDE this polygon are defined as
|
|
% negative.
|
|
%
|
|
% Optional:
|
|
% [d_min, x_d_min, y_d_min, is_vertex, xc, yc, idx_c, Cer, Ppr] =
|
|
% p_poly_dist(xp, yp, xv, yv, varargin);
|
|
%
|
|
% x_d_min - vector (1 X np) of X coordinates of the closest points of
|
|
% polyline.
|
|
%
|
|
% y_d_min - vector (1 X np) of Y coordinates of the closest points of
|
|
% polyline.
|
|
%
|
|
% is_vertex - logical vector (1 X np). If is_vertex(j)==true, the closest
|
|
% polyline point to a point (xp(j), yp(j)) is a vertex. Otherwise, this
|
|
% point is a projection on polyline's segment.
|
|
%
|
|
% idx_c - vector (1 X np) of indices of segments that contain the closest
|
|
% point. For instance, idx_c(2) == 4 means that the polyline point closest
|
|
% to point 2 belongs to segment 4
|
|
%
|
|
% xc - an array (np X nv-1) containing X coordinates of all projected
|
|
% points. xc(j,k) is an X coordinate of a projection of point j on a
|
|
% segment k
|
|
%
|
|
% yc - an array (np X nv-1) containing Y coordinates of all projected
|
|
% points. yc(j,k) is Y coordinate of a projection of point j on a
|
|
% segment k
|
|
%
|
|
% is_in_seg - logical array (np X nv-1). If is_in_seg(j,k) == true, the
|
|
% projected point j with coordinates (xc(j,k), yc(j,k)) lies INSIDE the
|
|
% segment k
|
|
%
|
|
% Cer - a 3D array (2 X 2 X nv-1). Each 2 X 2 slice represents a rotation
|
|
% matrix from an input Cartesian coordinate system to a system defined
|
|
% by polyline segments.
|
|
%
|
|
% Ppr - 3D array of size 2 X np X (nv-1). Ppr(1,j,k) is an X coordinate
|
|
% of point j in coordinate systems defined by segment k. Ppr(2,j,k) is its
|
|
% Y coordinate.
|
|
%
|
|
% Routines: p_poly_dist.m
|
|
% Revision history:
|
|
% Oct 2, 2015 - version 2.0 (Michael Yoshpe). The original ver. 1.0
|
|
% function was completely redesigned. The main changes introduced in
|
|
% ver. 2.0:
|
|
% 1. Distances to polyline (instead of a closed polygon in ver. 1.0) are
|
|
% returned. The polyline can optionally be treated as a closed polygon.
|
|
% 2. Distances from multiple points to the same polyline can be found
|
|
% 3. The algorithm for finding the closest points is now based on
|
|
% coordinate system transformation. The new algorithm avoids numerical
|
|
% problems that ver. 1.0 algorithm could experience in "ill-conditioned"
|
|
% cases.
|
|
% 4. Many optional output variables were added. In particular, the closest
|
|
% points on polyline can be returned.
|
|
% 5. Added input validity checks
|
|
% 7/9/2006 - case when all projections are outside of polygon ribs
|
|
% 23/5/2004 - created by Michael Yoshpe
|
|
% Remarks:
|
|
|
|
|
|
find_in_out = false;
|
|
|
|
if(nargin >= 5)
|
|
find_in_out = varargin{1,1};
|
|
elseif((nargin==4) && (nargout==1)) % mimic ver. 1.0 functionality
|
|
find_in_out = true;
|
|
end
|
|
|
|
% number of points and number of vertices in polyline
|
|
nv = length(xv);
|
|
np = length(xp);
|
|
|
|
if(nv < 2)
|
|
error('Polyline must have at least 2 vertices');
|
|
end
|
|
|
|
if((find_in_out == true) && (nv < 3))
|
|
error('Polygon must have at least 3 vertices');
|
|
end
|
|
|
|
% if finding wether the points are inside or outsite the polygon is
|
|
% required, make sure the verices represent a closed polygon
|
|
if(find_in_out)
|
|
% If (xv,yv) is not closed, close it.
|
|
nv = length(xv);
|
|
if ((xv(1) ~= xv(nv)) || (yv(1) ~= yv(nv)))
|
|
xv = [xv(:)' xv(1)];
|
|
yv = [yv(:)' yv(1)];
|
|
nv = nv + 1;
|
|
end
|
|
end
|
|
|
|
% Cartesian coordinates of the polyline vertices
|
|
Pv = [xv(:) yv(:)];
|
|
|
|
% Cartesian coordinates of the given points
|
|
Pp = [xp(:) yp(:)];
|
|
|
|
% matrix of distances between all points and all vertices
|
|
% dpv(j,k) - distance from point j to vertex k
|
|
dpv = zeros(np,nv); % required by Maltab Coder
|
|
dpv(:,:) = hypot((repmat(Pv(:,1)', [np 1])-repmat(Pp(:,1), [1 nv])),...
|
|
(repmat(Pv(:,2)', [np 1])-repmat(Pp(:,2), [1 nv])));
|
|
|
|
% Find the vector of minimum distances to vertices.
|
|
[dpv_min, I_dpv_min] = min(abs(dpv),[],2);
|
|
|
|
% coordinates of consecutive vertices
|
|
P1 = Pv(1:(end-1),:);
|
|
P2 = Pv(2:end,:);
|
|
dv = P2 - P1;
|
|
|
|
% vector of distances between each pair of consecutive vertices
|
|
vds = hypot(dv(:,1), dv(:,2));
|
|
|
|
% check for identical points
|
|
idx = find(vds < 10*eps);
|
|
if(~isempty(idx))
|
|
% Required by Matlab Coder
|
|
fprintf(1, '%s', 'Points ');
|
|
for j=1:length(idx)
|
|
fprintf(1, '%i ', int64(idx(j)));
|
|
end
|
|
fprintf(1, '%s\n', ' of the polyline are identical');
|
|
assert(false);
|
|
|
|
% error(['Points ' num2str(idx) ' of the polyline are identical']);
|
|
end
|
|
|
|
% check for a case when closed polygon's vertices lie on a stright line,
|
|
% i.e. the distance between the last and first vertices is equal to the sum
|
|
% of all segments except the last
|
|
if(find_in_out)
|
|
s = cumsum(vds);
|
|
if((s(end-1) - vds(end)) < 10*eps)
|
|
error('Polygon vertices should not lie on a straight line');
|
|
end
|
|
end
|
|
|
|
% Each pair of consecutive vertices P1(j), P2(j) defines a rotated
|
|
% coordinate system with origin at P1(j), and x axis along the vector
|
|
% (P2(j)-P1(j)).
|
|
% Build the rotation matrix Cer from original to rotated system
|
|
ctheta = dv(:,1)./vds;
|
|
stheta = dv(:,2)./vds;
|
|
Cer = zeros(2,2,nv-1);
|
|
Cer(1,1,:) = ctheta;
|
|
Cer(1,2,:) = stheta;
|
|
Cer(2,1,:) = -stheta;
|
|
Cer(2,2,:) = ctheta;
|
|
|
|
% Build the origin translation vector P1r in rotated frame by rotating the
|
|
% P1 vector
|
|
P1r = [(ctheta.*P1(:,1) + stheta.*P1(:,2)),...
|
|
-stheta.*P1(:,1) + ctheta.*P1(:,2)];
|
|
|
|
Cer21 = zeros(2, nv-1);
|
|
Cer22 = zeros(2, nv-1);
|
|
|
|
Cer21(:,:) = Cer(1,:,:);
|
|
Cer22(:,:) = Cer(2,:,:);
|
|
|
|
% Ppr is a 3D array of size 2 * np * (nv-1). Ppr(1,j,k) is an X coordinate
|
|
% of point j in coordinate systems defined by segment k. Ppr(2,j,k) is its
|
|
% Y coordinate.
|
|
|
|
% Rotation. Doing it manually, since Matlab cannot multiply 2D slices of ND
|
|
% arrays.
|
|
Ppr = zeros(2,np,nv-1); % required by Matlab Coder
|
|
Ppr(1,:,:) = Pp*Cer21;
|
|
Ppr(2,:,:) = Pp*Cer22;
|
|
|
|
% translation
|
|
Ppr(1,:,:) = Ppr(1,:,:) - permute(repmat(P1r(:,1), [1 1 np]), [2 3 1]);
|
|
Ppr(2,:,:) = Ppr(2,:,:) - permute(repmat(P1r(:,2), [1 1 np]), [2 3 1]);
|
|
|
|
% Pcr is a 3D array of size 2 * np * (nv-1) that holds the projections of
|
|
% points on X axis of rotated coordinate systems. Pcr(1,j,k) is an X
|
|
% coordinate of point j in coordinate systems defined by segment k.
|
|
% Pcr(2,j,k) is its Y coordinate, which is identically zero for projected
|
|
% point.
|
|
Pcr = zeros(size(Ppr));
|
|
Pcr(1, :, :) = Ppr(1,:,:);
|
|
Pcr(2, :, :) = 0;
|
|
|
|
% Cre is a rotation matrix from rotated to original system
|
|
Cre = permute(Cer, [2 1 3]);
|
|
|
|
% Pce is a 3D array of size 2 * np * (nv-1) that holds the projections of
|
|
% points on a segment in original coordinate systems. Pce(1,j,k) is an X
|
|
% coordinate of the projection of point j on segment k.
|
|
% Pce(2,j,k) is its Y coordinate
|
|
Pce = zeros(2,np,(nv-1));
|
|
Pce(1,:,:) = Pcr(1,:,:).*repmat(Cre(1,1,:), [1 np 1]) + ...
|
|
Pcr(2,:,:).*repmat(Cre(1,2,:), [1 np 1]);
|
|
Pce(2,:,:) = Pcr(1,:,:).*repmat(Cre(2,1,:), [1 np 1]) + ...
|
|
Pcr(2,:,:).*repmat(Cre(2,2,:), [1 np 1]);
|
|
|
|
% Adding the P1 vector
|
|
Pce(1,:,:) = Pce(1,:,:) + permute(repmat(P1(:,1), [1 1 np]), [2 3 1]);
|
|
Pce(2,:,:) = Pce(2,:,:) + permute(repmat(P1(:,2), [1 1 np]), [2 3 1]);
|
|
|
|
% x and y coordinates of the projected (cross-over) points in original
|
|
% coordinate frame
|
|
xc = zeros(np, (nv-1));
|
|
yc = zeros(np, (nv-1));
|
|
xc(:,:) = Pce(1,:,:);
|
|
yc(:,:) = Pce(2,:,:);
|
|
|
|
r = zeros(np,(nv-1));
|
|
cr = zeros(np,(nv-1));
|
|
r(:,:) = Ppr(1,:,:);
|
|
cr(:,:) = Ppr(2,:,:);
|
|
|
|
% Find the projections that fall inside the segments
|
|
is_in_seg = (r>0) & (r<repmat(vds(:)', [np 1 1]));
|
|
|
|
% find the minimum distances from points to their projections that fall
|
|
% inside the segments (note, that for some points these might not exist,
|
|
% which means that closest points are vertices)
|
|
B = NaN(np,nv-1);
|
|
B(is_in_seg) = cr(is_in_seg);
|
|
|
|
[cr_min, I_cr_min] = min(abs(B),[],2);
|
|
|
|
% Build the logical index which is true for members that are vertices,
|
|
% false otherwise. Case of NaN in cr_min means that projected point falls
|
|
% outside of a segment, so in such case the closest point is vertex.
|
|
|
|
% point's projection falls outside of ALL segments
|
|
cond1 = isnan(cr_min);
|
|
|
|
% point's projection falls inside some segments, find segments for which
|
|
% the minimum distance to vertex is smaller than to projection
|
|
cond2 = ((I_cr_min ~= I_dpv_min) & (cr_min - dpv_min)> 0);
|
|
|
|
is_vertex = (cond1 | cond2);
|
|
|
|
% build the minimum distances vector
|
|
d_min = cr_min;
|
|
d_min(is_vertex) = dpv_min(is_vertex);
|
|
|
|
% mimic the functionality of ver. 1.0 - make all distances negative for
|
|
% points INSIDE the polygon
|
|
if(find_in_out)
|
|
in = inpolygon(xp, yp, xv, yv);
|
|
d_min(in) = -d_min(in);
|
|
end
|
|
|
|
% initialize the vectors of minimum distances to the closest vertices
|
|
nz = max(np, nv);
|
|
|
|
vtmp = zeros(nz, 1);
|
|
vtmp(1:nv) = xv(:);
|
|
x_d_min = vtmp(I_dpv_min);
|
|
|
|
vtmp = zeros(nz, 1);
|
|
vtmp(1:nv) = yv(:);
|
|
y_d_min = vtmp(I_dpv_min);
|
|
|
|
% replace the minimum distances with those to projected points that fall
|
|
% inside the segments
|
|
idx_pr = sub2ind(size(xc), find(~is_vertex), I_cr_min(~is_vertex));
|
|
x_d_min(~is_vertex) = xc(idx_pr);
|
|
y_d_min(~is_vertex) = yc(idx_pr);
|
|
|
|
% find the indices of segments that contain the closest points
|
|
% note that I_dpv_min contains indices of closest POINTS. To find the
|
|
% indices of closest SEGMENTS, we have to substract 1
|
|
idx_c = I_dpv_min-1;
|
|
[ii,jj] = ind2sub(size(xc), idx_pr);
|
|
idx_c(ii) = jj;
|
|
|
|
% assign optional outputs
|
|
switch nargout
|
|
case 0
|
|
case 1
|
|
case 2
|
|
varargout{1} = x_d_min;
|
|
case 3
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
case 4
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
varargout{3} = is_vertex;
|
|
case 5
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
varargout{3} = is_vertex;
|
|
varargout{4} = idx_c;
|
|
case 6
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
varargout{3} = is_vertex;
|
|
varargout{4} = idx_c;
|
|
varargout{5} = xc;
|
|
case 7
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
varargout{3} = is_vertex;
|
|
varargout{4} = idx_c;
|
|
varargout{5} = xc;
|
|
varargout{6} = yc;
|
|
case 8
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
varargout{3} = is_vertex;
|
|
varargout{4} = idx_c;
|
|
varargout{5} = xc;
|
|
varargout{6} = yc;
|
|
varargout{7} = is_in_seg;
|
|
case 9
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
varargout{3} = is_vertex;
|
|
varargout{4} = idx_c;
|
|
varargout{5} = xc;
|
|
varargout{6} = yc;
|
|
varargout{7} = is_in_seg;
|
|
varargout{8} = Cer;
|
|
case 10
|
|
varargout{1} = x_d_min;
|
|
varargout{2} = y_d_min;
|
|
varargout{3} = is_vertex;
|
|
varargout{4} = idx_c;
|
|
varargout{5} = xc;
|
|
varargout{6} = yc;
|
|
varargout{7} = is_in_seg;
|
|
varargout{8} = Cer;
|
|
varargout{9} = Ppr;
|
|
otherwise
|
|
error('Invalid number of output arguments, must be between 1 and 9');
|
|
end
|
|
|
|
end
|