O método de julgar o ponto dentro do polígono é realizado pelo matlab (o ponto está dentro do casco convexo e qualquer forma é julgada)

Declaração convencional: não tenho experiência relevante em aplicativos de engenharia e só escrevo este blog apenas porque estou interessado em algoritmos relacionados. Portanto, se houver erros, sejam bem-vindos para corrigi-los na área de comentários, muito obrigado. Este artigo se concentra principalmente na implementação do algoritmo. Não tenho experiência em aplicações práticas e outros assuntos, então não vou mais envolvê-lo.

0 Prefácio

Este artigo fala brevemente sobre os métodos comumente usados ​​por computadores para determinar se um ponto está dentro de um polígono. Essa aplicação prática é bastante comum, como julgamento puramente geométrico ou seleção de um intervalo para ver se os dados excedem esse intervalo.

Quando julgamos se um ponto está dentro de um polígono, geralmente primeiro identificamos a figura poligonal e depois verificamos se o ponto está dentro do alcance da figura reconhecida. Mas para gráficos muito complexos, não é fácil tirar conclusões de uma vez, e esse tipo de algoritmo baseado em gráficos não é realmente eficiente e não é adequado para cálculos de computador.

O algoritmo comumente usado para julgar se um ponto está dentro de um polígono está relacionado à variável n do polígono e geralmente é da ordem de O(n); portanto, na verdade, não há muita diferença entre todos e depende sobre quem pode otimizar melhor. Mas para polígonos convexos, o algoritmo da bisseção pode ser usado para reduzir a complexidade computacional ao nível O(logn), que será abordado posteriormente.

Se você tiver tempo no futuro, tentarei escrever um artigo sobre como julgar se um ponto está em um poliedro tridimensional ou de alta dimensão ( sem preencher o buraco ).

Os blogs e artigos referenciados neste artigo são os seguintes:

[1] Polígono convexo (geometria computacional, julgar se um ponto está dentro de um polígono, dicotomia)
https://www.cnblogs.com/yym2013/p/3673616.html
[2] Algoritmo para julgar um ponto dentro de um polígono (Winding Explicação detalhada do número )
https://www.codenong.com/cs106265948/
[3]Hormann K, Agathos A. O problema do ponto no polígono para polígonos arbitrários [J]. Geometria computacional, 2001, 20(3): 131-144 .

Método de interseção de 1 raio (número de cruzamento)

Esse método é chamado de método do raio ou método da regra par-ímpar. O princípio é disparar um raio para fora do ponto P. Se o número de interseções entre o raio e o polígono for um número ímpar, o ponto P está dentro do polígono.

Imagine uma bolha de sabão.Se você quiser sair da bolha de sabão, você deve passar por ela uma vez. Se duas bolhas de sabão forem passadas, uma para dentro e outra para fora equivale a nenhuma passagem.
Adicione uma descrição da imagem
Normalmente, para simplificar, o raio é escolhido diretamente como a direção +x, ou seja, a direção para a direita.

As ideias gerais de otimização são as seguintes:
1. Defina aproximadamente o alcance de um polígono.Se o ponto exceder esse intervalo, não há necessidade de julgar novamente e ele é julgado diretamente como fora do gráfico.
2 Para o segmento de linha que intercepta o raio na direção +x, seu alcance y deve estar em ambas as extremidades do raio. Ou seja, se o segmento de reta está diretamente acima do raio, ou o segmento de reta está diretamente abaixo do raio, não há necessidade de calcular o ponto de interseção, pois é impossível cruzar.
3 O segmento de reta que intercepta o raio na direção +x não pode estar no lado esquerdo do raio.
Adicione uma descrição da imagem

Este método parece muito simples, mas ainda existem alguns detalhes que precisam ser deduzidos. Por exemplo, quando a linha de borda também é uma linha horizontal, como calcular o ponto de interseção? Quando o ponto cai na linha lateral, como calcular o ponto de interseção? Quando a linha lateral é exatamente uma linha horizontal e os pontos também caem nessa linha lateral, quantas interseções são contadas? Você não obterá um bug que cruza duas arestas quando o raio do ponto passa direto pelo vértice do polígono?

Em relação a esses problemas, minha solução aproximada é: definir um conceito de ponto de borda, que é especialmente usado para julgar se o ponto está na borda. Se o ponto cair apenas na linha lateral da linha horizontal, ele não calculará mais se há interseção e o tratará diretamente como um ponto na linha lateral. Para o julgamento do ponto de interseção, use o método de uma extremidade sendo menor ou igual a e a outra extremidade sendo menor que para evitar contagem dupla. E como há erros no cálculo, uma faixa de erro precisa ser adicionada ao ponto de interseção xc calculado para evitar erros de julgamento.

Não há muito a dizer, o programa matlab é o seguinte:

clear
clc
close all
%多边形定义(连线必须按照首尾相接的顺序)
BD=[0,0;0,-1;1,0;2,-1;2,0;2,1;2,2;1,2;1,1;0,1];%凹多边形
%BD=[0,-1;0,-2;1,-1;2,-2;2,-1;2,0;2,3;-1,3;-1,1;...
%    3,1;3,1.5;0,1.5;0,2.5;1,2.5;1,0;0,0];%凹多边形,而且自相交
%BD=[0,1;-1,0;-1,-1;0,-1;1,-1;1,0];%凸多边形
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
xy2=[X(:),Y(:)];

[IsInPoly,IsOnBD]=IfInPoly1(BD,xy2);

figure()
hold on
plot(BD(:,1),BD(:,2))
scatter(xy2(:,1),xy2(:,2),24,IsInPoly+2*IsOnBD,'Marker','.')
%scatter(xy2(:,1),xy2(:,2),24,or(IsInPoly,IsOnBD),'Marker','.');%不显示边线

%后置函数
function [IsInPoly,IsOnBD]=IfInPoly1(BD,xy2)
%输出逻辑索引(IsInPoly表示在内部,IsOnBD表示在多边形边界上)
%BD是多边形边界,存在顺序,两列。xy2是点的坐标,两列。
%方法1,射线法
if (BD(1,1)~=BD(end,1)) || (BD(1,2)~=BD(end,2))
    BD2=[BD;BD(1,:)];
else %给出的边界已经收尾相接
    BD2=BD;
end
%删除边界中相邻重复的点
IsSame=and(BD2(1:end-1,1)==BD2(2:end,1),BD2(1:end-1,2)==BD2(2:end,2));
BD2([IsSame;false],:)=[];
NB=size(BD2,1)-1;
NP=size(xy2,1);

IsInPoly=false(NP,1);%true(NP,1);%false(NP,1);
IsOnBD=false(NP,1);%是否在边线上
%判断整个边线的大概范围
min_X_BD=min(BD(:,1));
max_X_BD=max(BD(:,1));
min_Y_BD=min(BD(:,2));
max_Y_BD=max(BD(:,2));
%做+x方向的射线
for kp=1:NP
    %如果这个点的xy超过整个边线的xy,则肯定不在边线内
    xy_k=xy2(kp,:);
    xp=xy_k(1);
    yp=xy_k(2);
    if xp<min_X_BD || xp>max_X_BD || yp<min_Y_BD || yp>max_Y_BD
        %IsInPoly(kp)=false;
        continue
    end
    %循环每一条边
    NCross=0;%初始化交点数量
    for kB=1:NB
        xB1=BD2(kB,1);xB2=BD2(kB+1,1);
        yB1=BD2(kB,2);yB2=BD2(kB+1,2); 
        %如果在+x方向上相交,在点一定在线的左边
        if max([xB1,xB2])<xp
            continue %所以点在线右侧的情况无需计算
        end
        %判断是否在水平的边线上
        if yB1==yp && yB2==yp && ( min([xB1,xB2])<=xp && max([xB1,xB2])>=xp)
            IsOnBD(kp)=true;%如果点在水平线段上,则证明点在边缘
            break %停止循环
        end
        %判断是否是某个顶点
        if (xp==xB1 && yp==yB1) || (xp==xB2 && yp==yB2) 
            IsOnBD(kp)=true;%如果点是某个边缘顶点,则证明点在边缘
            break
        end
        %如果射线穿过这个边,则y值一定介于这个边的两个y值中间
        if (yB1<=yp && yp<yB2) || (yB2<=yp && yp<yB1) 
            %如果在+x方向上相交,则交点一定在xp的右边
            yc=yp;
            xc=xB1+(xB2-xB1)/(yB2-yB1)*(yc-yB1);%计算射线与边的交点(xc,yc)
            if (xc-xp)>4*eps %由于计算会导致xc有一定的误差,所以保守估算为4*eps
                NCross=NCross+1;%证明的确相交,交点+1
            end
            %再次判断是否在边线上
            if abs(xc-xp)<=4*eps && abs(yc-yp)<=4*eps %这里因为也涉及到xc,所以也设置了一个误差带
                IsOnBD(kp)=true;%如果交点xc就是点xp,则证明点在线上
                break
            end
        end
        
    end
    %根据相交点数的奇偶性判断是否在多边形内
    if ~IsOnBD(kp) && mod(NCross,2)
        IsInPoly(kp)=true;%如果是奇数,则证明在多边形内
    end
end
end

Nota: Aqui estão dois resultados de IsInPoly para pontos dentro do limite e IsOnBD para pontos no limite do polígono. Se precisar mesclar, você pode usar or(IsInPoly, IsOnBD) para mesclar os resultados.

Os resultados do cálculo são os seguintes:
Adicione uma descrição da imagem

2 Número do Enrolamento

O método do número circundante é semelhante ao método do número de interseção, ambos conduzem a um raio. No entanto, o número sinuoso não calcula mais o ponto de interseção específico, mas julga se o segmento de linha de interseção passa pelo raio para cima ou para baixo pelo raio.

Adicione uma descrição da imagem
Como exemplo, vamos usar a figura do capítulo anterior como exemplo. Mas aqui os segmentos de linha recebem direções. Definimos que subindo pelo raio (o ponto no lado esquerdo do segmento de linha), o número de envolvimento é +1, e descendo o raio (apontando no lado direito do segmento de linha), o número de envolvimento é - 1. Por fim, soma-se o número de voltas, igual a 0, indicando que o ponto está fora do polígono, e não igual a 0, indicando que o ponto está dentro do polígono.

O programa matlab para este método é o seguinte:

clear
clc
close all
%多边形定义(连线必须按照首尾相接的顺序)
BD=[0,0;0,-1;1,0;2,-1;2,0;2,1;2,2;1,2;1,1;0,1];%凹多边形
%BD=[0,-1;0,-2;1,-1;2,-2;2,-1;2,0;2,3;-1,3;-1,1;...
%    3,1;3,1.5;0,1.5;0,2.5;1,2.5;1,0;0,0];%凹多边形,而且自相交
%BD=[0,1;-1,0;-1,-1;0,-1;1,-1;1,0];%凸多边形
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
xy2=[X(:),Y(:)];

[IsInPoly,IsOnBD]=IfInPoly2(BD,xy2);

figure()
hold on
plot(BD(:,1),BD(:,2))
scatter(xy2(:,1),xy2(:,2),24,IsInPoly+2*IsOnBD,'Marker','.')
%如果考虑边界情况,则把两个结果合并
%IsInPoly=or(IsInPoly,IsOnBD);

function [IsInPoly,IsOnBD]=IfInPoly2(BD,xy2)
%输出逻辑索引(IsInPoly表示在内部,IsOnBD表示在多边形边界上)
%BD是多边形边界,存在顺序,两列。xy2是点的坐标,两列。
%方法2 winding number 环绕数法
%将边界收尾相接
if (BD(1,1)~=BD(end,1)) || (BD(1,2)~=BD(end,2))
    NB=size(BD,1);
    BD2=[BD;BD(1,:)];
else %给出的边界已经收尾相接
    NB=size(BD,1)-1;
    BD2=BD;
end
%删除边界中相邻重复的点
IsSame=and(BD2(1:end-1,1)==BD2(2:end,1),BD2(1:end-1,2)==BD2(2:end,2));
BD2([IsSame;false],:)=[];
NB=size(BD2,1)-1;
NP=size(xy2,1);

IsInPoly=false(NP,1);%true(NP,1);%false(NP,1);
IsOnBD=false(NP,1);%是否在边线上
%判断整个边线的大概范围
min_X_BD=min(BD(:,1));
max_X_BD=max(BD(:,1));
min_Y_BD=min(BD(:,2));
max_Y_BD=max(BD(:,2));
%做+x方向的射线,来判定环绕数
for kp=1:NP
    %如果这个点的xy超过整个边线的xy,则肯定不在边线内
    xy_k=xy2(kp,:);
    xp=xy_k(1);
    yp=xy_k(2);
    if xp<min_X_BD || xp>max_X_BD || yp<min_Y_BD || yp>max_Y_BD
        continue
    end
    %循环每一条边
    NWinding=0;%初始化环绕数
    for kB=1:NB
        xB1=BD2(kB,1);xB2=BD2(kB+1,1);
        yB1=BD2(kB,2);yB2=BD2(kB+1,2);
        %如果在+x方向上相交,在点一定在线的左边
        if max([xB1,xB2])<xp
            continue %所以点在线右侧的情况无需计算
        end
        %判断是否在水平的边线上
        if yB1==yp && yB2==yp && ( min([xB1,xB2])<=xp && max([xB1,xB2])>=xp)
            IsOnBD(kp)=true;%如果点在水平线段上,则证明点在边缘
            break
        end
        %判断是否是某个顶点
        if (xp==xB1 && yp==yB1) || (xp==xB2 && yp==yB2) 
            IsOnBD(kp)=true;%如果点是某个边缘顶点,则证明点在边缘
            break
        end
        %如果射线穿过这个边,则y值一定介于这个边的两个y值中间
        if (yB1<=yp && yp<yB2) || (yB2<=yp && yp<yB1) 
            %如果在+x方向上相交,则开始判定点在向量的左边还是右边(向量的左右,不是前面整个几何意义的左右)
            if yB2>yB1 %这个边的方向向上
                CrossP=(xB2-xB1)*(yp-yB1)-(xp-xB1)*(yB2-yB1);%计算向量差积,判定点在向量左右
                if CrossP>0 %点在向量左侧
                    NWinding=NWinding+1;
                end
            elseif yB2<yB1 %这个边的方向向下
                CrossP=(xB2-xB1)*(yp-yB1)-(xp-xB1)*(yB2-yB1);%计算向量差积,判定点在向量左右
                if CrossP<0 %点在向量右侧
                    NWinding=NWinding-1;
                end
            else %这个边水平,不计入相交情况
                CrossP=-1;
            end
            %再次判断是否在边线上
            if abs(CrossP)<=4*eps %理论上CrossP==0是在边线上,但这里因为涉及到误差计算,所以加了一个4eps。
                IsOnBD(kp)=true;
                %break
            end
        end
        
    end
    %如果NWinding不是0,则证明在多边形内
    if ~IsOnBD(kp) && NWinding
        IsInPoly(kp)=true;
    end
end

end

A vantagem deste método é que, para grafos de auto-interseção, o método do número de envolvimento pode distinguir regiões sobrepostas. Por exemplo, quando o número circundante for estipulado igual a 2, se for usado o método do raio, será julgado que o ponto não está no gráfico, mas o método do número circundante poderá distinguir essa área.
(Se o número de envolvimento for especificado como um número par, o ponto está fora do gráfico e, se o número de envolvimento for ímpar, o ponto está dentro do gráfico, então o método de número de envolvimento e o método de interseção de raios são equivalentes.)

