image thumbnail
from 2D polygon interior detection by Bruno Luong
Detect a whereas a point is interior or exterior to a 2D polygon

insidepoly(varargin)
function [inpoly onboundary] = insidepoly(varargin)
% [inpoly onboundary] = insidepoly(X, Y, PX, PY)
% 
% Check if (X,Y) are inside the interior of a 2D polygon delimited by the
% polygon vertices (PX,PY).
%
% INPUTS:
%   - X, Y: arrays of same size, coordinates of N data points
%   - PX, PY: arrays of same size, coordinates of M vertices
%   - Provide optionally EDGE: insidepoly(x, y, Px, Py, edges)
%   EDGES are (m x 2) linear indexes of (Px,Py) that will
%   be considered as vertices of the polygon. This allows user to provide
%   polygons with multiple connexed boundaries (e.g., having holes)
%
% Alternate calling:
%   >> inpoly = insidepoly(XY, P) % or
%   >> inpoly = insidepoly(XY, P, EDGES)
%   - XY: (n x 2) array of n points, ranged by row
%   - P: (m x 2) array of m vertices, ranged by row
%   - EDGES (if provided in third argument) correponds to the
%   first-dimension indexed of P: P(EDGES,:) is the polygone boundary
%
% Advanced control the algorithm:
%   Optionally, user can provide parameters to control the algorithm
%   >> insidepoly(..., 'property1', value1, ...)
% Valid properties (case sensitive) are
%   - 'tol': ['auto'] or positive value: eulidian distance tolerance to
%      detect boundary points. When 'tol' is is set to 'auto', the
%      tolerance value of 1e-9*max(dPx,dPy) is used, where dPx, dPy are
%      respectively the horizontal and vertical size of the polygon.
%   - 'presortflag':  ['auto'], 0, or 1:
%     For large number of vertices and points, INSIDEPOLY uses a sorting
%     strategy to reduce the data to be scanned by each polygonal edge.
%     This reduction comes at the cost of additional processing. The 
%     complexity of the sorting step is O((N + M)*log(N+M)). If the numbers
%     of vertices and/or data are small, the sorting is relatively expensive
%     with respect to the complexity of O(M*N) needed for the calculation
%     for the polygonal region determitaion. It might be preferable to
%     disable this step. By default, the presorting is enabled when:
%                      M>32 and M*N>25000.
%     Set the presortflag to 0 or 1 to enable/disable the sorting step.
% 
% OUTPUTS:
%   - INPOLY: logical array same size as (X,Y), TRUE if the point is
%   inside or on the boundary of the polygon, FALSE otherwise
%   - ONBOUNDARY: logical array same size as (X,Y) to determine points
%   on the boundary (within numercial tolerance)
%
% This functions has C-MEX engine to speed up the calculation. These Mex
% files must be compiled by lauching insidepoly_install.m
%
% See also: inpolygon, inpoly, insidepoly_dblengine, insidepoly_sglengine
%
% Acknowlegment: The idea of sorting coordinates is first implemented by
% Darren Engwirda, http://www.mathworks.com/matlabcentral/fileexchange/10391
%
% Author: Bruno Luong <brunoluong@yahoo.com>
% History:
%     Original: 06-Jun-2010

% Processing the argument list

% Default options
options = struct('tol', 'auto', ...
                 'presortflag', 'auto');
                 
optionloc = cellfun('isclass',varargin,'char');
if any(optionloc)
    optionloc = find(optionloc,1,'first');
    mainargin = varargin(1:optionloc-1);
    % retreive property/value pairs
    for k=optionloc:2:nargin-1
        options.(varargin{k})= varargin{k+1};
    end
else
    mainargin = varargin;
end

edges = NaN;
if length(mainargin)<=3
    [xy P] = deal(varargin{1:2});
    x = xy(:,1);
    y = xy(:,2);
    if length(mainargin)==3 % edge provided
        edges = varargin{3};
    end
    Px = P(:,1);
    Py = P(:,2);
else
    [x y Px Py] = deal(varargin{1:4});
     if length(mainargin)==5 % edge provided
        edges = varargin{5};
     end
end

edgeprovided = ~isnan(edges);

% Orginal size of the data
sz = size(x);

% Reshape in columns
Px = Px(:);
Py = Py(:);
x = x(:);
y = y(:);

