You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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;
  };
问题分析

现有代码的核心问题是:直接通过父边界顶点的坐标差计算宽高,然后基于左上角顶点做轴对齐的偏移生成子多边形,完全忽略了父多边形的旋转状态。当父边界被旋转后,它的四个顶点已经不再是轴对齐的矩形,这种轴对齐的生成逻辑自然无法适配旋转后的区域。

解决方案

要解决这个问题,需要通过坐标旋转变换,将轴对齐生成的子多边形转换为适配父区域旋转角度的坐标。具体步骤如下:

  1. 计算父多边形的旋转中心和旋转角度
  2. 实现坐标旋转函数,基于旋转中心和角度转换点坐标
  3. 先在轴对齐的虚拟父矩形内生成子多边形,再对每个顶点应用旋转变换
修改后的代码
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 13:17:01