判断多边形是否属于复杂多边形

标注场景下,用户可以选取多点框选一个区域,这样会生成一个多边形。但某些多边形不适合标注场景,还会增加其他参数计算复杂度,需要判断出来禁止绘制。

分类

根据标注的场景,可以将多边形归纳为边有交点多边形与边无无交点多边形。如图:

image.png

这样实际上就是多边形划分中的简单多边形与复杂多边形。

我们可以通过多边形边之间是否有相交来判断。

判断

怎么判断变是否相交呢?

如果去计算交点是否落于连线上,不仅计算量大,而且还会因为比较精度等问题导致麻烦。这类问题早已有更好的方案:相交的线段的特征是端点分别位于相交的线段两侧,只需要判断两个端点是否在线段两侧就能判断线段是否能相交。

以右边复杂多边形举例:

image.png

\( 点_A,点_F \)组成的\( 线段a \)与\( 点_C,点_D \)组成的\( 线段b \)相交。\( 线段a \)的两个端点\( A \)、\( F \)必然在\( 线段b \)的两侧,反之亦然。

如何计算出端点在线段两侧呢?

可以利用数学工具向量的叉乘(Cross product)来简化几何运算,叉乘在二维计算上的结果是具有方向含义的。

我们只需选择一条线作为中线,将其端点与需要判断的端点连接做出新的线,然后辅助线与中线运算,如果符号相异则说明是在两侧,线段是可以相交的。

计算

二维叉乘的计算公式:

$$ a \times b = \begin{bmatrix} x_a & x_b \\ y_a & y_b \end{bmatrix} = x_a y_b - x_b y_a $$

已知图形的各个顶点坐标,比如\( 点_A \)坐标为\( (x_1,y_1) \),并且顶点仅能顺序直线相连。

现在取\( b(点_C,点_D) \)作中线,取\( 点_C \)为起点,连接需要计算的\( 点_A,点_F \),这时候我们有了一条中线,与两条新画的辅助线:

中线\( b(点_C,点_D) \)向量表示:\( V_b(x_c - x_d, y_c - y_d) = V_b(x_b, y_b) \)

辅助线\( c(点_C,点_A) \)向量表示:\( V_c(x_C - x_A, y_C - y_A) = V_c(x_c, y_c) \)

辅助线\( d(点_C,点_F) \)向量表示:\( V_d(x_C - x_F, y_C - y_F) = V_d(x_d, y_d) \)

image.png

计算\( V_b \times V_c = x_b \cdot y_c - x_c \cdot y_b \)可得出正负,由此可知\( 点_A \)在\( V_b \)中线的一侧。

无需知道在哪一侧,只要\( V_b \times V_d \)的结果与\( V_b \times V_c \)正负相同,则说明两点在线段的同一侧,反之则在两侧,代表线段相交。

简化运算\( (V_b \times V_d) \times (V_b \times V_c) <= 0 \)即知不在同一侧。

最后还有一个很重要,需要反向再算一次,两者皆成立才能确认线段相交。

如果只算一个,只是延长线可以相交,但实际并不一定有相交。例如

image.png

取\( 线段AB \)作中线,判断\( 点_E \)与\( 点_F \)是在\( 线段AB \)两侧,计算结果是相交的,但实际\( 线段AB \)并未经过\( 线段EF \)。这时候只要以\( 线段EF \)作中线再计算一次\( 点_A \)、\( 点_B \)是否在两侧即可。

这只是两条边是否相交,剩下只要逐个判断所有非相邻边是否相交即可。

对于标注场景不会有那么多图形,计算量完全可以接受,大量图形就得上更复杂的算法了。

代码

interface Point {
  x: number;
  y: number;
}
type Edge = [Point, Point];

/**
 * 叉乘
 */
const crossProduct = (v1: number[], v2: number[]) => {
  const [v1x, v1y] = v1;
  const [v2x, v2y] = v2;
  return v1x * v2y - v2x * v1y;
};

/**
 * 是否可以相交
 * @param baseEedge
 * @param targetEdge
 */
const isIntersection = (baseEedge: Edge, targetEdge: Edge) => {
  const [basePointA, basePointB] = baseEedge;
  const [targetPointC, targetPointD] = targetEdge;

  const vBase = [basePointA.x - basePointB.x, basePointA.y - basePointB.y];
  const vBaseC = [basePointA.x - targetPointC.x, basePointA.y - targetPointC.y];
  const vBaseD = [basePointA.x - targetPointD.x, basePointA.y - targetPointD.y];
  return crossProduct(vBase, vBaseC) * crossProduct(vBase, vBaseD) <= 0;
};

/**
 * 提取数组元素
 */
const extractArray = (array: Edge[], startIndex: number, length: number) => {
  const arr = [];
  for (let i = 0; i < length; i++) {
    arr.push(array[(startIndex + i) % array.length]);
  }
  return arr;
};

/**
 * 是否是复杂多边形
 * @param points 多边形的顶点
 */
const isComplexPolygon = (points: Point[]) => {
  const length = points.length;
  if (length < 4) return false;
  const edges = points.reduce<Edge[]>((edges, startPoint, i, array) => {
    const endPoint = array[(i + 1) % length];
    edges.push([startPoint, endPoint]); // [起始点, 结束点]
    return edges;
  }, []);

  // 逐边判断 相邻的边无需判断
  for (const [i, baseEdge] of Object.entries(edges)) {
    const nonadjacentEdge = extractArray(edges, Number(i) + 2, edges.length - 3);
    const flag = nonadjacentEdge.some(
      (edge) => isIntersection(baseEdge, edge) && isIntersection(edge, baseEdge)
    );
    if (flag) return true;
  }
  return false;
};

效果

参考资料

简单多边形判定和修复
平面向量快速入门
向量运算:叉乘
js中常用的数学方法-用于测试形状与形状是否相交


01小径
在路上,遇见了一只BUG,我将它抓住,收藏在了这里。<( ̄︶ ̄)>
651 声望
14 粉丝
0 条评论
推荐阅读
Theia 开发环境搭建
Node.js &gt;= 16.14.0 and &lt; 17.If you are interested in Theia's VS Code Extension support then you should use a Node version at least compatible with the one included in the version of Electron ...

LnEoi1阅读 180

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy49阅读 7.4k评论 12

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 7k评论 12

封面图
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs32阅读 3.6k评论 5

封面图
安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城29阅读 6.4k评论 5

封面图
2022大前端总结和2023就业分析
我在年前给掘金平台分享了《2022年热点技术盘点》的前端热点,算是系统性的梳理了一下我自己对前端一整年的总结。年后,在知乎上看到《前端的就业行情怎么样?》,下面都是各种唱衰前端的论调,什么裁员,外包化...

i5ting27阅读 2.4k评论 4

封面图
深入理解React Diff算法
fiber上的updateQueue经过React的一番计算之后,这个fiber已经有了新的状态,也就是state,对于类组件来说,state是在render函数里被使用的,既然已经得到了新的state,那么当务之急是执行一次render,得到持有新...

nero31阅读 11.8k评论 3

651 声望
14 粉丝
宣传栏