数学 - 获取线围绕图表起点闭合的最小多边形

Posted

技术标签:

【中文标题】数学 - 获取线围绕图表起点闭合的最小多边形【英文标题】:Math - get the smallest polygon that the lines close around chart starting point 【发布时间】:2021-01-17 20:58:43 【问题描述】:

我正在尝试获取线条在图表起点周围创建的最小多边形的点,即最里面的多边形。线条因某些可以更改的参数而异。

此类图表的示例:

从图表中可以看出,这些线多次相交,因此创建了多个多边形。但是,我有兴趣只获取图表起点(中心)内的最小多边形。

我正在考虑从 y 轴到从顶部和底部向左和向右绘制多个与 x 轴的平行线(+y 和 -y 轴上的最小截距)并查看哪条线首先被“击中”为了得到将包围这个多边形的线,然后得到它们的交点以获得可用于绘制多边形的顶点。但是,由于要精确检查这些线需要很多点,我想知道是否有更优雅的解决方案?

【问题讨论】:

拜托,你能简化你的问题吗,别人很难理解你要解决的问题。不要让人们猜测问题可能是什么。 啊,抱歉。会尽量让它更清楚。 嗨!你熟悉线性规划吗?这个问题看起来非常类似于线性规划中遇到的概念。 【参考方案1】:

在 Stef 和他的算法的帮助下,我设法解决了这个问题。这是我使用的代码:

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 a.x - b.x;
        );
        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 = i.x * i.x + i.y * i.y;
        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) => p.x === startingPoint.x && p.y === 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) => p.x === startingPoint.x && p.y === 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) => v.x));
            if (uniqueValues.size < tempResult.length) 
                if (i === 0) result1 = tempResult;
                else result2 = tempResult;
                break;
            
            let newCrossedLines = intersections.filter((x) =>
                x.intersects.some(
                    (p) =>
                        p.x === tempResult[tempResult.length - 1].x &&
                        p.y === tempResult[tempResult.length - 1].y
                )
            );
            let newLine = newCrossedLines.filter((l) =>
                l.intersects.every(
                    (p) =>
                        p.x !== tempResult[tempResult.length - 2].x &&
                        p.y !== 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) =>
                    p.x === tempResult[tempResult.length - 1].x &&
                    p.y === 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;
;

基本上,首先我得到图表上所有线条的所有交点。然后我找到最接近图表起点 (0,0) 的多边形,因为包围它的最小多边形应该包含该顶点。之后,我开始沿着构成最近交叉点的两条线移动。重复这两条起始线的过程,我沿着这条线顺时针移动到下一个交叉点,然后沿着下一条线移动,继续这个过程,直到我在结果数组中得到一个重复的顶点,也就是说,直到多边形已经关闭。最后,我比较两个多边形并返回较小的那个。

很可能有一种更有效的方法可以做到这一点,但目前可行!

最终结果:

【讨论】:

【参考方案2】:

这是一个可能的算法:

未找到循环时: 从某点开始(x,y) 某行LL上顺时针方向查找下一个交点(x',y') 如果原点(0, 0) 在此行的右侧: x = x' y = y' L = 那条新线 如果找到一个环:这个环就是多边形。

【讨论】:

对不起,迟到的评论。我试图实现这个解决方案以及一个不同的解决方案,我最终得到了一些可行的东西。感谢您的帮助!

以上是关于数学 - 获取线围绕图表起点闭合的最小多边形的主要内容,如果未能解决你的问题,请参考以下文章

Canvas:绘制线和填充多边形

如何使对象回到java map中的起点

闭合多边形的算法

用于查找从点到多边形的最小距离的 Javascript 代码(由 html 区域定义)

opencv 多边形近似物体形状 cv.approxPolyDP函数的应用

CAD中都有哪些常用绘图命令?