Skip to content

Commit

Permalink
fix: tolerate async function #95 (#96)
Browse files Browse the repository at this point in the history
* fix: tolerate async function #95

* feat: add polygon intersect test function #94

* chore: bump v3.3.0
  • Loading branch information
xiaoiver authored Sep 16, 2022
1 parent c596f7e commit c9ad79b
Show file tree
Hide file tree
Showing 9 changed files with 329 additions and 7 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,30 @@ distanceSquareRoot(a: [number, number], b: [number, number]): number;
const [formattedPath1, formattedPath2] = equalizeSegments(path1, path2);
```

### isPointInPolygon

判断一个点是否在多边形内。多边形形如:

```js
const polygon = [
[0, 0],
[0, 100],
[30, 100],
[30, 0],
];

// [0, 0] 在多边形的边上
isPointInPolygon(polygon, 0, 0); // true
```

### isPolygonsIntersect

判断两个多边形是否相交:

```js
isPolygonsIntersect(polygon1, polygon2);
```

## License

MIT@[AntV](https://github.com/antvis).
32 changes: 32 additions & 0 deletions __tests__/unit/math/is-point-in-polygon.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { isPointInPolygon } from '../../../src/math';

describe('point in polygon', () => {
it('in polygon', () => {
const points = [
[0, 0],
[0, 100],
[30, 100],
[30, 0],
];

expect(isPointInPolygon(points, 0, 0)).toEqual(true); // 顶点
expect(isPointInPolygon(points, 0, 10)).toEqual(true); // 边上
expect(isPointInPolygon(points, 0, -1)).toEqual(false); // 顶点

expect(isPointInPolygon(points, 10, 10)).toEqual(true); // 边上
expect(isPointInPolygon(points, 30, 0)).toEqual(true);
expect(isPointInPolygon(points, 30, 10)).toEqual(true);
});

it('other polygon', () => {
const points = [
[0, 0],
[100, 0],
[0, 100],
[100, 100],
];
expect(isPointInPolygon(points, 50, 50)).toEqual(true);
expect(isPointInPolygon(points, 50, 40)).toEqual(true);
expect(isPointInPolygon(points, 40, 50)).toEqual(false);
});
});
55 changes: 55 additions & 0 deletions __tests__/unit/math/is-polygons-intersect.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { isPolygonsIntersect } from '../../../src/math';

describe('is polygons intersect', () => {
const points = [
[0, 0],
[0, 100],
[20, 100],
[20, 0],
];

it('length < 2', () => {
expect(isPolygonsIntersect(points, [])).toEqual(false);
expect(isPolygonsIntersect([], points)).toEqual(false);
expect(isPolygonsIntersect([[0, 0]], points)).toEqual(false);
});

it('length = 2', () => {
const points1 = [
[0, 0],
[20, 0],
];
expect(isPolygonsIntersect(points, points1)).toEqual(true);
});

it('no intersect', () => {
const points1 = [
[10, 120],
[10, 120],
[30, 98],
];
expect(isPolygonsIntersect(points, points1)).toEqual(false);
expect(isPolygonsIntersect(points1, points)).toEqual(false);
});

it('intersect', () => {
const points1 = [
[10, 120],
[10, 120],
[30, 98],
[-10, 98],
];
expect(isPolygonsIntersect(points, points1)).toEqual(true);
expect(isPolygonsIntersect(points1, points)).toEqual(true);
});

it('one in one', () => {
const points1 = [
[1, 10],
[8, 20],
[14, 10],
];
expect(isPolygonsIntersect(points, points1)).toEqual(true);
expect(isPolygonsIntersect(points1, points)).toEqual(true);
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@antv/util",
"version": "3.2.5",
"version": "3.3.0",
"license": "MIT",
"sideEffects": false,
"main": "lib/index.js",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './color';
export * from './matrix';
export * from './path';
export * from './lodash';
export * from './math';
8 changes: 2 additions & 6 deletions src/lodash/is-function.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
/**
* 是否为函数
* @param {*} fn 对象
* @return {Boolean} 是否函数
* @see https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_isfunction
*/
import isType from './is-type';

export default (value: any): value is Function => {
return isType(value, 'Function');
return typeof value === 'function';
};
2 changes: 2 additions & 0 deletions src/math/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { isPointInPolygon } from './is-point-in-polygon';
export { isPolygonsIntersect } from './is-polygons-intersect';
51 changes: 51 additions & 0 deletions src/math/is-point-in-polygon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 多边形的射线检测,参考:https://blog.csdn.net/WilliamSun0122/article/details/77994526
const tolerance = 1e-6;
// 三态函数,判断两个double在eps精度下的大小关系
function dcmp(x: number) {
if (Math.abs(x) < tolerance) {
return 0;
}

return x < 0 ? -1 : 1;
}

// 判断点Q是否在p1和p2的线段上
function onSegment(p1: number[], p2: number[], q: number[]) {
if (
(q[0] - p1[0]) * (p2[1] - p1[1]) === (p2[0] - p1[0]) * (q[1] - p1[1]) &&
Math.min(p1[0], p2[0]) <= q[0] &&
q[0] <= Math.max(p1[0], p2[0]) &&
Math.min(p1[1], p2[1]) <= q[1] &&
q[1] <= Math.max(p1[1], p2[1])
) {
return true;
}
return false;
}

// 判断点P在多边形内-射线法
export function isPointInPolygon(points: number[][], x: number, y: number) {
let isHit = false;
const n = points.length;
if (n <= 2) {
// svg 中点小于 3 个时,不显示,也无法被拾取
return false;
}
for (let i = 0; i < n; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % n];
if (onSegment(p1, p2, [x, y])) {
// 点在多边形一条边上
return true;
}
// 前一个判断min(p1[1],p2[1])<P.y<=max(p1[1],p2[1])
// 后一个判断被测点 在 射线与边交点 的左边
if (
dcmp(p1[1] - y) > 0 !== dcmp(p2[1] - y) > 0 &&
dcmp(x - ((y - p1[1]) * (p1[0] - p2[0])) / (p1[1] - p2[1]) - p1[0]) < 0
) {
isHit = !isHit;
}
}
return isHit;
}
161 changes: 161 additions & 0 deletions src/math/is-polygons-intersect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { isPointInPolygon } from './is-point-in-polygon';

type Point = {
x: number;
y: number;
};

type Line = {
from: Point;
to: Point;
};

const isBetween = (value: number, min: number, max: number) => value >= min && value <= max;
function getLineIntersect(p0: Point, p1: Point, p2: Point, p3: Point): Point | null {
const tolerance = 0.001;
const E: Point = {
x: p2.x - p0.x,
y: p2.y - p0.y,
};
const D0: Point = {
x: p1.x - p0.x,
y: p1.y - p0.y,
};
const D1: Point = {
x: p3.x - p2.x,
y: p3.y - p2.y,
};
const kross: number = D0.x * D1.y - D0.y * D1.x;
const sqrKross: number = kross * kross;
const sqrLen0: number = D0.x * D0.x + D0.y * D0.y;
const sqrLen1: number = D1.x * D1.x + D1.y * D1.y;
let point: Point | null = null;
if (sqrKross > tolerance * sqrLen0 * sqrLen1) {
const s = (E.x * D1.y - E.y * D1.x) / kross;
const t = (E.x * D0.y - E.y * D0.x) / kross;
if (isBetween(s, 0, 1) && isBetween(t, 0, 1)) {
point = {
x: p0.x + s * D0.x,
y: p0.y + s * D0.y,
};
}
}
return point;
}

function parseToLines(points: number[][]): Line[] {
const lines = [];
const count = points.length;
for (let i = 0; i < count - 1; i++) {
const point = points[i];
const next = points[i + 1];
lines.push({
from: {
x: point[0],
y: point[1],
},
to: {
x: next[0],
y: next[1],
},
});
}
if (lines.length > 1) {
const first = points[0];
const last = points[count - 1];
lines.push({
from: {
x: last[0],
y: last[1],
},
to: {
x: first[0],
y: first[1],
},
});
}
return lines;
}

function lineIntersectPolygon(lines: Line[], line: Line) {
let isIntersect = false;
lines.forEach((l) => {
if (getLineIntersect(l.from, l.to, line.from, line.to)) {
isIntersect = true;
return false;
}
});
return isIntersect;
}

type BBox = {
minX: number;
minY: number;
maxX: number;
maxY: number;
};

function getBBox(points: number[][]): BBox {
const xArr = points.map((p) => p[0]);
const yArr = points.map((p) => p[1]);
return {
minX: Math.min.apply(null, xArr),
maxX: Math.max.apply(null, xArr),
minY: Math.min.apply(null, yArr),
maxY: Math.max.apply(null, yArr),
};
}

function intersectBBox(box1: BBox, box2: BBox) {
return !(box2.minX > box1.maxX || box2.maxX < box1.minX || box2.minY > box1.maxY || box2.maxY < box1.minY);
}

/**
* @see https://stackoverflow.com/questions/753140/how-do-i-determine-if-two-convex-polygons-intersect
*/
export function isPolygonsIntersect(points1: number[][], points2: number[][]) {
// 空数组,或者一个点返回 false
if (points1.length < 2 || points2.length < 2) {
return false;
}

const bbox1 = getBBox(points1);
const bbox2 = getBBox(points2);
// 判定包围盒是否相交,比判定点是否在多边形内要快的多,可以筛选掉大多数情况
if (!intersectBBox(bbox1, bbox2)) {
return false;
}

let isIn = false;
// 判定点是否在多边形内部,一旦有一个点在另一个多边形内,则返回
points2.forEach((point) => {
if (isPointInPolygon(points1, point[0], point[1])) {
isIn = true;
return false;
}
});
if (isIn) {
return true;
}
// 两个多边形都需要判定
points1.forEach((point) => {
if (isPointInPolygon(points2, point[0], point[1])) {
isIn = true;
return false;
}
});
if (isIn) {
return true;
}

const lines1 = parseToLines(points1);
const lines2 = parseToLines(points2);
let isIntersect = false;
lines2.forEach((line) => {
if (lineIntersectPolygon(lines1, line)) {
isIntersect = true;
return false;
}
});
return isIntersect;
}

0 comments on commit c9ad79b

Please sign in to comment.