Por exemplo, a figura abaixo mostra um gráfico de auto-interseção, quando o método de interseção e o método de número surround são usados ​​ao mesmo tempo, como os dois lidam com a parte sobreposta. Os gráficos comuns não aparecerão como autointerseção, mas se houver uma demanda relativamente alta de autointerseção, você poderá escolher o algoritmo necessário.
Adicione uma descrição da imagem
Obviamente, o método do número surround também evita o uso da divisão porque não precisa calcular o ponto de interseção específico. Se você também é sensível à divisão, pode tentar usar o método do número surround.

Método de 3 ângulos (método de canto)

3.1 Método de adição de ângulo

Esse método é mais fácil de entender, ou seja, se o ponto estiver dentro do gráfico, o ângulo de todos os raios somados é igual a 360°.
Adicione uma descrição da imagem
Mas se o gráfico for um polígono côncavo, esse método também precisa definir os ângulos positivos e negativos. Conforme mostrado na figura acima, um ângulo que aumenta na mesma direção é definido como positivo, e um ângulo que se inverte repentinamente nessa direção é definido como negativo. O método de determinação específica é determinado pelo produto vetorial de vetores.

公司公式文:
θ = ∑ ω i = ∑ 1 n − 1 acos ( v 1 ⃗ ⋅ v 2 ⃗ ∥ v 1 ⃗ ∥ ∥ v 2 ⃗ ∥ ) ∗ sinal ( v 1 ⃗ × v 2 ⃗ ) \theta =\ soma{\omega_i}=\soma_{1}^{n-1}{acos(\frac{\vec{v_1} \cdot \vec{v_2}}{\esquerda \| \vec{v_1}\direita \| \esquerda \| \vec{v_2}\direita \|} ) *sinal(\vec{v_1}×\vec{v_2})}eu=oheu=1n - 1a co s ( _v1 v2 v1 v2 )assinar ( _ _ _v1 ×v2 )

Aqui v1 e v2 são dois raios do ponto p às duas extremidades do segmento de linha.
O princípio básico e os resultados aqui são os mesmos do método do número surround, mas como envolve o cálculo de acos, a velocidade geralmente é relativamente lenta e o método de melhoria na seção a seguir falará sobre como acelerá-lo.

O procedimento específico é o seguinte:

clear
clc
close all
%多边形定义(连线必须按照首尾相接的顺序)
BD=[0,0;0,-1;1,0;2,-1;2,0;2,1;2,2;1,2;1,1;0,1];%凹多边形
%BD=[0,-1;0,-2;1,-1;2,-2;2,-1;2,0;2,3;-1,3;-1,1;...
%    3,1;3,1.5;0,1.5;0,2.5;1,2.5;1,0;0,0];%凹多边形,而且自相交
%BD=[0,1;-1,0;-1,-1;0,-1;1,-1;1,0];%凸多边形
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
xy2=[X(:),Y(:)];
%xy2=[0.5,2];

[IsInPoly,IsOnBD]=IfInPoly3(BD,xy2);

figure()
hold on
plot(BD(:,1),BD(:,2))
scatter(xy2(:,1),xy2(:,2),24,IsInPoly+2*IsOnBD,'Marker','.')

function [IsInPoly,IsOnBD]=IfInPoly3(BD,xy2)
%方法3 计算角度法
%将边界收尾相接
if (BD(1,1)~=BD(end,1)) || (BD(1,2)~=BD(end,2))
    NB=size(BD,1);
    BD2=[BD;BD(1,:)];
else %给出的边界已经收尾相接
    NB=size(BD,1)-1;
    BD2=BD;
end
%删除边界中相邻重复的点
IsSame=and(BD2(1:end-1,1)==BD2(2:end,1),BD2(1:end-1,2)==BD2(2:end,2));
BD2([IsSame;false],:)=[];
NB=size(BD2,1)-1;
NP=size(xy2,1);

IsInPoly=false(NP,1);%true(NP,1);%false(NP,1);
IsOnBD=false(NP,1);%是否在边线上
%判断整个边线的大概范围
min_X_BD=min(BD(:,1));
max_X_BD=max(BD(:,1));
min_Y_BD=min(BD(:,2));
max_Y_BD=max(BD(:,2));
%做+x方向的射线,来判定环绕数
for kp=1:NP
    %如果这个点的xy超过整个边线的xy,则肯定不在边线内
    xy_k=xy2(kp,:);
    xp=xy_k(1);
    yp=xy_k(2);
    if xp<min_X_BD || xp>max_X_BD || yp<min_Y_BD || yp>max_Y_BD
        continue
    end
    %判断是否是某个顶点
    if any(and(xp==BD2(:,1),yp==BD2(:,2)))
        %IsOnBD(kp)=true;%如果点是某个边缘顶点,则证明点在边缘
        IsOnBD(kp)=true;
        continue
    end
    %循环每一条边
    AngleSum=0;%初始化环绕数
    for kB=1:NB

        xB1=BD2(kB,1);xB2=BD2(kB+1,1);
        yB1=BD2(kB,2);yB2=BD2(kB+1,2);
        v1=[xB1-xp,yB1-yp];v2=[xB2-xp,yB2-yp];
        CosAngle=dot(v1,v2)/norm(v1)/norm(v2);
        if abs(CosAngle+1)<4*eps
            IsOnBD(kp)=true;
            break
        end
        Sign=sign(det([v1(1),v2(1);v1(2),v2(2)]));
        AngleSum=acos(CosAngle)*Sign+AngleSum;
        %AngleList(kB)=acos(CosAngle)*Sign/pi*180;
    end
    if abs(AngleSum)/2/pi>(1-1e-5)
        IsInPoly(kp)=true;
    end
