The cell method approximates the problem of the largest inscribed rectangle of a polygon [Explanation of ideas + Java implementation]

Problem Description

Given a polygon point set, we want to find the rectangle with the largest internal area of ​​the polygon. This problem may occur when cutting out the largest rectangle from a polygonal waste material. The rectangle can be reused. Solving this problem can save raw materials and reduce business operating costs.

problem solution

The main method used in this article is to discretize the polygon into multiple cells, then determine which cells are inside the polygon, and finally find the rectangle with the largest area by traversing the internal cells.

Polygon meshing

It is known that the coordinates of each point in each polygon point set are (y, z). By traversing all points, we can find out minY, maxY, minZ, maxZ. This article uses a parameter segmentNumto determine the number of discrete segments of the polygon in the y-axis direction, dis=(maxY-minY)/segmentNumas Step length, every step length generates a scan line perpendicular to the y-axis, which will intersect the polygon. This article generates the z coordinate value of the intersection point into a scan line perpendicular to the z-axis. Finally, after the scan lines perpendicular to the y-axis or z-axis are generated, you will get a grid as shown in the figure below

Insert image description here

The grid corresponding to segmentNum=5

Insert image description here

The grid corresponding to segmentNum=100

It should be noted that in order to speed up calculations, certain strategies can be used to filter out some scan lines. If the distance between two scan lines is very close, only one of them needs to be used. This article uses a very simple strategy. , for example, y=1.2, y=1.3, use a map to record (int)(1.2)=1, because int(1.3) is also equal to 1, the scan line will be discarded

[java code implementation]

/**
 * 构建网络单元
 *
 * @param pointList
 * @return
 */
private Cell[][] constructCells(List<Point> pointList) {
    
    
    声明变量
    Cell[][] cells;

    开始构建
    List<Double> yList = new ArrayList<>();
    List<Double> zList = new ArrayList<>();
    Map<Integer, Boolean> yMap = new HashMap<>();
    Map<Integer, Boolean> zMap = new HashMap<>();
    for (Point point : pointList) {
    
    
        if (!yMap.containsKey((int) point.getY())) {
    
    
            yList.add(point.getY());
            yMap.put((int) point.getY(), true);
        }
        if (!zMap.containsKey((int) point.getZ())) {
    
    
            zList.add(point.getZ());
            zMap.put((int) point.getZ(), true);
        }
    }
    根据分段继续添加yList和zList
    double minY = Double.MAX_VALUE;
    double maxY = -Double.MAX_VALUE;
    double minZ = Double.MAX_VALUE;
    double maxZ = -Double.MAX_VALUE;
    for (Point point : pointList) {
    
    
        minY = Math.min(minY, point.getY());
        maxY = Math.max(maxY, point.getY());
        minZ = Math.min(minZ, point.getZ());
        maxZ = Math.max(maxZ, point.getZ());
    }
    ///根据分段数来确定y的增量
    double deltaOfY = (maxY - minY) * 1.0 / this.segmentNum;
    double curY = minY + deltaOfY;
    while (curY < maxY) {
    
    
        if (yMap.containsKey(curY)) {
    
    
            curY += deltaOfY;
            continue;
        }
        boolean isAdd = false;
        // 对于每一根y扫描线,都拿来和多边形的每条边相交一下,来找到z扫描线
        for (int i = 0; i < pointList.size(); i++) {
    
    
            int j = (i + 1) % pointList.size();
            //获取边的两个点
            Point point1 = pointList.get(i);
            Point point2 = pointList.get(j);
            double minYOfSide = Math.min(point1.getY(), point2.getY());
            double maxYOfSide = Math.max(point1.getY(), point2.getY());
            if (minYOfSide < curY && curY < maxYOfSide) {
    
    
                ///对边构建表达式
                double curZ = this.getZByYAndLineMessage(point1, point2, curY);
                if (curZ == Double.MAX_VALUE) {
    
    
                    continue;
                }
                if (!zMap.containsKey((int) curZ)) {
    
    
                    zList.add(curZ);
                    zMap.put((int) curZ, true);
                }
                isAdd = true;
            }
        }
        if (isAdd == true) {
    
    
            yList.add(curY);
            yMap.put((int) curY, true);
        }
        curY += deltaOfY;
    }
    //对yList、zList升序排序
    Collections.sort(yList);
    Collections.sort(zList);
    cells = new Cell[zList.size() - 1][yList.size() - 1];
    for (int j = 0; j < zList.size() - 1; j++) {
    
    
        for (int i = 0; i < yList.size() - 1; i++) {
    
    
            Point left_up_point = new Point(0, yList.get(i), zList.get(j));
            Point right_down_point = new Point(0, yList.get(i + 1), zList.get(j + 1));
            cells[j][i] = new Cell(left_up_point, right_down_point);
        }
    }
    return cells;
}

Differentiate whether each cell is inside or outside the polygon

The distinction method used in this article is to judge whether the four points of a cell are all inside the polygon. To judge whether a point is inside the polygon, you can refer to the article Ray Method - Judging whether a point is inside the polygon ( applicable to convex polygons and Concave polygon) [Key principle explanation + text pseudo code + java code implementation] , as long as there is a point that is not inside the polygon, the cell is marked 0, indicating that it is not inside the polygon. If the four points of the cell are all inside the polygon, it is not enough to prove that the cell is really inside the polygon. The following figure is an example. Therefore, it is also necessary to
Insert image description here
determine whether the sides of the cell and the sides of the polygon intersect. If so, then the cell is not completely inside the polygon. After all cells are judged to be completely inside the polygon and marked as 1, the following figure is obtained
Insert image description here
[java implementation]

/**
 * 标记网络单元,当一个单元处于多边形内部时,将单元标记为 1,否则标记为 0
 *
 * @param pointList
 * @param cells
 */
private void markCellsLabel(List<Point> pointList, Cell[][] cells) throws Exception {
    
    
    long start = System.currentTimeMillis();
    for (int i = 0; i < cells.length; i++) {
    
    
        for (int j = 0; j < cells[i].length; j++) {
    
    
            cells[i][j].setLabel(this.getCellLabel(pointList, cells[i][j]));
        }
    }
    long end = System.currentTimeMillis();
    this.setCellsLabelTime += end - start;
}

/**
 * 判断单元格是否被纯包含于多边形内,是:返回1;否:返回0
 * 单元格中有一个点不在多边形内,单元格便是不在单元格内
 * type=0:第一次设置label时使用; type=1:reset label时使用;
 *
 * @param pointList
 * @param cell
 */
private int getCellLabel(List<Point> pointList, Cell cell) throws Exception {
    
    
    // 存储单元格的四个点
    Point[] pointArr = new Point[4];
    //左上角
    pointArr[0] = new Point(0, cell.getLeft_up_point().getY(), cell.getLeft_up_point().getZ());
    //右上角
    pointArr[1] = new Point(0, cell.getRight_down_point().getY(), cell.getLeft_up_point().getZ());
    //右下角
    pointArr[2] = new Point(0, cell.getRight_down_point().getY(), cell.getRight_down_point().getZ());
    //左下角
    pointArr[3] = new Point(0, cell.getLeft_up_point().getY(), cell.getRight_down_point().getZ());
    
    for (Point point : pointArr) {
    
    
        //只要有一个点不在多边形内,单元格被标记为不在多边形内
        if (PolygonUtil.judgeWhetherThePointInPolygon(pointList, point) == false) {
    
    
            return 0;
        }
    }

    //就算单元格的所有点都在多边形内部,不代表单元格就在多边形内了
    //再判断一下网络单元的四条边是否和线段有相交,且斜率不能相同,为了避免出现较特殊矩形时,单元的四个点都在多边形内部,但是实际上不在内部的情况出现
    for (int i = 0; i < pointArr.length; i++) {
    
    
        int j = (i + 1) % pointArr.length;
        for (int m = 0; m < pointList.size(); m++) {
    
    
            int n = (m + 1) % pointList.size();
            if (PolygonUtil.judgeWhetherTwoLineSegmentIsIntersect(pointArr[i], pointArr[j], pointList.get(m), pointList.get(n)) == true &&
                    (this.getKOfLine(pointArr[i], pointArr[j]) != this.getKOfLine(pointList.get(m), pointList.get(n)))) {
    
    
                //--if--当单元格的边和多边形有交叉,且不是平行的那种时,单元不在多边形内部
                return 0;
            }
        }
    }
//            System.out.println("单元格在多边形内部");
    return 1;
}

Find the largest inscribed rectangle based on marked cells

This article's method of finding the largest inscribed rectangle is as follows: traverse each cell, try to start at that cell, and then search for cells to the right and down to combine to find the largest rectangle of the cell. During the traversal, the rectangle with the largest area is constantly updated, and the result can be obtained after the traversal is completed.

When traversing to a cell, start from the cell, traverse each column to the right, find the corresponding continuous height of each column and fill it in heightArr, as shown in the figure below. In the same way, traverse each row, find the continuous width of each row, fill it into center widthArr, and fill the column index of the cell with the maximum continuous width of each row into maxColumnNumArrcenter
Insert image description here
. The specific operation is as shown in the following code.

/*
* 只需要设置一次heightArr,后面需要多少列直接拿就行
* 只需要设置一次widthArr,下一列的格子的所能找到的最大宽度直接为当前格子所能找到的最大宽度-当前格子宽度
*/
double[] widthArr = new double[cells.length - i];
// 存储每一行到达最大宽度时的列数
int[] maxColumnNumArr = new int[cells.length - i];
double[] heightArr = new double[cells[0].length - j];

/// 初始化heightArr
for (int x = j; x < heightArr.length + j; x++) {
    
    
   // --for--循环每一列
   double heightOfCurColumn = 0;
   for (int y = i; y < cells.length; y++) {
    
    
       // --for--循环每一行
       if (cells[y][x].getLabel() == 1) {
    
    
           heightOfCurColumn += cells[y][x].getHeight();
       } else {
    
    
           break;
       }
   }
   heightArr[x - j] = heightOfCurColumn;
   if (Math.abs(heightOfCurColumn - 0) < this.precisionError) {
    
    
       // 相当于这一列开始,后面的列全是默认为0,因为所形成的矩形需要被最矮的一列限制
       break;
   }
}

