凹凸多边形求中间坐标

3,750 阅读2分钟

什么是凹凸多边形

凹多边形:如果一个多边形的所有边中有一条无限延伸到两条边形成一条直线,而其他边都不在这条直线的同一边,那么这个多边形叫做凹多边形,它的内角至少有一个钝角。

凸多边形:如果多边形的任意一条边向两边无限延伸形成一条直线,其他所有的边都在该直线的同一边,则称该多边形为凸多边形,其内角不应为钝角,任意两个顶点之间的线段位于多边形的内部或边上。

求多边形的中心坐标

不规则凸多边形

对于不规则凸多边形,我们可以按照下面的思路来求中间坐标:

1、首先,将所有坐标的x和y分别累加,得到总和。
2、然后,将总和除以坐标数量,即可得到平均坐标X和平均坐标Y。

代码如下:

function calculateCenter(coordinates) {
  let totalX = 0;
  let totalY = 0;

  // 计算总和
  for (let i = 0; i < coordinates.length; i++) {
    totalX += coordinates[i].X;
    totalY += coordinates[i].Y;
  }

  // 计算平均值
  const averageX = totalX / coordinates.length;
  const averageY = totalY / coordinates.length;

  return { X: averageX, Y: averageY };
}

// 示例坐标集合
const coordinates = [
  { X: 40.712776, Y: -74.005974 },
  { X: 34.052235, Y: -118.243683 },
  { X: 51.5074, Y: -0.1278 }
];

// 调用函数并打印结果
const center = calculateCenter(coordinates);
console.log("中心点坐标:", center);

这段代码会计算给定坐标数组(coordinates)的平均纬度和经度,并返回中心点坐标。

不规则多边形

如果不规则多边形是凹多边形,则计算的结果可能会在多变行外面,需要进一步优化

1、首先,将多边形的各个顶点按照顺序连接起来,形成一个封闭的路径。
2、使用路径上的点的平均值作为中心点的初始估计。
3、对于每个路径上的点,计算它到初始估计中心点的向量。
4、对于在形状内部的点,向量会指向中心点,而对于在形状外部的点,向量会指向外部。因此,通过对所有向量进行求和并取其反方向,得到一个修正向量。
5、将初始估计中心点坐标与修正向量相加,得到一个新的中心点估计。
6、重复步骤3-5,直到中心点估计收敛或达到设定的迭代次数。

代码如下:

function calculateCenter(vertices) {
  let centroidX = 0;
  let centroidY = 0;

  // 计算多边形的面积和中心点
  let signedArea = 0;
  let previousIndex = vertices.length - 1;
  for (let currentIndex = 0; currentIndex < vertices.length; currentIndex++) {
    const previousVertex = vertices[previousIndex];
    const currentVertex = vertices[currentIndex];

    const partialArea = (previousVertex.x * currentVertex.y) - (currentVertex.x * previousVertex.y);
    signedArea += partialArea;

    centroidX += (previousVertex.x + currentVertex.x) * partialArea;
    centroidY += (previousVertex.y + currentVertex.y) * partialArea;

    previousIndex = currentIndex;
  }

  signedArea /= 2;
  centroidX /= (6 * signedArea);
  centroidY /= (6 * signedArea);

  return { x: centroidX, y: centroidY };
}

// 示例多边形的顶点集合
const vertices = [
  { x: 10, y: 20 },
  { x: 30, y: 40 },
  { x: 50, y: 10 },
  { x: 70, y: 40 },
  { x: 90, y: 20 }
];

// 调用函数并打印结果
const center = calculateCenter(vertices);
console.log("中心点坐标:", center);

这段代码会计算给定简单多边形顶点集合(vertices)的中心点坐标。你可以根据需要修改顶点集合并尝试运行它。请注意,该代码假设多边形是一个简单多边形且顶点按照顺序提供。如果你要处理非简单多边形或无序顶点集合,就需要使用其他算法。

应用

