标注场景下,用户可以选取多点框选一个区域,这样会生成一个多边形。但某些多边形不适合标注场景,还会增加其他参数计算复杂度,需要判断出来禁止绘制。
分类
根据标注的场景,可以将多边形归纳为边有交点多边形与边无无交点多边形。如图:
这样实际上就是多边形划分中的简单多边形与复杂多边形。
我们可以通过多边形边之间是否有相交来判断。
判断
怎么判断变是否相交呢?
如果去计算交点是否落于连线上,不仅计算量大,而且还会因为比较精度等问题导致麻烦。这类问题早已有更好的方案:相交的线段的特征是端点分别位于相交的线段两侧,只需要判断两个端点是否在线段两侧就能判断线段是否能相交。
以右边复杂多边形举例:
组成的与组成的相交。的两个端点、必然在的两侧,反之亦然。
如何计算出端点在线段两侧呢?
可以利用数学工具向量的叉乘(Cross product)来简化几何运算,叉乘在二维计算上的结果是具有方向含义的。
我们只需选择一条线作为中线,将其端点与需要判断的端点连接做出新的线,然后辅助线与中线运算,如果符号相异则说明是在两侧,线段是可以相交的。
计算
二维叉乘的计算公式:
已知图形的各个顶点坐标,比如坐标为,并且顶点仅能顺序直线相连。
现在取作中线,取为起点,连接需要计算的,这时候我们有了一条中线,与两条新画的辅助线:
中线向量表示:
辅助线向量表示:
辅助线向量表示:
计算可得出正负,由此可知在中线的一侧。
无需知道在哪一侧,只要的结果与正负相同,则说明两点在线段的同一侧,反之则在两侧,代表线段相交。
简化运算即知不在同一侧。
最后还有一个很重要,需要反向再算一次,两者皆成立才能确认线段相交。
如果只算一个,只是延长线可以相交,但实际并不一定有相交。例如
取作中线,判断与是在两侧,计算结果是相交的,但实际并未经过。这时候只要以作中线再计算一次、是否在两侧即可。
这只是两条边是否相交,剩下只要逐个判断所有非相邻边是否相交即可。
对于标注场景不会有那么多图形,计算量完全可以接受,大量图形就得上更复杂的算法了。
代码
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;
};