什么是凹凸多边形
凹多边形:如果一个多边形的所有边中有一条无限延伸到两条边形成一条直线,而其他边都不在这条直线的同一边,那么这个多边形叫做凹多边形,它的内角至少有一个钝角。
凸多边形:如果多边形的任意一条边向两边无限延伸形成一条直线,其他所有的边都在该直线的同一边,则称该多边形为凸多边形,其内角不应为钝角,任意两个顶点之间的线段位于多边形的内部或边上。
求多边形的中心坐标
不规则凸多边形
对于不规则凸多边形,我们可以按照下面的思路来求中间坐标:
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>