% Cast to double of one of them is (required by Mex)
isxdbl = isa(x,'double');
isydbl = isa(y,'double');
isPxdbl = isa(Px,'double');
isPydbl = isa(Py,'double');
doubleengine = isxdbl || isydbl || isPxdbl|| isPydbl;
if doubleengine
    if ~isxdbl, x = double(x); end
    if ~isydbl, y = double(y); end
    if ~isPxdbl, Px = double(Px); end
    if ~isPydbl, Py = double(Py); end   
end

Pxmin = min(Px);
Pxmax = max(Px);
Pymin = min(Py);
Pymax = max(Py);

% Select the tolerance for determining on-boundary points
if isequal(options.tol,'auto')
    ontol = 1e-9*max(Pxmax-Pxmin,Pymax-Pymin);
else
    ontol = options.tol;
end

if ~edgeprovided
    % We don't want duplicate end vertices
    if Px(1)==Px(end) && Py(1)==Py(end)
        Px(end) = [];
        Py(end) = [];
    end
    % Linear wrap around the points
    Px1 = Px;
    Py1 = Py;
    Px2 = Px([2:end 1]);
    Py2 = Py([2:end 1]);
else
    Px1 = Px(edges(:,1),:);
    Py1 = Py(edges(:,1),:);
    Px2 = Px(edges(:,2),:);
    Py2 = Py(edges(:,2),:);
end

% Filter data outside the rectangular box
inpoly = x>=Pxmin-ontol & x<=Pxmax+ontol & ...
         y>=Pymin-ontol & y<=Pymax+ontol;
x = x(inpoly);
y = y(inpoly);

if isequal(options.presortflag,'auto')
    n = size(x,1);
    m = size(Px1,1);
    % We don't do presorting for small size polygon or small size data
    % Empirical law from experimental tests (Bruno)
    presortflag = (m>32) && (n*m > 25000);
else
    presortflag = options.presortflag;
end

if presortflag
    % Sort the array in x, and find the brackets. This is used to find
    % easily which data points have abscissa fall into the abcissa bracket
    % of each edge of the polygon.
    [x ix first last] = presort(Px1, Px2, x);
    y = y(ix); % arrange y in the same order
    
    % Call mex engine
    if doubleengine
        [in on] = insidepoly_dblengine(x, y, Px1, Py1, Px2, Py2, ontol, ...
                                       first, last);
    else % single arrays
        [in on] = insidepoly_sglengine(x, y, Px1, Py1, Px2, Py2, ontol, ...
                                       first, last);
    end
    
    % Restore the original order
    in(ix) = in;
    on(ix) = on;
else % No presorting
    % Call mex engine, without presorting
    if doubleengine
        [in on] = insidepoly_dblengine(x, y, Px1, Py1, Px2, Py2, ontol);
    else % single arrays
        [in on] = insidepoly_sglengine(x, y, Px1, Py1, Px2, Py2, ontol);
    end
end

in = in | on;

if nargout>=2
    onboundary = inpoly;
    onboundary(inpoly) = on;
    % Reshape to original size
    onboundary = reshape(onboundary, sz);
end

inpoly(inpoly) = in;
% Reshape to original size
inpoly = reshape(inpoly, sz);

end % insidepoly

%%
function [xsorted ix first last] = presort(Px1, Px2, x)
% (Px1, Px2) abscissa of vertices, x abscissa of data, they are supposed
% to be ranged in column.
% Return:
%   xsorted as sort(x) = x(ix)
%   "first" & "last" indexes such that, for each vertice point
%       min(Px1,Px2) <= xsorted(first:last) <= max(Px1,Px2)

% left and right brackets of the segment
Pmin = min(Px1,Px2);
Pmax = max(Px1,Px2);

nvertices = size(Px1,1);

% We seek to see how x interveaves with Pmin by sorting the ensemble
[trash is] = sort([Pmin; x],1); %#ok
isdata = is>nvertices; % tail index, i.e., belong to data abscissa x
anchor = find(~isdata);
% Get the sorted data alone
ix = is(isdata)-nvertices;
xsorted = x(ix); % sorted x
% Index of the first element in xsorted such that
%   xsorted(first)>=sort(Pmin)
first = anchor-(0:nvertices-1).';
% Rearrange first corresponds to the original order
ip = is(anchor);
first(ip) = first;

% determine how Pmax interleaves with xsorted, i.e.,
% index of the last element in xsorted such that xsorted(first)<=Pmax
% Note: in case of draw in binning edges, HISTC must return the last edge
[trash last] = histc(Pmax, [xsorted; inf]); %#ok

end % presort

Contact us at files@mathworks.com