end
IsInPoly=and(IsInPoly,~IsOnBD);%对于那些又在边线上又边线内的,判定为在边线上。

end

3.2 Método de ângulo aprimorado (o matlab vem com o método de função inpolygon)

O Matlab vem com a função inpolygon() para determinar se um ponto está dentro de um polígono.

Aqui não calculamos mais especificamente o ângulo, mas usamos a relação entre os raios x e y para fazer um julgamento aproximado. Primeiro conecte os pontos aos vértices na aresta para formar um conjunto de raios. Divida a direção do raio em quatro quadrantes: superior direito ↗, superior esquerdo ↖, inferior esquerdo ↙ e inferior direito ↘ e atribua 4 números de 0, 1, 2 e 3, respectivamente. Em seguida, julgue o ângulo final com base nas mudanças desses números.

Para o método específico, consulte o código específico da função matlab no polígono ou consulte o artigo O problema do ponto no polígono para polígonos arbitrários, aqui não mostrarei minha feiura e o reeditarei sozinho.

O método de uso específico é o seguinte

clear
clc
close all
%多边形定义(连线必须按照首尾相接的顺序)
BD=[0,0;0,-1;1,0;2,-1;2,0;2,1;2,2;1,2;1,1;0,1];%凹多边形
%BD=[0,-1;0,-2;1,-1;2,-2;2,-1;2,0;2,3;-1,3;-1,1;...
%    3,1;3,1.5;0,1.5;0,2.5;1,2.5;1,0;0,0];%凹多边形,而且自相交
%BD=[0,1;-1,0;-1,-1;0,-1;1,-1;1,0];%凸多边形
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
xy2=[X(:),Y(:)];

%方法6 matlab自带方法
[IsInPoly,IsOnBD]=inpolygon(xy2(:,1),xy2(:,2),BD(:,1),BD(:,2));

figure()
hold on
plot(BD(:,1),BD(:,2))
scatter(xy2(:,1),xy2(:,2),24,IsInPoly+2*IsOnBD,'Marker','.')

Estou usando a versão 2019b, e existem vários pontos laterais que não foram identificados, o que provavelmente é resultado de nenhum erro introduzido. Mas corre rápido.
Adicione uma descrição da imagem

4 Método do produto vetorial (somente para polígonos convexos)

A ideia do método do produto vetorial vem do método do ângulo. Para um polígono convexo, se um ponto estiver dentro do polígono, conecte o ponto a cada vértice do polígono para formar uma série de vetores, e os ângulos entre esses vetores devem ser ângulos agudos.
Adicione uma descrição da imagem
Por exemplo, na figura acima à esquerda, o produto vetorial de P1 e P2 é positivo, e o produto vetorial de P2 e P3 também é positivo. Da mesma forma, P3 e P4, P4 e P5, P5 e P1 são todos positivos. Mas para a imagem à direita, o produto vetorial de P5 e P1 é um valor negativo, indicando que o ponto está fora do polígono.

código mostra como abaixo:

clear
clc
close all
%多边形定义(连线必须按照首尾相接的顺序)
%BD=[0,0;0,-1;1,0;2,-1;2,0;2,1;2,2;1,2;1,1;0,1];%凹多边形
%BD=[0,-1;0,-2;1,-1;2,-2;2,-1;2,0;2,3;-1,3;-1,1;...
%    3,1;3,1.5;0,1.5;0,2.5;1,2.5;1,0;0,0];%凹多边形,而且自相交
BD=[0,1;-1,0;-1,-1;0,-1;1,-1;1,0];%凸多边形
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
xy2=[X(:),Y(:)];
%xy2=[0.8,0.8];

IsInPoly=IfInPoly4(BD,xy2);

figure()
hold on
plot(BD(:,1),BD(:,2))
%scatter(xy2(:,1),xy2(:,2),24,IsInPoly+2*IsOnBD,'Marker','.')
scatter(xy2(:,1),xy2(:,2),24,IsInPoly,'Marker','.')

function IsInPoly=IfInPoly4(BD,xy2)
%方法4 计算叉积法
%将边界收尾相接
if (BD(1,1)~=BD(end,1)) || (BD(1,2)~=BD(end,2))
    NB=size(BD,1);
    BD2=[BD;BD(1,:)];
else %给出的边界已经收尾相接
    NB=size(BD,1)-1;
    BD2=BD;
