基于react-leaflet/Leaflet Geoman的旋转边界多边形生成算法问题
问题描述
我使用react-leaflet和Leaflet Geoman开发音乐节露营位生成功能,现有算法可在给定边界内生成小多边形,但当父边界被旋转后,生成的子多边形仍按未旋转的轴对齐方式创建,无法适配旋转后的区域。用户操作流程为:先选择大方形作为露营场地,旋转该区域后点击保存触发生成算法。我自行修改过算法,也尝试过多种方式解决,但始终未能搞定,当前算法代码如下:
const createPolygons = ( parentBounds: number[][], Xamt: number, Yamt: number ) => { const parentWidth = parentBounds[1][0] - parentBounds[0][0]; const parentHeight = parentBounds[2][1] - parentBounds[0][1]; const childWidth = parentWidth / Yamt; const childHeight = parentHeight / Xamt; const newField: BookableResource[] = []; let counter = startNumberRef ? startNumberRef.current - 1 : 0; for (let y = 0; y < Xamt; y++) { for (let x = 0; x < Yamt; x++) { counter += 1; const startX = parentBounds[0][0] + x * childWidth; const startY = parentBounds[0][1] + y * childHeight; const endX = startX + childWidth; const endY = startY + childHeight; const childBounds: number[][] = [ [startX, startY], [startX, endY], [endX, endY], [endX, startY], ]; newField.push({ resourceId: counter, resourceNumber: counter, resourceCode: `${fieldLetterRef.current.toUpperCase()}${counter}`, bookingStatus: "PublicBookable", resourceName: `${ resourceGroupRef.current.groupName } Plass ${fieldLetterRef.current.toUpperCase()}${counter}`, resourceGroupId: resourceGroupRef.current.resourceGroupID, partGroupId: itemGroupRef.current.partGroupId, placement: { bounds: childBounds, resourceId: counter, available: true, }, }); } } /* startNumberRef.current += newField.length; */ /* setPolygons([...polygons, ...newField]); */ setCreatePolygon(false); return newField; };
问题分析
现有代码的核心问题是:直接通过父边界顶点的坐标差计算宽高,然后基于左上角顶点做轴对齐的偏移生成子多边形,完全忽略了父多边形的旋转状态。当父边界被旋转后,它的四个顶点已经不再是轴对齐的矩形,这种轴对齐的生成逻辑自然无法适配旋转后的区域。
解决方案
要解决这个问题,需要通过坐标旋转变换,将轴对齐生成的子多边形转换为适配父区域旋转角度的坐标。具体步骤如下:
- 计算父多边形的旋转中心和旋转角度
- 实现坐标旋转函数,基于旋转中心和角度转换点坐标
- 先在轴对齐的虚拟父矩形内生成子多边形,再对每个顶点应用旋转变换
修改后的代码
const createPolygons = ( parentBounds: number[][], Xamt: number, Yamt: number ) => { // 提取父多边形的四个顶点(假设顺序为连续的顺时针/逆时针顶点) const [p0, p1, p2, p3] = parentBounds; // 计算父多边形的中心坐标(对角线中点) const centerX = (p0[0] + p2[0]) / 2; const centerY = (p0[1] + p2[1]) / 2; // 计算旋转角度:基于p0到p1的向量与x轴的夹角(弧度) const dx = p1[0] - p0[0]; const dy = p1[1] - p0[1]; const rotationAngle = Math.atan2(dy, dx); // 计算父矩形的实际宽高(旋转前的轴对齐尺寸) const parentWidth = Math.sqrt(dx ** 2 + dy ** 2); const parentHeight = Math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2); const childWidth = parentWidth / Yamt; const childHeight = parentHeight / Xamt; const newField: BookableResource[] = []; let counter = startNumberRef ? startNumberRef.current - 1 : 0; // 坐标旋转工具函数:将点(x,y)绕(centerX, centerY)旋转指定弧度 const rotatePoint = (x: number, y: number): number[] => { const translatedX = x - centerX; const translatedY = y - centerY; const rotatedX = translatedX * Math.cos(rotationAngle) - translatedY * Math.sin(rotationAngle); const rotatedY = translatedX * Math.sin(rotationAngle) + translatedY * Math.cos(rotationAngle); return [rotatedX + centerX, rotatedY + centerY]; }; for (let y = 0; y < Xamt; y++) { for (let x = 0; x < Yamt; x++) { counter += 1; // 先计算轴对齐状态下子矩形的坐标(基于父矩形旋转前的左上角) const parentTopLeftX = centerX - parentWidth / 2; const parentTopLeftY = centerY - parentHeight / 2; const startX = parentTopLeftX + x * childWidth; const startY = parentTopLeftY + y * childHeight; const endX = startX + childWidth; const endY = startY + childHeight; // 对子矩形的四个顶点应用旋转变换 const childBounds: number[][] = [ rotatePoint(startX, startY), rotatePoint(startX, endY), rotatePoint(endX, endY), rotatePoint(endX, startY), ]; newField.push({ resourceId: counter, resourceNumber: counter, resourceCode: `${fieldLetterRef.current.toUpperCase()}${counter}`, bookingStatus: "PublicBookable", resourceName: `${ resourceGroupRef.current.groupName } Plass ${fieldLetterRef.current.toUpperCase()}${counter}`, resourceGroupId: resourceGroupRef.current.resourceGroupID, partGroupId: itemGroupRef.current.partGroupId, placement: { bounds: childBounds, resourceId: counter, available: true, }, }); } } setCreatePolygon(false); return newField; };
补充说明
- 代码假设父多边形的顶点顺序是连续的顺时针或逆时针排列,如果你的
parentBounds顶点顺序不同,需要调整p0到p3的对应关系 - 旋转角度通过
Math.atan2计算,返回的是弧度值,可直接用于三角函数计算 - 先基于旋转前的轴对齐父矩形生成子区域,再整体旋转到父区域的角度,确保生成的子多边形完美适配旋转后的父边界
内容的提问来源于stack exchange,提问作者Magnus Pladsen
相关产品推荐
相关产品推荐