/// 初始化 widthArr 和 maxColumnNumArr
for (int m = i; m < widthArr.length + i; m++) {
    
    
   // --for--循环每一行
   double widthOfCurRow = 0;
   // 找最大宽度所到达的列数
   int maxColumnNum = j;
   int num = 0;
   for (int k = j; k < cells[0].length; k++) {
    
    
       // --for--循环每一列
       if (cells[m][k].getLabel() == 1) {
    
    
           widthOfCurRow += cells[m][k].getWidth();
           num++;
       } else {
    
    
           break;
       }
   }
   maxColumnNum += num > 0 ? num - 1 : 0;
   // 存储当前行宽度
   widthArr[m - i] = widthOfCurRow;
   maxColumnNumArr[m - i] = maxColumnNum;
}

When traversing to a cell, you also need to do the following things to find the largest rectangle corresponding to the current cell. Because the text is too difficult to describe, I directly posted the code o(╥﹏╥)o

/// 找到最小的n
// 从widthArr和nArr来看
int minN = Integer.MAX_VALUE;
for (int index = 0; index < maxColumnNumArr.length; index++) {
    
    
   if (maxColumnNumArr[index] > 0) {
    
    
       minN = Math.min(minN, maxColumnNumArr[index]);
   } else {
    
    
       break;
   }
}
if (minN == Integer.MAX_VALUE) {
    
    
   // 将minN赋值为j-1,这样下面就无需循环寻找结果了
   minN = j - 1;
}
// 从heightArr来看
int minNOfHeightArr = j;
for (int k = 1; k < heightArr.length; k++) {
    
    
   if (heightArr[k] <= heightArr[k - 1]) {
    
    
       minNOfHeightArr++;
   } else {
    
    
       break;
   }
}
minN = Math.min(minN, minNOfHeightArr);

/// 【寻找最大面积的矩形】
int columnNum = 0;
int recordJ = j;
if (j <= minN) {
    
    
   while (j <= minN) {
    
    
       if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
    
    
           for (int m = i; m < widthArr.length + i; m++) {
    
    
               // --for--循环每一行
               double widthOfCurRow = widthArr[m - i];
               // 找最大宽度所到达的列数
               int maxColumnNum = maxColumnNumArr[m - i];
               if ((m - i - 1) >= 0 && maxColumnNum == maxColumnNumArr[m - i - 1]) {
    
    
                   // --if-- 最大列数和上一个的最大列数一样,可以直接跳过此轮循环,直接进入下一轮
                   continue;
               }
               if (Math.abs(widthOfCurRow - 0) < this.precisionError) {
    
    
                   break;
               } else {
    
    
                   // 当前行对应的最大宽度
                   double maxWid = widthOfCurRow;
                   // 寻找当前宽度对应的最小高度
                   double maxHei = Integer.MAX_VALUE;
                   boolean isAssign = false;
                   for (int k = columnNum; k < maxColumnNum - recordJ + 1; k++) {
    
    
                       maxHei = Math.min(maxHei, heightArr[k]);
                       isAssign = true;
                   }
                   // 更新最优解
                   if (maxWid * maxHei > bestArea && isAssign == true) {
    
    
                       bestArea = maxWid * maxHei;
                       bestRectangle = new LoadProductDto(maxWid, maxHei, cells[i][j].getLeft_up_point().getY(), cells[i][j].getLeft_up_point().getZ());
                   }
               }
           }
       }
       // 更新widthArr
       for (int m = i; m < widthArr.length + i; m++) {
    
    
           if (widthArr[m - i] > 0) {
    
    
               // 每一行的width减去当前列的宽度
               widthArr[m - i] -= cells[m][j].getWidth();
           } else {
    
    
               break;
           }
       }
       j++;
       columnNum++;
       break;
   }
   // 上面的j++已经超出minN了,重新回到minN,因为循环的时候,j会++,如果不回到minN,会漏掉一列
   j = minN;
}

The code snippet annotated as [Finding the rectangle with the largest area] in the code mainly does the following things, which can be understood in conjunction with the following series of pictures.

Insert image description here
[Start looking for rectangles from the first column]
Insert image description here
Insert image description here
Insert image description here
[Start looking for rectangles from the second column]
Insert image description here
---- and so on----
Insert image description here
[After the above process is completed, the next traversed cell is as shown below, because y has become 】minN+1_
Insert image description here

You may be confused, why do you need this code to limitminN

// 从heightArr来看
int minNOfHeightArr = j;
for (int k = 1; k < heightArr.length; k++) {
    
    
   if (heightArr[k] <= heightArr[k - 1]) {
    
    
       minNOfHeightArr++;
   } else {
    
    
       break;
   }
}
minN = Math.min(minN, minNOfHeightArr);

If there is no restriction, the traversal situation is as shown in the figure.
Insert image description here
To put it more directly, you will not find the following type of results.
Insert image description here

Pruning optimization

When traversing from the upper left corner of the grid to the lower right corner of the grid, the rectangles that can be found are actually very small. If it can be predicted in advance that the largest rectangular area that can be found in a cell is smaller than the largest rectangular area currently found. If so, the cell does not need to be traversed and can be skipped directly.

This article uses the recursive method to set the ideal optimal rectangular area that a cell can find. The code is as follows:

/**
* 填充单元格的理想最大面积
*
* @param cells
*/
private void fillCellsMaxArea(Cell[][] cells) {
    
    
    填补每个单元格所能找到的理想最大面积(递推)
   /// 初始化网络单元的最后一行和最后一列
   // 初始化最后一行
   for (int i = 0; i < cells[cells.length - 1].length; i++) {
    
    
       //对每一列的最后一个单元进行处理
       Cell cell = cells[cells.length - 1][i];
       cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
   }
   // 初始化最后一列
   for (int i = 0; i < cells.length; i++) {
    
    
       //对每一行的最后一个单元进行处理
       Cell cell = cells[i][cells[0].length - 1];
       cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
   }
   /// 填充其他单元格的理想最大面积
   for (int i = cells.length - 2; i >= 0; i--) {
    
    
       for (int j = cells[0].length - 2; j >= 0; j--) {
    
    
           Cell cell = cells[i][j];
           //先填充自己的面积
           cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
           //再添加自己下面的单元格maxArea和右侧的单元格的maxArea
           if (i + 1 < cells.length) {
    
    
               cell.setMaxArea(cell.getMaxArea() + cells[i + 1][j].getMaxArea());
           }
           if (j + 1 < cells[0].length) {
    
    
               cell.setMaxArea(cell.getMaxArea() + cells[i][j + 1].getMaxArea());
           }
       }
   }
}

When the mark of the satisfied cell is 1, and the ideal maximum rectangular area exceeds the currently found maximum rectangular area, the above step of [finding the largest inscribed rectangle based on the marked cell] will be performed based on the cell.

// 对每个被标记为1的单元格,将其向右、向下遍历,计算形成的最大矩形
for (int i = 0; i < cells.length; i++) {
    
    
    for (int j = 0; j < cells[0].length; j++) {
    
    
        if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
    
    
            // 根据已标记单元格寻找最大内接矩形
        }
    }
}

Multi-angle rotation

After the above method is completed, only the maximum inscribed rectangle at one angle can be obtained, which will lead to the situation shown in the following figure.

Insert image description here
This article solves a maximum inscribed rectangle for every 5° rotation, and finally selects the largest rectangle as the final result. Of course, if you want to find better results, you can continue to reduce the rotation angle. Of course, the calculation time will also increase a lot.
Insert image description here

case test

Insert image description hereInsert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here

Code

package com.ruoyi.algorithm.get_maximumArea_rectangles_in_a_simple_polygon.version.v1;

import com.ruoyi.algorithm.common.entity.Cell;
import com.ruoyi.algorithm.common.entity.GetMaxRectangleInSimplePolygonResult;
import com.ruoyi.algorithm.common.entity.LoadProductDto;
import com.ruoyi.algorithm.common.entity.Point;

import java.util.*;

/**
 * 获取多边形内的最大内接矩形
 */
public class GetMaximumAreaRectangleInASimplePolygonApi {
    
    

    /**
     * 对边进行分段处理,增加更多的点
     */
    private int segmentNum = 100;
    /**
     * 是否打印标记矩阵
     */
    private boolean isPrintLabelMatrix = false;
    /**
     * paintType=0:画未补充点的线;paintType=1:画补充点后的线
     */
    private int paintType = 0;
    /**
     * 旋转度数
     */
    private int rotateDegreeDelta = 5;
    /**
     * 精度误差
     */
    private double precisionError = 0.0001;
    /**
     * 标记单元格时间
     */
    private long setCellsLabelTime = 0;
    /**
     * 根据已有解重置单元格标记的时间
     */
    private long resetCellsLabelTime = 0;
    /**
     * 给定单元格,寻找解的时间
     */
    private long searchSolutionTime = 0;