end
%删除边界中相邻重复的点
IsSame=and(BD2(1:end-1,1)==BD2(2:end,1),BD2(1:end-1,2)==BD2(2:end,2));
BD2([IsSame;false],:)=[];
NB=size(BD2,1)-1;
NP=size(xy2,1);

IsInPoly=false(NP,1);%true(NP,1);%false(NP,1);
IsOnBD=false(NP,1);%是否在边线上
%判断整个边线的大概范围
min_X_BD=min(BD(:,1));
max_X_BD=max(BD(:,1));
min_Y_BD=min(BD(:,2));
max_Y_BD=max(BD(:,2));
%做+x方向的射线,来判定环绕数
for kp=1:NP
    %如果这个点的xy超过整个边线的xy,则肯定不在边线内
    xy_k=xy2(kp,:);
    xp=xy_k(1);
    yp=xy_k(2);
    if xp<min_X_BD || xp>max_X_BD || yp<min_Y_BD || yp>max_Y_BD
        continue
    end
    %判断是否是某个顶点
    if any(and(xp==BD2(:,1),yp==BD2(:,2)))
        %IsOnBD(kp)=true;%如果点是某个边缘顶点,则证明点在边缘
        IsInPoly(kp)=true;
        continue
    end
    %循环每一条边
    ArrowDir=zeros(NB,1);%初始化叉积方向
    for kB=1:NB
        xB1=BD2(kB,1);xB2=BD2(kB+1,1);
        yB1=BD2(kB,2);yB2=BD2(kB+1,2);
        v1=[xB1-xp,yB1-yp,0];v2=[xB2-xp,yB2-yp,0];
        CrossV=cross(v1,v2);
        ArrowDir(kB)=CrossV(3);
    end
    %根据最大角度和最小角度之差
    if all(ArrowDir>=0) || all(ArrowDir<=0)
        IsInPoly(kp)=true;
    end
end

end

O resultado é o seguinte:
Adicione uma descrição da imagem
Pode-se ver que para polígonos convexos, este algoritmo ainda não é problema. Mas para polígonos côncavos, esse método produzirá resultados errados. Portanto, ao usar esse método, você deve prestar atenção se é um polígono convexo.

5 método de grade

Como o nome sugere, o método de grade é porque a forma poligonal é muito complexa para ser julgada simplesmente. Em seguida, você pode primeiro desenhar muitas grades dentro do polígono e, em seguida, julgar se os pontos estão na grade por sua vez.

A vantagem da grade retangular é que a velocidade de julgamento é rápida, mas o julgamento da hipotenusa é mais complicado. É possível fazer com que parte do retângulo fique dentro do polígono e parte fique fora do polígono.

Um julgamento simples é feito aqui com uma malha triangular. A primeira é dividir o triângulo.Aqui eu uso a função delaunayTriangulation() que vem com o matlab.
Julgar se um ponto está dentro de um triângulo pode ser obtido resolvendo uma equação quadrática binária com dois vetores.
Adicione uma descrição da imagem
A posição desse ponto pode ser determinada por a u+b v. Se a, b e a+b estiverem todos entre 0 e 1, o ponto deve estar dentro do triângulo.

O procedimento específico é o seguinte:

clear
clc
close all
%多边形定义(连线必须按照首尾相接的顺序)
BD=[0,0;0,-1;1,0;2,-1;2,0;2,1;2,2;1,2;1,1;0,1];%凹多边形
%BD=[0,-1;0,-2;1,-1;2,-2;2,-1;2,0;2,3;-1,3;-1,1;...
%    3,1;3,1.5;0,1.5;0,2.5;1,2.5;1,0;0,0];%凹多边形,而且自相交
%BD=[0,1;-1,0;-1,-1;0,-1;1,-1;1,0];%凸多边形
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
xy2=[X(:),Y(:)];

IsInPoly=IfInPoly5(BD,xy2);

figure()
hold on
plot(BD(:,1),BD(:,2))
scatter(xy2(:,1),xy2(:,2),24,IsInPoly,'Marker','.')


function IsInPoly=IfInPoly5(BD,xy2)
%方法5 三角形网格剖分法
%如果边界收尾相接,则取消
if (BD(1,1)==BD(end,1)) && (BD(1,2)==BD(end,2))
    BD2=BD(1:end-1,:);
else %给出的边界已经收尾相接
    BD2=BD;
end
%删除边界中相邻重复的点
IsSame=and(BD2(1:end-1,1)==BD2(2:end,1),BD2(1:end-1,2)==BD2(2:end,2));
BD2([IsSame;false],:)=[];
NB=size(BD2,1);
NP=size(xy2,1);

IsInPoly=false(NP,1);%true(NP,1);%false(NP,1);
%IsOnBD=false(NP,1);%是否在边线上
%判断整个边线的大概范围
min_X_BD=min(BD2(:,1));
max_X_BD=max(BD2(:,1));
min_Y_BD=min(BD2(:,2));
max_Y_BD=max(BD2(:,2));

