简体   繁体   中英

Math - get the smallest polygon that the lines close around chart starting point

I'm trying to get the points of the smallest polygons that the lines create around the starting point of the chart, that is, the innermost polygon. The lines vary depending on some parameters that can be changed.

Example of such a chart:

在此处输入图片说明

As is visible from the chart, the lines intersect many times and, thus, create multiple polygons. However, I am interested in getting only the smallest polygon that is within the starting point (the center) of the chart.

I was thinking about drawing multiple parallels with x-axis from the y-axis to left and right from the top and the bottom (the smallest intercept on +y and -y axis) and seeing which line gets "hit" first as to get the lines which would enclose this polygon, then getting their intersections for vertices which can be used to drawing the polygon. However, since it will take numerous points to check such lines with precision, I am wondering if there is perhaps a more elegant solution to the problem?

I managed to solve this with the help of Stef and his algorithm. Here is the code I used:

 const getIntersections = async ( lines: IPoint[][] ): Promise<IIntersectLine[]> => { let lineIntersects: IIntersectLine[] = []; lines.forEach((line) => { let lineIntersect: IIntersectLine = { line: line, intersects: [], }; let x1 = line[1].x; let y1 = line[1].y; let x2 = line[2].x; let y2 = line[2].y; for (let i = 0; i < lines.length; i++) { let x3 = lines[i][1].x; let y3 = lines[i][1].y; let x4 = lines[i][2].x; let y4 = lines[i][2].y; if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) continue; let denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); if (denominator === 0) continue; let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator; let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator; if (ua < 0 || ua > 1 || ub < 0 || ub > 1) continue; let x = x1 + ua * (x2 - x1); let y = y1 + ua * (y2 - y1); lineIntersect.intersects.push({ x: +x.toFixed(4), y: +y.toFixed(4), }); } lineIntersect.intersects.sort((a, b) => { return ax - bx; }); lineIntersects.push(lineIntersect); }); return lineIntersects; }; const getStartingPoint = async (intersects: IPoint[]) => { let result: IPoint = intersects[0]; let distance = result.x * result.x + result.y * result.y; intersects.forEach((i) => { let newDistance = ix * ix + iy * iy; if (newDistance < distance) { distance = newDistance; result = i; } }); return result; }; const calcPolygonArea = async (polygon: IPoint[]) => { let total = 0; for (let i = 0, l = polygon.length; i < l; i++) { let addX = polygon[i].x; let addY = polygon[i == polygon.length - 1 ? 0 : i + 1].y; let subX = polygon[i == polygon.length - 1 ? 0 : i + 1].x; let subY = polygon[i].y; total += addX * addY * 0.5; total -= subX * subY * 0.5; } return Math.abs(total); }; export const getPolygonVertices = async (lines: IPoint[][]) => { let result: IPoint[] = []; let intersections = await getIntersections(lines); let intersectionVertices = intersections.map((x) => x.intersects).flat(); let startingPoint = await getStartingPoint(intersectionVertices); let crossedLines = intersections.filter((x) => x.intersects.some( (p) => px === startingPoint.x && py === startingPoint.y ) ); let newPoints: IPoint[] = []; const x0 = 0; const y0 = 0; crossedLines.forEach((line) => { let x1 = startingPoint.x; let y1 = startingPoint.y; let pointIndex = line.intersects.findIndex( (p) => px === startingPoint.x && py === startingPoint.y ); let d; if (line.intersects[pointIndex - 1]) { let x2 = line.intersects[pointIndex - 1].x; let y2 = line.intersects[pointIndex - 1].y; d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1); if (d > 0) newPoints.push({ x: x2, y: y2 }); } if (line.intersects[pointIndex + 1]) { let x2 = line.intersects[pointIndex + 1].x; let y2 = line.intersects[pointIndex + 1].y; d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1); if (d > 0) newPoints.push({ x: x2, y: y2 }); } }); let result1: IPoint[] = []; let result2: IPoint[] = []; for (let i = 0; i < newPoints.length; i++) { let tempResult: IPoint[] = []; tempResult.push(startingPoint, newPoints[i]); for (let j = 0; j < 50; j++) { const uniqueValues = new Set(tempResult.map((v) => vx)); if (uniqueValues.size < tempResult.length) { if (i === 0) result1 = tempResult; else result2 = tempResult; break; } let newCrossedLines = intersections.filter((x) => x.intersects.some( (p) => px === tempResult[tempResult.length - 1].x && py === tempResult[tempResult.length - 1].y ) ); let newLine = newCrossedLines.filter((l) => l.intersects.every( (p) => px !== tempResult[tempResult.length - 2].x && py !== tempResult[tempResult.length - 2].y ) )[0]; let x1 = tempResult[tempResult.length - 1].x; let y1 = tempResult[tempResult.length - 1].y; let pointIndex = newLine.intersects.findIndex( (p) => px === tempResult[tempResult.length - 1].x && py === tempResult[tempResult.length - 1].y ); let d; if (newLine.intersects[pointIndex - 1]) { let x2 = newLine.intersects[pointIndex - 1].x; let y2 = newLine.intersects[pointIndex - 1].y; d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1); if (d > 0) tempResult.push({ x: x2, y: y2 }); } if (newLine.intersects[pointIndex + 1]) { let x2 = newLine.intersects[pointIndex + 1].x; let y2 = newLine.intersects[pointIndex + 1].y; d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1); if (d > 0) tempResult.push({ x: x2, y: y2 }); } } } const area1 = await calcPolygonArea(result1); const area2 = await calcPolygonArea(result2); area1 < area2 ? (result = result1) : (result = result2); return result; };

Essentially, first I get all the intersections of all the lines on the chart. Then I find the closest one to the chart's starting point (0,0) as the smallest polygon enclosing it should contain that vertex. After that, I begin moving along the two lines that make up the closest intersection. Repeating the process for those two starting lines, I move clockwise along the line up to the next intersection, where I then move along the next line, continuing the process until I get a duplicate vertex in my result array, that is, until the polygon is closed. In the end, I compare the two polygons and return the smaller one.

There is most likely a more efficient way to do this, but this works for now!

End result:

在此处输入图片说明

Here is a possible algorithm:

  • While a cycle was not found:
    • Start at some point (x,y) on some line L
    • Find next intersection point (x',y') on L in clockwise direction
    • If the origin (0, 0) is on the right of this line:
      • x = x'
      • y = y'
      • L = that new line
  • If a cycle was found: this cycle is the polygon.

算法图解

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM