interface Point { x: number; y: number; [proppName: string]: any; } /** * 缩放多边形坐标 * https://blog.csdn.net/sun_and_breeze/article/details/107517088 * @decoration 需配合顺时针判断方法一起使用 * @param {Point[]} points 点坐标数组 [{x:0,y:0}...] * @param {number} extra 外延大小。为正: 向外扩; 为负: 向内缩 * @return {Point[]} 扩展或缩小后的多边形点坐标数组 */ export function scalePolygon(points: Point[], extra: number) { if (!Array.isArray(points) || points.length < 3) { console.error('多边形坐标集合不能少于3个'); return; } const ps = points; // 通过顺时针判断取正值还是负值 const extra0 = isClockwise(ps) ? -extra : extra; const norm = (x: number, y: number) => Math.sqrt(x * x + y * y); const len = ps.length; const polygon = []; for (let i = 0; i < len; i++) { const point = ps[i]; const point1 = ps[i === 0 ? len - 1 : i - 1]; const point2 = ps[i === len - 1 ? 0 : i + 1]; // 向量PP1 const vectorX1 = point1.x - point.x; // 向量PP1 横坐标 const vectorY1 = point1.y - point.y; // 向量PP1 纵坐标 const n1 = norm(vectorX1, vectorY1); // 向量的平方根 为了对向量PP1做单位化 let vectorUnitX1 = vectorX1 / n1; // 向量单位化 横坐标 let vectorUnitY1 = vectorY1 / n1; // 向量单位化 纵坐标 // 向量PP2 const vectorX2 = point2.x - point.x; // 向量PP2 横坐标 const vectorY2 = point2.y - point.y; // 向量PP2 纵坐标 const n2 = norm(vectorX2, vectorY2); // 向量的平方根 为了对向量PP1做单位化 let vectorUnitX2 = vectorX2 / n2; // 向量单位化 横坐标 let vectorUnitY2 = vectorY2 / n2; // 向量单位化 纵坐标 // PQ距离 const vectorLen = -extra0 / Math.sqrt( (1 - (vectorUnitX1 * vectorUnitX2 + vectorUnitY1 * vectorUnitY2)) / 2 ); // 根据向量的叉乘积来判断角是凹角还是凸角 if (vectorX1 * vectorY2 + -1 * vectorY1 * vectorX2 < 0) { vectorUnitX2 *= -1; vectorUnitY2 *= -1; vectorUnitX1 *= -1; vectorUnitY1 *= -1; } // PQ的方向 const vectorX = vectorUnitX1 + vectorUnitX2; const vectorY = vectorUnitY1 + vectorUnitY2; const n = vectorLen / norm(vectorX, vectorY); const vectorUnitX = vectorX * n; const vectorUnitY = vectorY * n; const polygonX = vectorUnitX + point.x; const polygonY = vectorUnitY + point.y; polygon[i] = { x: polygonX, y: polygonY }; } return polygon; } /** * 判断坐标数组是否顺时针(默认为false) * @param {Point[]} points 点坐标数组 [{x:0,y:0}...] * @returns {boolean} 是否顺时针 */ export function isClockwise(points: Point[]) { // 三个点可以判断矢量是顺时针旋转还是逆时针旋转的,但由于可能存在凹边,所以并不是任意三点都可以正确反映多边形的走向 // 因此需要取多边形中绝对是凸边的点来判断, // 多边形中的极值点(x最大或x最小或y最大或y最小)它与相邻两点构成的边必然是凸边,因此我们先取出多边形中的极值点,再由极值点和其前后两点去判断矢量的走向,从而判断出多边形的走向。 if (!Array.isArray(points) || points.length < 3) { console.error('多边形坐标集合不能少于3个'); return false; } let coords = JSON.parse(JSON.stringify(points)); if (coords[0] === coords[coords.length - 1]) { coords = coords.slice(0, coords.length - 1); } coords = coords.reverse(); let maxXIndex = 0; let maxX = parseFloat(coords[maxXIndex].x); let c1; let c2; let c3; for (let i = 0; i < coords.length; i++) { if (parseFloat(coords[i].x) > maxX) { maxX = parseFloat(coords[i].x); maxXIndex = i; } } if (maxXIndex === 0) { c1 = coords[coords.length - 1]; c2 = coords[maxXIndex]; c3 = coords[maxXIndex + 1]; } else if (maxXIndex === coords.length - 1) { c1 = coords[maxXIndex - 1]; c2 = coords[maxXIndex]; c3 = coords[0]; } else { c1 = coords[maxXIndex - 1]; c2 = coords[maxXIndex]; c3 = coords[maxXIndex + 1]; } const x1 = parseFloat(c1.x); const y1 = parseFloat(c1.y); const x2 = parseFloat(c2.x); const y2 = parseFloat(c2.y); const x3 = parseFloat(c3.x); const y3 = parseFloat(c3.y); const s = (x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3); return s < 0; }