image thumbnail
from hullfit by Peter Wasmeier
Fit polygon hull to a data set better than convhull would do.

k=hullfit(x,y,p,fh)
function k=hullfit(x,y,p,fh)

%HULLFIT    Fit a polygon hull to a given data set so no data point is on
%           the outside of the hull. Enhancement of CONVHULL.
%
%   K = HULLFIT(X,Y,P)
%
% Inputs:   X,Y vectors of the same size containing the coordinates of the
%               data set points
%           P   fraction of longest hull line length to split hull lines
%               to. Default is 0.5.
% Output:   K   vector of indizes into X and Y containing the hull polygon
%               points in clockwise order.
%
% HULLFIT enhances the functionality of CONVHULL. While CONVHULL gives you
% a hull polygon of minimal outline length, HULLFIT tries to minimize the 
% polygon area by allowing only line lengths of (p * longest line).
% this might provide the possibility to skip unwanted interpolation effects
% and only use the inner area.
%
% Example:
% This example is a measurement set from a street curve
%
% load street;
% xi=linspace(min(x),max(x),100);
% yi=linspace(min(y),max(y),100);
% zi=griddata(x,y,z,xi,yi');
% pcolor(xi,yi,zi),shading interp,hold on
% plot(x,y,'k+');
% % pcolor shows unwanted interpolation effects, because it only uses
% % convhull. Show convhull:
% k=convhull(x,y);
% disp('CONVHULL only. Press return to continue.');
% pause
% plot(x(k),y(k),'k-');
% % Now use hullfit
% k=hullfit(x,y,0.1);
% plot(x(k),y(k),'r-')
% disp('Now HULLFIT also. Let's see what this can do for our plot:')
% [xx,yy]=meshgrid(xi,yi);
% in=inpolygon(xx,yy,x(k),y(k));
% zi(~in)=NaN;
% figure
% pcolor(xi,yi,zi),shading interp
%
% Also try k=hullfit(x,y,0.02); and see what happens to the left out areas
% of the data set.
%
%   programmed by Peter Wasmeier, Technical University of Munich
%   p.wasmeier@bv.tum.de
%   11-11-04



if nargin<3,p=.5;end
if nargin<2,error('hullfit needs at least two input arguments!'),end
if ~any(size(x)==1),error('hullfit needs x and y inputs be vectors of the same size!'),end
if ~all(size(x)==size(y)),error('hullfit needs x and y inputs be vectors of the same size!'),end

% Create starting convex hull
k=convhull(x,y);
% Calculate the angles from starting point to the second and to the last one
w1=pi-atan2(y(k(2))-y(k(1)),x(k(2))-x(k(1)));
w2=pi-atan2(y(k(end-1))-y(k(1)),x(k(end-1))-x(k(1)));
% From triangulation it is clear, that only if w1>w2 we have
% counterclockwise orientation of the k-index. In this case, change it to
% clockwise indexing.
if w1>w2,k=flipud(k);end

% Calculate maximum distance between two index points from hull
l=exdist(x(k),y(k));
% Calculate the maximum allowed length from user input p.
ll=p*l;

% Start a infinite loop to work on all hull lines which are too long
while 1
    % Calculate the actual longest line length l, its starting index m in the hull
    % vector k and the shortest hull line length mi
    [l,m,mi]=exdist(x(k),y(k));
    if l<=ll,break,end  % if no line is long enough to split
    % Initialize a counter for the added hull points in the following step
    ku=0;
    % Loop to add hull points, starting from the point with index m.
    while 1
        % If hull line length is short enough, no more points need to be
        % added.
        if sqrt((x(k(m))-x(k(m+1)))^2+(y(k(m))-y(k(m+1)))^2)<ll,break,end
        % Calculate the next point which fits on the hull.
        kn=npcw(x,y,k,m,m+1);
        % Add the point between the starting point with index m and the
        % follow-on.
        k=[k(1:m);kn;k(m+1:end)];
        % If no new point was successfully added, leave loop
        if isempty(kn),break,end
        % Otherwise increase counter of added points by one.
        ku=ku+1;
    end
    % If no point could be added in the last run, work is finished. Leave
    % master loop.
    if ~ku,break,end
    % From definition of adding new points in function npcw, we might have
    % added more points than necessary. Try, if we can leave some of them
    % away.
    % Create a starting index of hull points in k, which might possibly be
    % left out. The ending index of that group of points is of course
    % m+1+ku.
    % Start a loop to leave points out
    for i=m+1:m+1+ku
        % Look, if there is a point more far to the left hand side of the
        % bearing from hull point i to hull point m+1+ku. If this is not
        % the case, all points in between can be left out.
        [kn,lp]=npcw(x,y,k,i,m+1+ku);
        % If no point on the left is found, lp is equal to 0
        if ~lp
            % Delete all following hull points until m+1+ku and leave loop.
            k(i+1:m+ku)=[];
            break
        end
    end
end

% Helper functions

function [lmax,mi,lmin]=exdist(x,y)

s=sqrt(diff(x).^2+diff(y).^2);
mi=find(s==max(s));
lmax=s(mi);
lmin=min(s);


function [kn,lp]=npcw(x,y,k,ks,ke)
% Calculate the next fitting point for the hull and return its index kn.
% Calculation prinziple is simple:
% Because we know, that hull definition is clockwise, the next candidate
% for a hull point is those having the smallest positive angle with the
% hull line to intersect.
% second condition: the distance to a candidate must be smaller than the
% length of the line to intersect.
% Using this algorithm, either a point close to the starting point or clse
% to the ending point of the hull line is found.
kn=[];
% calculate all angles to the points
w=pi-atan2(y-y(k(ks)),x-x(k(ks)));
% Take the angle of the reference line (to be intersected)
wSE=w(k(ke));
% Calculate the angle differences
dw=w-wSE;
% Angles to starting and ending point would be zero and must be skipped.
dw(k(ke))=NaN;
dw(k(ks))=NaN;
% Second condition: calculate the distances
s=sqrt((x-x(k(ks))).^2+(y-y(k(ks))).^2);
% Take the distance of reference line
sSE=s(k(ke));
% Skip too long distances
dw(s>sSE)=NaN;
% Look if there are negative angles and if it is so, set the flag lp to 1
if min(dw)>0,lp=0;else,lp=1;end
% Find the index of the smallest angle
kn=find(dw==min(abs(dw)));
% if no points to calculate the angle to were left, the result is NaN. But
% wen want an empty variable to be returned instead.
if isnan(kn),kn=[];end

Contact us at files@mathworks.com