下面是根据一些顶点绘制的多边形,并且将序号绘制在多变行内部

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="200" style="background-color: #b0c1fd;"></canvas>
    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const dpi = window.devicePixelRatio;
        canvas.style.width = canvas.width * dpi;
        canvas.style.height = canvas.height * dpi;
        const w = canvas.width;
        const h = canvas.height;
        // 原点坐标(1/3w,1/3h)
        const origin = {
            x: 1 / 3 * w,
            y: 1 / 2 * h
        }
        const coord = {
            // 大坝轮廓点
            boundingBox: [0, 160, -20, 80],
            area: { "0": [{ "x": 0, "y": 0, "num": 0 }, { "x": -45, "y": 0, "num": 1 }, { "x": -45, "y": -22.5, "num": 2 }, { "x": -30, "y": -10, "num": 5 }], "1": [{ "x": 0, "y": 0, "num": 0 }, { "x": -30, "y": -10, "num": 5 }, { "x": 40, "y": -20, "num": 6 }, { "x": 30, "y": 5, "num": -2 }, { "x": 10, "y": 5, "num": -1 }], "2": [{ "x": 30, "y": 5, "num": -2 }, { "x": 40, "y": -20, "num": 6 }, { "x": 70, "y": 10, "num": 7 }, { "x": 60, "y": 15, "num": -4 }, { "x": 50, "y": 15, "num": -3 }], "3": [{ "x": 60, "y": 15, "num": -4 }, { "x": 70, "y": 10, "num": 7 }, { "x": 100, "y": 3, "num": -5 }], "4": [{ "x": 100, "y": 3, "num": -5 }, { "x": 70, "y": 10, "num": 7 }, { "x": 145, "y": -22.5, "num": 3 }, { "x": 145, "y": 0, "num": 4 }], "5": [{ "x": -45, "y": -22.5, "num": 2 }, { "x": 145, "y": -22.5, "num": 3 }, { "x": 70, "y": 10, "num": 7 }, { "x": 40, "y": -20, "num": 6 }, { "x": -30, "y": -10, "num": 5 }] }
        }

        ctx.clearRect(0, 0, canvas.width, canvas.height);

        let heightRito = h / 2

        // 包围盒长
        const boundingBoxWidth = coord.boundingBox[1] - coord.boundingBox[0]
        // 包围盒宽
        const boundingBoxHeight = coord.boundingBox[3] - coord.boundingBox[2]

        // 高宽的比值, 此案例中boundingBoxHeight > boundingBoxWidth,因此使用高度为缩放标准
        const aspectRatio = boundingBoxWidth / boundingBoxHeight

        if ((boundingBoxWidth / boundingBoxHeight) < (w / h)) {
            heightRito = h / 2
        }
        else {
            const widthRito = 2 * w / 3
            heightRito = widthRito / aspectRatio
        }

        // 归一化x值
        function normalizationX(itemX) {
            return (itemX - coord.boundingBox[0]) / boundingBoxWidth
        }
        // 归一化y值
        function normalizationY(itemY) {
            return (itemY - coord.boundingBox[2]) / boundingBoxHeight
        }

        // 获取中心点
        function getPointsCenter(points) {
            let centroidX = 0;
            let centroidY = 0;

            // 计算多边形的面积和中心点
            let signedArea = 0;
            let previousIndex = points.length - 1;
            for (let currentIndex = 0; currentIndex < points.length; currentIndex++) {
                const previousVertex = points[previousIndex];
                const currentVertex = points[currentIndex];

                const partialArea = (previousVertex.x * currentVertex.y) - (currentVertex.x * previousVertex.y);
                signedArea += partialArea;

                centroidX += (previousVertex.x + currentVertex.x) * partialArea;
                centroidY += (previousVertex.y + currentVertex.y) * partialArea;

                previousIndex = currentIndex;
            }

            signedArea /= 2;
            centroidX /= (6 * signedArea);
            centroidY /= (6 * signedArea);

            return [centroidX, centroidY];
        }


        // ====== 参数化建模 ======
        Object.keys(coord.area).forEach(key => {
            ctx.moveTo(normalizationX(coord.area[key][0].x) * heightRito * aspectRatio + origin.x, h - origin.y);
            ctx.beginPath();
            ctx.strokeStyle = 'black';
            ctx.lineWidth = 1;
            coord.area[key].map((item) => {
                ctx.lineTo(normalizationX(item.x) * heightRito * aspectRatio + origin.x, h - (origin.y + normalizationY(item.y) * heightRito - normalizationY(0) * heightRito))
            })
            const pointCenter = getPointsCenter(coord.area[key]);
            ctx.fillStyle = "black";
            ctx.fillText(key, normalizationX(pointCenter[0]) * heightRito * aspectRatio + origin.x, h - (normalizationY(pointCenter[1]) * heightRito + origin.y - normalizationY(0) * heightRito))
            ctx.closePath();
            ctx.stroke();
        });
    </script>
</body>

</html>