跳转至

Mesh2D:inpoly

约 608 个字 217 行代码 9 张图片 预计阅读时间 5 分钟

本期给大家带来的是 Mesh2D 项目中的 inpoly 方法,该方法与 matlab 内置的 inpolygon 方法相似,用于判定一组点是否在任意二维多边形内部,在判定的过程中效率相当高,尤其是边界复杂的情况下,优势越明显。

主要功能

INPOLY 用于判断一组点(VERT)是否在任意二维多边形(PSLG)内部。它支持一般的非凸和多连通(带洞)多边形。

Note

  • INPOLY 基于“穿越数(crossing-number)”测试:
    • 具体做法是,从每个点向右引一条射线,统计它与多边形边界的交点个数。如果交点数是奇数,则点在多边形内,否则在外部
  • 朴素做法需要每个点和每条边都判断一遍,复杂度 O(N*M),速度较慢(N 为测试点数,M 为多边形边数)
  • INPOLY 的优化:
    • 先按 y 值排序点,通过二分法快速锁定可能交点的边
    • 这样大幅减少了实际需要判定的边数
    • 平均复杂度可达到 O((N+M)*LOG(N)),非常快

案例一: 基础操作

几何区域

node = [
    4,0         % 外环
    8,4
    4,8
    0,4
    3,3         % 内洞
    5,3
    5,5
    3,5
];
edge = [
    1,2         % 外环
    2,3
    3,4
    4,1
    5,6         % 内洞
    6,7
    7,8
    8,5
];

整个区域生成点组

[xpos,ypos] = meshgrid(-1:0.2:9); % 横坐标产生51个点,纵坐标产生51个点
xpos = xpos(:);
ypos = ypos(:);

判断点的位置

[stat,bnds] = inpoly2([xpos,ypos], node, edge);
  1. stat:逻辑数组 \([2601\times1]\),值为 1 的点表示内部和边界的点
  2. bnds:逻辑数组 \([2601\times1]\),值为 1 的点表示边界上的点

可视化

figure; hold on; axis equal off;

plot(xpos(~stat), ypos(~stat), 'r.', 'markersize', 14); % 外部
plot(xpos(stat),  ypos(stat),  'b.', 'markersize', 14); % 内部
plot(xpos(bnds),  ypos(bnds),  'ks', 'markersize', 7);  % 边界点

% ------ 画多边形所有边 ------
for i = 1:size(edge,1)
    plot(node(edge(i,:),1), node(edge(i,:),2), 'k-', 'LineWidth', 1.2);
end

legend('外部','内部','边界','多边形','Location','northeast');

案例二: 凹多边形

几何区域

theta = linspace(0, 2*pi, 11)';
R = [4 1 4 1 4 1 4 1 4 1 4]'; % 交替大半径小半径
node = [R.*cos(theta), R.*sin(theta)];

edge = [ (1:10)' (2:11)']; % 顺次连接
edge(end,2) = 1;

[xpos,ypos] = meshgrid(-4:0.1:4);
xpos = xpos(:); ypos = ypos(:);

案例三: 带多个洞的多边形

几何区域

% 外环(正方形)
node = [0 0; 8 0; 8 8; 0 8];
edge = [1 2;2 3;3 4;4 1];

% 洞1(圆形近似,顺序逆时针)
t = linspace(0,2*pi,32)';
c1 = [3 3] + 0.9*[cos(t), sin(t)];
node = [node; c1];
idx1 = (5:36)';
edge = [edge; [idx1 [idx1(2:end);idx1(1)]]];

% 洞2
c2 = [6 6] + 1.2*[cos(t), sin(t)];
node = [node; c2];
idx2 = (37:68)';
edge = [edge; [idx2 [idx2(2:end);idx2(1)]]];

[xpos,ypos] = meshgrid(-1:0.1:9);
xpos = xpos(:); ypos = ypos(:);

案例四: 带自交的多边形

几何区域

node = [0 0; 4 8; 8 0; 0 6; 8 6];
edge = [1 2; 2 3; 3 4; 4 5; 5 1];

[xpos,ypos] = meshgrid(-1:0.1:9);
xpos = xpos(:); ypos = ypos(:);

案例五:内部随机孔洞

几何区域

%% 1. 外环(正方形顺时针)
node = [0 0; 10 0; 10 10; 0 10];
edge = [1 2;2 3;3 4;4 1];

%% 2. 随机生成若干不重叠圆洞
n_hole = 7;                 % 洞的数量
R_min = 0.7; R_max = 1.5;   % 半径范围
hole_centers = [];
hole_radii = [];
theta = linspace(0,2*pi,36)'; % 圆多边形分辨率

% 循环随机放置不重叠圆洞
max_try = 1000; try_cnt = 0;
while size(hole_centers,1) < n_hole && try_cnt < max_try
    try_cnt = try_cnt + 1;
    r = R_min + (R_max-R_min)*rand;
    x = 1.2*r + (10-2.4*r)*rand; % 保证不出界
    y = 1.2*r + (10-2.4*r)*rand;
    c_new = [x, y];
    % 检查是否与已有洞重叠
    if isempty(hole_centers)
        overlap = false;
    else
        d = sqrt(sum((hole_centers - c_new).^2,2));
        overlap = any(d < (hole_radii + r)*1.05); % 1.05避免浮点误差
    end
    % 检查是否出界(离外环足够远)
    if x-r<0 || x+r>10 || y-r<0 || y+r>10
        continue;
    end
    if ~overlap
        hole_centers = [hole_centers; c_new];
        hole_radii   = [hole_radii;   r];
    end
end

% 生成所有洞的节点与边
for k = 1:n_hole
    c = hole_centers(k,:);
    r = hole_radii(k);
    circ = c + r*[cos(theta), sin(theta)];
    idx0 = size(node,1)+1;
    node = [node; circ];
    idx = (idx0:(idx0+length(theta)-1))';
    edge = [edge; [idx [idx(2:end); idx(1)]]];
end

%% 3. 生成点
[xpos, ypos] = meshgrid(linspace(-1,11,100), linspace(-1,11,100));
xpos = xpos(:);
ypos = ypos(:);

[stat, bnds] = inpoly2([xpos, ypos], node, edge);

案例六:复杂几何区域

对于较为复杂的几何区域,将 inpoly2 与 matlab 内置的inpolygon 进行对比:

clc; clear; close all;

geom = loadmsh('test-data/coast.msh'); % 这里是官方例子
node = geom.point.coord(:,1:2);
edge = geom.edge2.index(:,1:2);

% 随机点密集布满多边形范围
n_rand = 2500;
half = max(node,[],1) + min(node,[],1);
half = half * 0.5;
scal = max(node,[],1) - min(node,[],1);

rpts = rand(n_rand,2);
rpts(:,1) = 1.10*scal(1)*(rpts(:,1)-.5)+half(1);
rpts(:,2) = 1.10*scal(2)*(rpts(:,2)-.5)+half(2);

% 加上所有节点和所有边中点
emid = .5 * node(edge(:,1),:) + .5 * node(edge(:,2),:);
xpos = [node(:,1); emid(:,1); rpts(:,1)];
ypos = [node(:,2); emid(:,2); rpts(:,2)];

tic
[stat, bnds] = inpoly2([xpos, ypos], node, edge);
fprintf('Runtime: %f (INPOLY2)\n', toc);

tic
[stat2, bnds2] = inpolygon(xpos, ypos, node(:,1), node(:,2));
fprintf('Runtime: %f (INPOLYGON)\n', toc);

figure; hold on; axis equal off;
plot(xpos(~stat), ypos(~stat), 'r.', 'markersize', 8);
plot(xpos(stat),  ypos(stat),  'b.', 'markersize', 8);
plot(xpos(bnds),  ypos(bnds),  'ks', 'markersize', 5);
for i = 1:size(edge,1)
    plot(node(edge(i,:),1), node(edge(i,:),2), 'b-', 'LineWidth', 1.2);
end
legend('外部','内部','边界','多边形边线');
title('大规模点集的inpoly2判定');

时间对比结果:

Runtime: 0.008523 (INPOLY2)
Runtime: 3.738240 (INPOLYGON)

从上面结果来看,inpoly2 比 matlab 内置的 INPOLYGON 方法快了 400 多倍!

案例七:“空”的情况

将案例一的点组向右平移 12,将会得到所有的点都判定为外部的点,算法在极端情况下不会出错。

将案例一的点组向右平移 5,也可以精准判定点的范围。

案例八:边界边缘带

[in, on] = inpoly2([x, y], [px, py], [], 1e-2)

inpoly2不传入 edge 信息,空着,第四个参数传入边界边缘带的宽度,返回的on就可以是整个边界边缘带的点了,即距离边界小于 0.01 的点都视为边界点。

完整代码:

clc; close all;
px=[+0.0 -1.0 +0.5 +2.0 +3.0 +0.0]';
py=[-3.0 +1.0 +2.0 +3.0 +2.0 -3.0]';

dx=0.1;
x=linspace(min(px)-dx,max(px)+dx,100);
y=linspace(min(py)-dx,max(py)+dx,100);
[x,y]=meshgrid(x,y);
x=x(:);
y=y(:);

% 可视化多边形
figure; hold on; axis equal off;
plot(px,py,'b-','linewidth',4);      % 多边形边


% 先画全部点为红色(外部点的底色)
h_out = plot(x, y, 'r.', 'markersize', 6);

% 调用 inpoly2 判定
[in, on] = inpoly2([x, y], [px, py], [], 1e-2);
in2 = in & ~on; % 纯内部点

% 画内部点(蓝色圆点,覆盖红色点)
h_in = plot(x(in2), y(in2), 'bo', 'markersize', 5, 'MarkerFaceColor', 'b');

% 画边界点(蓝色方块,覆盖红色点)
h_bd = plot(x(on), y(on), 'ks', 'markersize', 7, 'MarkerFaceColor', 'cyan');

legend([h_out h_in h_bd], {'外部点','内部点','边界点'}, 'Location','northeast');


% ---- 选择要放大的局部区域 ----
xrange = [1.0 2.0];
yrange = [1.8 2.8];

% 主图上画放大区域框
rectangle('Position', [xrange(1), yrange(1), diff(xrange), diff(yrange)], ...
          'EdgeColor', [1 0.5 0], 'LineWidth', 2, 'LineStyle', '--');

% ---- 添加 inset axes(放大图)----
ax1 = gca;    % 主图句柄
ax2 = axes('Position',[0.6 0.58 0.27 0.27]); % [left bottom width height],可调位置和大小
box on; hold on; axis equal;

% 放大图内容与主图一致,但只画局部
plot(px,py,'b-','linewidth',4);
plot(px,py,'bo','markersize',10);
plot(x(~in), y(~in), 'r.', 'markersize', 4);
plot(x(in2), y(in2), 'bo', 'markersize', 4, 'MarkerFaceColor', 'b');
plot(x(on), y(on), 'ks', 'markersize', 6, 'MarkerFaceColor', 'cyan');
set(gca,'XLim',xrange,'YLim',yrange);
set(gca,'xtick',[],'ytick',[]); % 关闭放大图刻度
title('边界细节');

参考文献

[1] - J. Kepner, D. Engwirda, V. Gadepally, C. Hill, T. Kraska, M. Jones, A. Kipf, L. Milechin, N. Vembar: Fast Mapping onto Census Blocks, IEEE HPEC, 2020.