%按照边缘三角剖分
C=(1:NB)';
C=[C,[(2:NB)';1]];
DT = delaunayTriangulation(BD2,C);
%剔除落在外面的三角形
IO = isInterior(DT);
CL=DT.ConnectivityList;
CL(~IO,:)=[];
BD2=DT.Points;%点有可能会被网格划分所更新,所以这里重新加载一下
NB=size(BD2,1);

BDx=BD2(:,1);
BDy=BD2(:,2);
%triplot(CL,BDx,BDy)
%得到每个三角形的坐标
TRI_X=BDx(CL);
TRI_Y=BDy(CL);
NT=size(CL,1);
%计算每个三角形的范围
max_TRI_X=max(TRI_X,[],2);
min_TRI_X=min(TRI_X,[],2);
max_TRI_Y=max(TRI_Y,[],2);
min_TRI_Y=min(TRI_Y,[],2);
%计算每个三角形的向量
V1_Sum=[TRI_X(:,2)-TRI_X(:,1),TRI_Y(:,2)-TRI_Y(:,1)];
V2_Sum=[TRI_X(:,3)-TRI_X(:,1),TRI_Y(:,3)-TRI_Y(:,1)];
%开始循环判断
for kp=1:NP
    %如果这个点的xy超过整个边线的xy,则肯定不在边线内
    xy_k=xy2(kp,:);
    xp=xy_k(1);
    yp=xy_k(2);
    if xp<min_X_BD || xp>max_X_BD || yp<min_Y_BD || yp>max_Y_BD
        continue
    end
    %判断是否是某个顶点
    if any(and(xp==BD2(:,1),yp==BD2(:,2)))
        %IsOnBD(kp)=true;%如果点是某个边缘顶点,则证明点在边缘
        IsInPoly(kp)=true;
        continue
    end
    %循环每一三角形
    for kT=1:NT
        if xp<min_TRI_X(kT) || xp>max_TRI_X(kT) || yp<min_TRI_Y(kT) || yp>max_TRI_Y(kT)
            continue %如果超出三角形范围,则直接跳过
        end
        %计算是否在三角形内
        V1=V1_Sum(kT,:);
        V2=V2_Sum(kT,:);
        A=[V1',V2'];
        B=[xp-TRI_X(kT,1);yp-TRI_Y(kT,1)];
        u12=A\B;
        if max(u12)<=1 && min(u12)>=0 && sum(u12)<=1
            IsInPoly(kp)=true;
            break
        end
    end
end
end

Os resultados da divisão da malha e o padrão final são os seguintes:
Adicione uma descrição da imagem

6 Dicotomia (algoritmo O(logn))

Se o número de lados do polígono for muito grande, usando o método anterior para calcular, a complexidade é nível O(n) e o número de lados a serem repetidos é proporcional ao gráfico. (Embora eu ache que, se os gráficos são relativamente simples e a otimização é melhor, então, no método de raio e no método de número surround, muitas arestas não podem ser alternadas e não devem ser muito lentas).

Então a ideia da dicotomia é estreitar gradativamente o escopo e encontrar a área onde o ponto está localizado. O diagrama do algoritmo é o seguinte:
Adicione uma descrição da imagem
primeiro pegue um vértice e depois faça raios para outros vértices. Se o ponto estiver dentro do polígono, o ponto deverá estar dentro dessa faixa de ângulo. Então, reduzindo gradualmente o alcance, a posição final do ponto é determinada.
Depois disso, podemos usar o método de julgar que um ponto está dentro de um triângulo no Capítulo 5 para julgar se esse ponto está dentro desse triângulo.

O procedimento específico é o seguinte:

clear
clc
close all
%多边形定义(连线必须按照首尾相接的顺序)
%BD=[0,0;0,-1;1,0;2,-1;2,0;2,1;2,2;1,2;1,1;0,1];%凹多边形
%BD=[0,-1;0,-2;1,-1;2,-2;2,-1;2,0;2,3;-1,3;-1,1;...
%    3,1;3,1.5;0,1.5;0,2.5;1,2.5;1,0;0,0];%凹多边形,而且自相交
BD=[0,1;-1,0;-1,-1;0,-1;1,-1;1,0];%凸多边形
BD=[2.5*cos(0.01:0.01:2*pi)',2*sin(0.01:0.01:2*pi)'];
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
xy2=[X(:),Y(:)];

IsInPoly=IfInPoly7(BD,xy2);

figure()
hold on
plot(BD(:,1),BD(:,2))
scatter(xy2(:,1),xy2(:,2),24,IsInPoly,'Marker','.')

function IsInPoly=IfInPoly7(BD,xy2)
%方法7 二分法
NP=size(xy2,1);
BD2=BD;
%1初始化输入
%删除边界中相邻重复的点
IsSame=and(BD2(1:end-1,1)==BD2(2:end,1),BD2(1:end-1,2)==BD2(2:end,2));
BD2([IsSame;false],:)=[];
BD=BD2;
%将边界收尾相接
if (BD(1,1)~=BD(end,1)) || (BD(1,2)~=BD(end,2))
    BD2=[BD;BD(1,:)];
else %给出的边界已经收尾相接
    BD2=BD;
end
%删除三点共线情况的点
Is3Line=false(size(BD2,1)-1,1);
for k=2:size(BD2,1)-1
    if det([ BD2(k-1,:)-BD2(k,:) ; BD2(k+1,:)-BD2(k,:) ])==0
        Is3Line(k)=true;%如果三点共线,则叉积等于0
    end
end
BD2([Is3Line;true],:)=[];%删除三点共线的那些点
if det([ BD2(end,:)-BD2(1,:) ; BD2(2,:)-BD2(1,:) ])==0
    BD2(1,:)=[];%刚才循环没有判断第一个点,重新判断一下
end
NB=size(BD2,1);
%如果边的方向是顺时针方向,则变成逆时针排序方向
xy0=BD2(1,:);
v1_t=BD2(2,:)-xy0;
v2_t=BD2(NB,:)-xy0;
if det([v1_t;v2_t])<eps
    BD2=flipud(BD2);%小于0,说明给出的点是顺时针排序的
end

IsInPoly=false(NP,1);
IsOnBD=false(NP,1);%是否在边线上
%判断整个边线的大概范围
min_X_BD=min(BD(:,1));
max_X_BD=max(BD(:,1));
min_Y_BD=min(BD(:,2));
max_Y_BD=max(BD(:,2));
%2确定起始边和终止边
%以第一个点作为射线基准,计算出所有点的射线
vSum=BD2(2:end,:)-ones(NB-1,1)*BD2(1,:);
Nv=size(vSum,1);
for kp=1:NP
    %3优化,减少计算数量
    %如果这个点的xy超过整个边线的xy,则肯定不在边线内
    xy_k=xy2(kp,:);
    xp=xy_k(1);
    yp=xy_k(2);
    if xp<min_X_BD || xp>max_X_BD || yp<min_Y_BD || yp>max_Y_BD
        continue
    end
    %判断是否是某个顶点
    if any(and(xp==BD2(:,1),yp==BD2(:,2)))
        %IsOnBD(kp)=true;%如果点是某个边缘顶点,则证明点在边缘
        IsInPoly(kp)=true;
        continue
    end
    %判断是否在两端射线范围内
    v0_t=xy_k-BD2(1,:);
    v1_t=vSum(1,:);
    v2_t=vSum(Nv,:);
    if det([v1_t;v0_t])>=0 && det([v0_t;v2_t])>=0
        %如果在两个夹角范围内,则开始后续的二分法循环
        n1=1;
        n2=Nv;
    else
        continue
    end
    %4开始用二分法判断是否在区间内
    while n2-n1>1 %当区间大于1的时候,继续二分
        v1_t=vSum(n1,:);
        v2_t=vSum(n2,:);
        n3=fix((n1+n2)/2);
        v3_t=vSum(n3,:);
        
        if det([v1_t;v0_t])>=0 && det([v0_t;v3_t])>=0
            %在第一个区间
            n1=n1;
            n2=n3;
        else %在第二个区间
            n1=n3;
            n2=n2;
        end
    end
    %5二分法结束后,确定该点是否在两个向量所围成的三角形中,
    v1_t=vSum(n1,:);
    v2_t=vSum(n2,:);
    A=[v1_t',v2_t'];
    B=[xp-BD2(1,1);yp-BD2(1,2)];
    u12=A\B;
    if max(u12)<=1 && min(u12)>=0 && sum(u12)<=1
        IsInPoly(kp)=true;%如果点在这个三角形内,则证明点在多边形内
    end
end
end

O resultado final é o seguinte:
Adicione uma descrição da imagem

7 entre várias regiões

Se envolver a relação entre várias áreas, você pode usar as operações de interseção e diferença de operadores lógicos para dividir gradualmente a área selecionada.

Por exemplo, a figura abaixo mostra o resultado de cada extração de área após a interseção dos dois gráficos:
Adicione uma descrição da imagem
o código é o seguinte:

%多边形定义(连线必须按照首尾相接的顺序)
BD1=[2*cos(0.01:0.01:2*pi)'-1,2*sin(0.01:0.01:2*pi)'];
BD2=[2*cos(0.01:0.01:2*pi)'+1,2*sin(0.01:0.01:2*pi)'];
%要判断的点
[X,Y]=meshgrid(-5:0.1:5,-5:0.1:5);
X=X+randn(size(X))*0.05;
Y=Y+randn(size(Y))*0.05;
xy2=[X(:),Y(:)];

%判断点是否在图形内
IsInPoly1=inpolygon(xy2(:,1),xy2(:,2),BD1(:,1),BD1(:,2));
IsInPoly2=inpolygon(xy2(:,1),xy2(:,2),BD2(:,1),BD2(:,2));

%交集
Area1=and(IsInPoly1,IsInPoly2);
Area2=and(IsInPoly1,~IsInPoly2);
Area3=and(~IsInPoly1,IsInPoly2);
Area4=and(~IsInPoly1,~IsInPoly2);

figure()
hold on
scatter(xy2(Area1,1),xy2(Area1,2),24,1*ones(sum(Area1),1),'Marker','.')
scatter(xy2(Area2,1),xy2(Area2,2),24,2*ones(sum(Area2),1),'Marker','.')
scatter(xy2(Area3,1),xy2(Area3,2),24,3*ones(sum(Area3),1),'Marker','.')
scatter(xy2(Area4,1),xy2(Area4,2),24,4*ones(sum(Area4),1),'Marker','.')
colormap(lines(4))

Acho que você gosta

Origin blog.csdn.net/weixin_42943114/article/details/124645273
Recomendado
Clasificación