    /**
     * 且多边形的最大内接矩形
     *
     * @param pointList         顺时针或逆时针将点集传进来
     * @param bestRectangleList 可能有多个相等的内接矩形,都存储下来
     */
    public GetMaxRectangleInSimplePolygonResult solve(List<Point> pointList, List<LoadProductDto> bestRectangleList) throws Exception {
    
    
        System.out.println();

        long start = System.currentTimeMillis();

        声明变量
        List<Point> solution = new ArrayList<>();
        GetMaxRectangleInSimplePolygonResult getMaxRectangleInSimplePolygonResult = new GetMaxRectangleInSimplePolygonResult();

        存储到结果的数据
        List<Double> yList = new ArrayList<>();
        List<Double> zList = new ArrayList<>();
        double minY = Double.MAX_VALUE;
        double maxY = -Double.MAX_VALUE;
        double minZ = Double.MAX_VALUE;
        double maxZ = -Double.MAX_VALUE;
        Cell[][] cells = null;
        for (Point point : pointList) {
    
    
            minY = Math.min(minY, point.getY());
            maxY = Math.max(maxY, point.getY());
            minZ = Math.min(minZ, point.getZ());
            maxZ = Math.max(maxZ, point.getZ());
        }

        找最大矩形
        ///声明变量
        //记录最大矩形的面积
        double bestArea = 0;
        //记录最佳旋转角度
        int bestRotateDegree = 0;
        LoadProductDto bestRectangle = null;
        List<Point> bestSolution = new ArrayList<>();
        //记录当前所遍历到的角度
        int rotateDegree = 0;
        int endRotateDegree = 360;
        while (rotateDegree <= endRotateDegree) {
    
    
//            System.out.println("当前角度:"+rotateDegree);
            //每旋转rotateDegreeDelta,求一次结果,取中间的最优解
            LoadProductDto curRectangle = this.solveWithAppointRotateDegree(Point.cloneList(pointList), rotateDegree, solution);
            if (curRectangle == null) {
    
    
                rotateDegree += this.rotateDegreeDelta;
                continue;
            }
            //更新结果
            double curArea = curRectangle.getWidth() * curRectangle.getHeight();
            if ((bestRectangle == null) || (curArea > bestArea)) {
    
    
                System.out.println("找到矩形1-------------------------");
                //找curRectangle的4个角点
                List<Point> curSolution = new ArrayList<>();
                Point leftUpPoint = new Point(0, curRectangle.getY(), curRectangle.getZ());
                Point rightUpPoint = new Point(0, curRectangle.getY() + curRectangle.getWidth(), curRectangle.getZ());
                Point rightDownPoint = new Point(0, curRectangle.getY() + curRectangle.getWidth(), curRectangle.getZ() + curRectangle.getHeight());
                Point leftDownPoint = new Point(0, curRectangle.getY(), curRectangle.getZ() + curRectangle.getHeight());
                Collections.addAll(curSolution, leftDownPoint, rightDownPoint, rightUpPoint, leftUpPoint);
                //对结果进行反旋转
                for (Point point : curSolution) {
    
    
                    double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, -rotateDegree);
                    point.setY(arr[0]);
                    point.setZ(arr[1]);
                }
                //替换结果
                bestArea = curArea;
                bestRectangle = curRectangle;
                bestSolution.clear();
                bestSolution.addAll(curSolution);
                bestRotateDegree = rotateDegree;
                bestRectangle.setBestRotateDegree(bestRotateDegree);
            }
            rotateDegree += this.rotateDegreeDelta;
        }
        if (bestRectangle != null) {
    
    
            //找到解,对解进行存储
            solution = Point.cloneList(bestSolution);
            System.out.println("找到矩形2-------------------------");
            System.out.println("最优旋转角度:" + bestRotateDegree);
            System.out.println("bestSolution:" + bestSolution);
            bestRectangleList.add(bestRectangle);
            ///存储最优解,需要将网格那些也保存下来
            if (this.paintType == 1) {
    
    
                minY = Double.MAX_VALUE;
                maxY = -Double.MAX_VALUE;
                minZ = Double.MAX_VALUE;
                maxZ = -Double.MAX_VALUE;
                // 将零件点旋转一下
                for (Point point : pointList) {
    
    
                    double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, bestRotateDegree);
                    point.setY(arr[0]);
                    point.setZ(arr[1]);
                    minY = Math.min(minY, point.getY());
                    maxY = Math.max(maxY, point.getY());
                    minZ = Math.min(minZ, point.getZ());
                    maxZ = Math.max(maxZ, point.getZ());
                }
                // 将结旋转一下
                for (Point point : solution) {
    
    
                    double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, bestRotateDegree);
                    point.setY(arr[0]);
                    point.setZ(arr[1]);
                }
                //构建网络单元
                cells = this.constructCells(pointList);
                //标记单元格,所有被全包含于多边形的单元格被标记为1
                this.markCellsLabel(pointList, cells);
            }
        }
        getMaxRectangleInSimplePolygonResult.setSolution(solution);
        getMaxRectangleInSimplePolygonResult.setPointList(pointList);
        getMaxRectangleInSimplePolygonResult.setBestRectangleList(bestRectangleList);
        getMaxRectangleInSimplePolygonResult.setYList(yList);
        getMaxRectangleInSimplePolygonResult.setZList(zList);
        getMaxRectangleInSimplePolygonResult.setWidth(maxY - minY);
        getMaxRectangleInSimplePolygonResult.setHeight(maxZ - minZ);
        getMaxRectangleInSimplePolygonResult.setMinY(minY);
        getMaxRectangleInSimplePolygonResult.setMinZ(minZ);
        getMaxRectangleInSimplePolygonResult.setCells(cells);
        long end = System.currentTimeMillis();
        double calculateTime = (end - start) * 1.0 / 1000;
        getMaxRectangleInSimplePolygonResult.setCalculateTime(calculateTime);

        System.out.println("结果输出--------------------------------------------------");
        System.out.println("计算时间:" + calculateTime + "s");
        System.out.println("标记网络单元总标记时间:" + this.setCellsLabelTime * 1.0 / 1000 + "s");
        System.out.println("重置网络单元总标记时间:" + this.resetCellsLabelTime * 1.0 / 1000 + "s");
        System.out.println("给定网络搜索解时间:" + this.searchSolutionTime * 1.0 / 1000 + "s");

        return getMaxRectangleInSimplePolygonResult;
    }

    /**
     * 指定旋转角度,求当前旋转角度的最大矩形
     *
     * @param pointList
     * @param rotateDegree
     * @return
     */
    private LoadProductDto solveWithAppointRotateDegree(List<Point> pointList, int rotateDegree, List<Point> solution) throws Exception {
    
    

         数据处理
        // 将点旋转一下
        for (Point point : pointList) {
    
    
            double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, rotateDegree);
            point.setY(arr[0]);
            point.setZ(arr[1]);
        }

         声明变量
        double bestArea = 0;
        LoadProductDto bestRectangle = null;

        // 构建单元格
        Cell[][] cells = this.constructCells(pointList);
        // 标记单元格,所有被全包含于多边形的单元格被标记为1
        this.markCellsLabel(pointList, cells);
        if (this.isPrintLabelMatrix == true) {
    
    
            System.out.println("输出真实标记矩阵,在多边形内的单元被标记为1");
            for (int i = 0; i < cells.length; i++) {
    
    
                for (int j = 0; j < cells[i].length; j++) {
    
    
                    System.out.printf(String.valueOf(cells[i][j].getLabel()) + "\t");
                }
                System.out.println();
            }
        }
        // 填充单元格的最大面积(搜索到右下角的单元格时,如果知道所能找到的矩形一定不可能大于已经搜索到的矩形时,直接停止该单元格的搜索)
        this.fillCellsMaxArea(cells);
        // 对每个被标记为1的单元格,将其向右、向下遍历,计算形成的最大矩形
        for (int i = 0; i < cells.length; i++) {
    
    
            for (int j = 0; j < cells[0].length; j++) {
    
    
                if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
    
    
                    /*
                     * 只需要设置一次heightArr,后面需要多少列直接拿就行
                     * 只需要设置一次widthArr,下一列的格子的所能找到的最大宽度直接为当前格子所能找到的最大宽度-当前格子宽度
                     */
                    long searchSolutionTimeStart = System.currentTimeMillis();
                    double[] widthArr = new double[cells.length - i];
                    // 存储每一行到达最大宽度时的列数
                    int[] maxColumnNumArr = new int[cells.length - i];
                    double[] heightArr = new double[cells[0].length - j];

                    /// 初始化heightArr
                    for (int x = j; x < heightArr.length + j; x++) {
    
    
                        // --for--循环每一列
                        double heightOfCurColumn = 0;
                        for (int y = i; y < cells.length; y++) {
    
    
                            // --for--循环每一行
                            if (cells[y][x].getLabel() == 1) {
    
    
                                heightOfCurColumn += cells[y][x].getHeight();
                            } else {
    
    
                                break;
                            }
                        }
                        heightArr[x - j] = heightOfCurColumn;
                        if (Math.abs(heightOfCurColumn - 0) < this.precisionError) {
    
    
                            // 相当于这一列开始,后面的列全是默认为0,因为所形成的矩形需要被最矮的一列限制
                            break;
                        }
                    }

                    /// 初始化 widthArr 和 maxColumnNumArr
                    for (int m = i; m < widthArr.length + i; m++) {
    
    
                        // --for--循环每一行
                        double widthOfCurRow = 0;
                        // 找最大宽度所到达的列数
                        int maxColumnNum = j;
                        int num = 0;
                        for (int k = j; k < cells[0].length; k++) {
    
    
                            // --for--循环每一列
                            if (cells[m][k].getLabel() == 1) {
    
    
                                widthOfCurRow += cells[m][k].getWidth();
                                num++;
                            } else {
    
    
                                break;
                            }
                        }
                        maxColumnNum += num > 0 ? num - 1 : 0;
                        // 存储当前行宽度
                        widthArr[m - i] = widthOfCurRow;
                        maxColumnNumArr[m - i] = maxColumnNum;
                    }

                    /// 找到最小的n
                    // 从widthArr和nArr来看
                    int minN = Integer.MAX_VALUE;
                    for (int index = 0; index < maxColumnNumArr.length; index++) {
    
    
                        if (maxColumnNumArr[index] > 0) {
    
    
                            minN = Math.min(minN, maxColumnNumArr[index]);
                        } else {
    
    
                            break;
                        }
                    }
                    if (minN == Integer.MAX_VALUE) {
    
    
                        // 将minN赋值为j-1,这样下面就无需循环寻找结果了
                        minN = j - 1;
                    }
                    // 从heightArr来看
                    int minNOfHeightArr = j;
                    for (int k = 1; k < heightArr.length; k++) {
    
    
                        if (heightArr[k] <= heightArr[k - 1]) {
    
    
                            minNOfHeightArr++;
                        } else {
    
    
                            break;
                        }
                    }
                    minN = Math.min(minN, minNOfHeightArr);

                    /// 寻找最大面积的矩形
                    int columnNum = 0;
                    int recordJ = j;
                    if (j <= minN) {
    
    
                        while (j <= minN) {
    
    
                            if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
    
    
                                for (int m = i; m < widthArr.length + i; m++) {
    
    
                                    // --for--循环每一行
                                    double widthOfCurRow = widthArr[m - i];
                                    // 找最大宽度所到达的列数
                                    int maxColumnNum = maxColumnNumArr[m - i];
                                    if ((m - i - 1) >= 0 && maxColumnNum == maxColumnNumArr[m - i - 1]) {
    
    
                                        // --if-- 最大列数和上一个的最大列数一样,可以直接跳过此轮循环,直接进入下一轮
                                        continue;
                                    }
                                    if (Math.abs(widthOfCurRow - 0) < this.precisionError) {
    
    
                                        break;
                                    } else {
    
    
                                        // 当前行对应的最大宽度
                                        double maxWid = widthOfCurRow;
                                        // 寻找当前宽度对应的最小高度
                                        double maxHei = Integer.MAX_VALUE;
                                        boolean isAssign = false;
                                        for (int k = columnNum; k < maxColumnNum - recordJ + 1; k++) {
    
    
                                            maxHei = Math.min(maxHei, heightArr[k]);
                                            isAssign = true;
                                        }
                                        // 更新最优解
                                        if (maxWid * maxHei > bestArea && isAssign == true) {
    
    
                                            bestArea = maxWid * maxHei;
                                            bestRectangle = new LoadProductDto(maxWid, maxHei, cells[i][j].getLeft_up_point().getY(), cells[i][j].getLeft_up_point().getZ());
                                        }
                                    }
                                }
                            }
                            // 更新widthArr
                            for (int m = i; m < widthArr.length + i; m++) {
    
    
                                if (widthArr[m - i] > 0) {
    
    
                                    // 每一行的width减去当前列的宽度
                                    widthArr[m - i] -= cells[m][j].getWidth();
                                } else {
    
    
                                    break;
                                }
                            }
                            j++;
                            columnNum++;
                            break;
                        }
                        // 上面的j++已经超出minN了,重新回到minN,因为循环的时候,j会++,如果不回到minN,会漏掉一列
                        j = minN;
                    }
                    long searchSolutionTimeEnd = System.currentTimeMillis();
                    this.searchSolutionTime += searchSolutionTimeEnd - searchSolutionTimeStart;
                }
            }
        }

        return bestRectangle;
    }

    /**
     * 构建网络单元
     *
     * @param pointList
     * @return
     */
    private Cell[][] constructCells(List<Point> pointList) {
    
    
        声明变量
        Cell[][] cells;

        开始构建
        List<Double> yList = new ArrayList<>();
        List<Double> zList = new ArrayList<>();
        Map<Integer, Boolean> yMap = new HashMap<>();
        Map<Integer, Boolean> zMap = new HashMap<>();
        for (Point point : pointList) {
    
    
            if (!yMap.containsKey((int) point.getY())) {
    
    
                yList.add(point.getY());
                yMap.put((int) point.getY(), true);
            }
            if (!zMap.containsKey((int) point.getZ())) {
    
    
                zList.add(point.getZ());
                zMap.put((int) point.getZ(), true);
            }
        }
        根据分段继续添加yList和zList
        double minY = Double.MAX_VALUE;
        double maxY = -Double.MAX_VALUE;
        double minZ = Double.MAX_VALUE;
        double maxZ = -Double.MAX_VALUE;
        for (Point point : pointList) {
    
    
            minY = Math.min(minY, point.getY());
            maxY = Math.max(maxY, point.getY());
            minZ = Math.min(minZ, point.getZ());
            maxZ = Math.max(maxZ, point.getZ());
        }
        ///根据分段数来确定y的增量
        double deltaOfY = (maxY - minY) * 1.0 / this.segmentNum;
        double curY = minY + deltaOfY;
        while (curY < maxY) {
    
    
            if (yMap.containsKey(curY)) {
    
    
                curY += deltaOfY;
                continue;
            }
            boolean isAdd = false;
            // 对于每一根y扫描线,都拿来和多边形的每条边相交一下,来找到z扫描线
            for (int i = 0; i < pointList.size(); i++) {
    
    
                int j = (i + 1) % pointList.size();
                //获取边的两个点
                Point point1 = pointList.get(i);
                Point point2 = pointList.get(j);
                double minYOfSide = Math.min(point1.getY(), point2.getY());
                double maxYOfSide = Math.max(point1.getY(), point2.getY());
                if (minYOfSide < curY && curY < maxYOfSide) {
    
    
                    ///对边构建表达式
                    double curZ = this.getZByYAndLineMessage(point1, point2, curY);
                    if (curZ == Double.MAX_VALUE) {
    
    
                        continue;
                    }
                    if (!zMap.containsKey((int) curZ)) {
    
    
                        zList.add(curZ);
                        zMap.put((int) curZ, true);
                    }
                    isAdd = true;
                }
            }
            if (isAdd == true) {
    
    
                yList.add(curY);
                yMap.put((int) curY, true);
            }
            curY += deltaOfY;
        }
        //对yList、zList升序排序
        Collections.sort(yList);
        Collections.sort(zList);
        cells = new Cell[zList.size() - 1][yList.size() - 1];
        for (int j = 0; j < zList.size() - 1; j++) {
    
    
            for (int i = 0; i < yList.size() - 1; i++) {
    
    
                Point left_up_point = new Point(0, yList.get(i), zList.get(j));
                Point right_down_point = new Point(0, yList.get(i + 1), zList.get(j + 1));
                cells[j][i] = new Cell(left_up_point, right_down_point);
            }
        }
        return cells;
    }

    /**
     * 标记网络单元,当一个单元处于多边形内部时,将单元标记为1,否则标记为0
     *
     * @param pointList
     * @param cells
     */
    private void markCellsLabel(List<Point> pointList, Cell[][] cells) throws Exception {
    
    
        long start = System.currentTimeMillis();
        for (int i = 0; i < cells.length; i++) {
    
    
            for (int j = 0; j < cells[i].length; j++) {
    
    
                cells[i][j].setLabel(this.getCellLabel(pointList, cells[i][j]));
            }
        }
        long end = System.currentTimeMillis();
        this.setCellsLabelTime += end - start;
    }

    /**
     * 判断单元格是否被纯包含于多边形内,是:返回1;否:返回0
     * 单元格中有一个点不在多边形内,单元格便是不在单元格内
     * type=0:第一次设置label时使用; type=1:reset label时使用;
     *
     * @param pointList
     * @param cell
     */
    private int getCellLabel(List<Point> pointList, Cell cell) throws Exception {
    
    
        // 存储单元格的四个点
        Point[] pointArr = new Point[4];
        //左上角
        pointArr[0] = new Point(0, cell.getLeft_up_point().getY(), cell.getLeft_up_point().getZ());
        //右上角
        pointArr[1] = new Point(0, cell.getRight_down_point().getY(), cell.getLeft_up_point().getZ());
        //右下角
        pointArr[2] = new Point(0, cell.getRight_down_point().getY(), cell.getRight_down_point().getZ());
        //左下角
        pointArr[3] = new Point(0, cell.getLeft_up_point().getY(), cell.getRight_down_point().getZ());

        for (Point point : pointArr) {
    
    
            //只要有一个点不在多边形内,单元格被标记为不在多边形内
            if (PolygonUtil.judgeWhetherThePointInPolygon(pointList, point) == false) {
    
    
                return 0;
            }
        }

        //就算单元格的所有点都在多边形内部,不代表单元格就在多边形内了
        //再判断一下网络单元的四条边是否和线段有相交,且斜率不能相同,为了避免出现较特殊矩形时,单元的四个点都在多边形内部,但是实际上不在内部的情况出现
        for (int i = 0; i < pointArr.length; i++) {
    
    
            int j = (i + 1) % pointArr.length;
            for (int m = 0; m < pointList.size(); m++) {
    
    
                int n = (m + 1) % pointList.size();
                if (PolygonUtil.judgeWhetherTwoLineSegmentIsIntersect(pointArr[i], pointArr[j], pointList.get(m), pointList.get(n)) == true &&
                        (this.getKOfLine(pointArr[i], pointArr[j]) != this.getKOfLine(pointList.get(m), pointList.get(n)))) {
    
    
                    //--if--当单元格的边和多边形有交叉,且不是平行的那种时,单元不在多边形内部
                    return 0;
                }
            }
        }
//            System.out.println("单元格在多边形内部");
        return 1;
    }

    /**
     * 填充单元格的理想最大面积
     *
     * @param cells
     */
    private void fillCellsMaxArea(Cell[][] cells) {
    
    
         填补每个单元格所能找到的理想最大面积(递推)
        /// 初始化网络单元的最后一行和最后一列
        // 初始化最后一行
        for (int i = 0; i < cells[cells.length - 1].length; i++) {
    
    
            //对每一列的最后一个单元进行处理
            Cell cell = cells[cells.length - 1][i];
            cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
        }
        // 初始化最后一列
        for (int i = 0; i < cells.length; i++) {
    
    
            //对每一行的最后一个单元进行处理
            Cell cell = cells[i][cells[0].length - 1];
            cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
        }
        /// 填充其他单元格的理想最大面积
        for (int i = cells.length - 2; i >= 0; i--) {
    
    
            for (int j = cells[0].length - 2; j >= 0; j--) {
    
    
                Cell cell = cells[i][j];
                //先填充自己的面积
                cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
                //再添加自己下面的单元格maxArea和右侧的单元格的maxArea
                if (i + 1 < cells.length) {
    
    
                    cell.setMaxArea(cell.getMaxArea() + cells[i + 1][j].getMaxArea());
                }
                if (j + 1 < cells[0].length) {
    
    
                    cell.setMaxArea(cell.getMaxArea() + cells[i][j + 1].getMaxArea());
                }
            }
        }
    }


    /**
     * 求一条直线的斜率
     *
     * @param p1
     * @param p2
     * @return
     */
    private double getKOfLine(Point p1, Point p2) {
    
    
        return MathUtil.getKOfLine(p1.getY(), p1.getZ(), p2.getY(), p2.getZ());
    }

    /**
     * 平面上一点x1,y1,绕平面上另一点x2,y2顺时针旋转rotateDegree角度,求旋转后的x1,y1对应的坐标x,y
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param rotateDegree
     * @return arr[0]:x;arr[1]:y
     */
    private double[] rotate(double x1, double y1, double x2, double y2, int rotateDegree) {
    
    
        double[] arr = new double[2];
        //根据角度求弧度
        double radian = (rotateDegree * 1.0 / 180) * Math.PI;
        //旋转
        arr[0] = (x1 - x2) * Math.cos(radian) - (y1 - y2) * Math.sin(radian) + x2;
        arr[1] = (y1 - y2) * Math.cos(radian) + (x1 - x2) * Math.sin(radian) + y2;
        return arr;
    }

    /**
     * 传入一条线段,然后根据已知的y来求线段上的z
     *
     * @param p1
     * @param p2
     * @param y
     * @return
     */
    private double getZByYAndLineMessage(Point p1, Point p2, double y) {
    
    
        double k = this.getKOfLine(p1, p2);
        if (Math.abs(k - Double.MAX_VALUE) < this.precisionError) {
    
    
            return Double.MAX_VALUE;
        } else if (Math.abs(k - 0) < this.precisionError) {
    
    
            return p1.getZ();
        } else {
    
    
            //截距
            double b = p2.getZ() - k * p2.getY();
            return k * y + b;
        }
    }

    public GetMaximumAreaRectangleInASimplePolygonApi() {
    
    
    }
}

For some tools used in the code, please check the article Ray Method - Determining whether a point is inside a polygon (applicable to convex polygons and concave polygons) [Key principle explanation + text pseudo code + java code implementation]

illustrate

The algorithm in this article is a relatively crude solution algorithm. In addition, my coding ability is not strong enough, the coding time is short, and the processing in some places needs to be improved, which may lead to longer calculation time for some data. If readers have any better suggestions, I hope that Feel free to teach me. If you find it helpful, I hope you can give it a like

Guess you like

Origin blog.csdn.net/laodanqiu/article/details/132824305