单元格法近似求解多边形最大内接矩形问题【思路讲解+java实现】

🎯 这篇文章介绍了一种从给定的多边形点集中找出面积最大的内接矩形的方法,旨在通过优化原材料利用率来降低成本。文中详细描述了将多边形离散化为网格、确定单元格是否位于多边形内部、寻找最大面积矩形的算法,并使用Java实现了这些步骤。此外,为了进一步提高结果的准确性,还采用了多角度旋转策略。该方法适用于需要从不规则形状材料中切割出最大可用矩形的场合,具有一定的实用价值和经济效益。

问题描述

给定一个多边形的点集,希望找出多边形内部面积最大的矩形。该问题可能出现在,从一个多边形废料上面切割出一个最大的矩形,该矩形可以重复利用,解决该问题可以节约原材料,降低企业运作成本

问题解决方案

本文主要使用的方法是:将多边形离散化为多个单元格,然后判断哪些单元格处于多边形内部,最后通过遍历内部的单元格来找到面积最大的矩形

多边形网格化

已知每个多边形点集中的每个点的坐标为(y,z),通过遍历所有点,可以找出minY,maxY,minZ,maxZ,本文使用一个参数segmentNum来决定y轴方向上对多边形多离散的段数,dis=(maxY-minY)/segmentNum为步进长度,每间隔一个步进长度生成一条垂直于于y轴的扫描线,该扫描线会与多边形相交,本文将相交点的z坐标值生成为一条垂直于z轴的扫描线。最终待垂直于y轴或z轴的扫描线都生成完毕之后,会得到如下图所示的网格


segmentNum=5对应的网格


segmentNum=100对应的网格

需要注意的是,为了加快计算速度,可以使用一定的策略来筛选掉部分扫描线,如果两条扫描线之间间隔的距离非常相近,只需要使用其中一条即可,本文使用一个非常简单的策略,例如y=1.2、y=1.3,使用一个map来记录(int)(1.2)=1,因为int(1.3)也等于1,该扫描线会被舍弃

【java代码实现】

java 复制代码
/**
 * 构建网络单元
 *
 * @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;
}

区分每个单元格是在多边形内部还是外部

本文使用的区分方式是,分别判断一个单元格的四个点是否都在多边形内部,判断一个点是否在多边形的内部可以参考文章射线法------判断一个点是否在多边形内部(适用于凸多边形和凹多边形)【关键原理解释+文字伪代码+java代码实现】,只要有一个点不在多边形内部,则该单元格被标记为0,代表其不在多边形内部。如果单元格的四个点都在多边形内部,也不足以说明单元格就真的在多边形内部,下图就是一个例子 因此还需要判断单元格的边和多边形的边是否有相交,如果有相交,则单元格不完全在多边形内部。等所有的单元格都被判断是否完整地处于多边形内部并被标记为1之后,得到下图 【java实现】

java 复制代码
/**
 * 标记网络单元,当一个单元处于多边形内部时,将单元标记为 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;
}

根据已标记单元格寻找最大内接矩形

本文寻找最大内接矩形的方式如下:遍历每个单元格,尝试将该单元格开始,然后向右、向下寻找单元格进行组合来找到该单元格最大的矩形。在遍历的途中,不断更新面积最大的矩形,遍历结束之后即可求得结果

当遍历到一个单元格时,从单元格开始,向右遍历每一列,找出每一列的对应的连续高度填充到heightArr中,如下图所示。同理,遍历每一行,找出每一行的连续宽度,填充到widthArr中,同时将每一行最大连续宽度的单元格所在的列索引填充到maxColumnNumArr 具体操作如下面的代码所示

java 复制代码
/*
* 只需要设置一次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;
}

当遍历到一个单元格时,还需要做如下事情来寻找当前单元格对应的最大矩形,因为文字实在太难描述,我直接贴代码了o(╥﹏╥)o

java 复制代码
/// 找到最小的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;
}

代码中注释为【寻找最大面积的矩形】的代码片段主要在做如下的事情,可以结合下面的一系列图片来理解

【从第一列开始寻找矩形】

【从第二列开始寻找矩形】

---- 依次类推 ----

【上面的过程结束之后,下一个遍历的单元格如下图,原因是y已经变成了minN+1

你们可能会困惑,为啥需要这段代码来限制minN

java 复制代码
// 从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);

假如不限制的话,遍历情况如图所示

再直接点说,会找不到如下面类型的结果

剪枝优化

当从网格的左上角遍历到网格的右下角时,能找到的矩形实际上已经非常小了,如果能提前预知一个单元格所能找到的最大矩形面积已经小于当前所找到的最大矩形面积的话,则该单元格无需进行遍历,直接跳过即可

本文使用递推的方法来设置一个单元格所能找到的理想最优矩形面积,代码如下:

java 复制代码
/**
* 填充单元格的理想最大面积
*
* @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());
           }
       }
   }
}

当满足单元格的标记为1,且理想最大矩形面积超过当前已经找到的最大矩形面积,才会去根据该单元格去执行上面的【根据已标记单元格寻找最大内接矩形】步骤

java 复制代码
// 对每个被标记为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) {
            // 根据已标记单元格寻找最大内接矩形
        }
    }
}

多角度旋转

上面的方法结束之后,只能求得一个角度下的最大内接矩形,会导致下图的情况发生

本文通过每旋转5°,求解一个最大内接矩形,最终选择最大的那个矩形作为最终结果,当然,如果想要找到更好的结果,可以不断缩小旋转角度,当然,计算时间也会上涨很多

案例测试

代码实现

相关实体类

【LoadProduct】

java 复制代码
package com.ruoyi.algorithm.common.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class LoadProduct implements Serializable {

    private static final long serialVersionUID = 1L;
    private String id;
    private double length;
    /**
     * 长方体使用宽高;圆形宽为内径、高为外径
     */
    private double width;
    private double height;
    private double area;
    /**
     * 产品数量
     */
    private int num;
    /**
     * 产品类型 0:圆形;1:矩形
     */
    private int type;

    public LoadProduct(double height) {
        this.height = height;
    }

    public LoadProduct(double width, double height) {
        this.width = width;
        this.height = height;
        this.area = width * height;
    }

    public LoadProduct(double width, double height, int num) {
        this.width = width;
        this.height = height;
        this.area = width * height;
        this.num = num;
    }

    public LoadProduct(double height, int num) {
        this.height = height;
        this.num = num;
    }
}

【Cell】

java 复制代码
package com.ruoyi.algorithm.common.entity;

import lombok.Data;

@Data
public class Cell {
    /**
     * 左上角
     */
    private Point left_up_point;
    /**
     * 右下角
     */
    private Point right_down_point;
    /**
     * 标记 0:在多边形边上或外部;1:在多边形内部
     */
    private int label;
    /**
     * 当前单元的宽
     */
    private double width;
    /**
     * 当前单元的高
     */
    private double height;
    /**
     * 当前单元的面积
     */
    private double area;
    /**
     * 当前单元的的理想最大面积
     */
    private double maxArea;

    public Cell(Point left_up_point, Point right_down_point) {
        this.left_up_point = left_up_point;
        this.right_down_point = right_down_point;
        this.width = this.right_down_point.getY() - this.left_up_point.getY();
        this.height = this.right_down_point.getZ() - this.left_up_point.getZ();
        this.area = this.width * this.height;
    }
}

【GetMaxRectangleInSimplePolygonResult】

java 复制代码
package com.ruoyi.algorithm.common.entity;

import lombok.Data;

import java.util.List;

@Data
public class GetMaxRectangleInSimplePolygonResult {

    private List<Point> pointList;
    private List<LoadProductDto> bestRectangleList;
    private List<Point> solution;
    private List<Double> yList;
    private List<Double> zList;
    private Cell[][] cells;
    private double width;
    private double height;
    private double minY;
    private double minZ;
    private double calculateTime;
    private int bestRotateDegree;

}

【LoadProductDto】

java 复制代码
package com.ruoyi.algorithm.common.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class LoadProductDto extends LoadProduct implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 存储结果所在坐标
     */
    private double x;
    private double y;
    private double z;
    //存储最优旋转角度
    private double bestRotateDegree;

    /**
     * 存储pack_most_single_circle_to_rectangle的结果
     *
     * @param height
     * @param x
     * @param y
     * @param z
     */
    public LoadProductDto(int height, double x, double y, double z) {
        super(height);
        this.x = x;
        this.y = y;
        this.z = z;
    }

    /**
     * 存储get_maximumArea_rectangles_in_a_simple_polygon的最大矩形
     *
     * @param width
     * @param height
     * @param y
     * @param z
     */
    public LoadProductDto(double width, double height, double y, double z) {
        super(width, height);
        this.y = y;
        this.z = z;
    }
}

【Point】

java 复制代码
package com.ruoyi.algorithm.common.entity;

import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Data
public class Point implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;
    private String id;
    /**
     * 这个属性没啥用,我主要是用y、z,懒得修改了
     */
    private double x;
    private double y;
    private double z;

    public Point() {
    }

    public Point(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public Point clone() throws CloneNotSupportedException {
        return (Point) super.clone();
    }

    public static ArrayList<Point> cloneList(List<Point> pointList) throws CloneNotSupportedException {
        ArrayList<Point> clone = new ArrayList<>();
        for (Point point : pointList) {
            clone.add(point.clone());
        }
        return clone;
    }
}

工具类

该工具类的说明麻烦查看文章射线法------判断一个点是否在多边形内部(适用于凸多边形和凹多边形)【关键原理解释+文字伪代码+java代码实现】

java 复制代码
package com.ruoyi.algorithm.get_maximumArea_rectangles_in_a_simple_polygon.version.v1;

import com.ruoyi.algorithm.common.entity.Point;

import java.util.List;

/**
 * @Author dam
 * @create 2023/9/11 17:22
 */
public class PolygonUtil {

    /**
     * 吉林大学的ACM方法:判断点是否处于多边形内部(点处于多边形的边上同样被视为处于多边形内部)
     *
     * @param pointList
     * @param pointA
     * @return
     */
    public static boolean judgeWhetherThePointInPolygon(List<Point> pointList, Point pointA) throws Exception {

        ///首先给点集构造一个包络矩形,判断点是否在包络矩形外部
        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());
        }
        if (pointA.getY() < minY || pointA.getY() > maxY || pointA.getZ() < minZ || pointA.getZ() > maxZ) {
            return false;
        }

        ///判断点是否在多边形的内部,不包含在多边形的某一条边上
        //构建线1:点A向右延伸的水平射线
        Point line1_start = pointA;
        Point line1_end = pointA.clone();
        line1_end.setY(Double.MAX_VALUE);
        int num = getIntersectNumOfLineSegmentWithPolygon(pointList, line1_start, line1_end);

        boolean isContain = num % 2 != 0;

        ///如果上面识别出来说点不在多边形内部,尝试看看点在不在某一条边上,如果在,则点在多边形内
        if (isContain == false) {
            for (int i = 0; i < pointList.size(); i++) {
                int j = (i + 1) % pointList.size();
                if (PolygonUtil.judgeWhetherThePointInTheLine(pointList.get(i), pointList.get(j), pointA) == true ||
                        PointUtil.judgeWhetherTwoPointsIsSame(pointList.get(i), pointA) ||
                        PointUtil.judgeWhetherTwoPointsIsSame(pointList.get(j), pointA)) {
                    isContain = true;
                    break;
                }
            }
        }
        return isContain;
    }

    /**
     * 判断点 p 是否在线段 line 上
     *
     * @param line_start
     * @param line_end
     * @param p
     * @return
     */
    private static boolean judgeWhetherThePointInTheLine(Point line_start, Point line_end, Point p) {
        double xmulti = xmulti(line_end, p, line_start);
        return ((Math.abs(xmulti - 0) < 0.1 || PolygonUtil.calculateDistanceOfPointAndLine(line_start, line_end, p) < 0.000001) &&
                (((p.getY() - line_start.getY()) * (p.getY() - line_end.getY()) < 0) ||
                        ((p.getZ() - line_start.getZ()) * (p.getZ() - line_end.getZ()) < 0)));
    }

    /**
     * 计算点到直线的距离
     *
     * @param line_start
     * @param line_end
     * @param p
     * @return
     */
    private static double calculateDistanceOfPointAndLine(Point line_start, Point line_end, Point p) {
        return MathUtil.calculateDistanceOfPointAndLine(p.getY(), p.getZ(), line_start.getY(), line_start.getZ(), line_end.getY(), line_end.getZ());
    }

    /**
     * 计算线段和多边形的交点
     *
     * @param pointList
     * @param line1_start
     * @param line1_end
     * @return
     * @throws Exception
     */
    private static int getIntersectNumOfLineSegmentWithPolygon(List<Point> pointList, Point line1_start, Point line1_end) throws Exception {
        //统计射线所穿过多边形中点的数量
        int num = 0;
        //遍历多边形的每条线
        for (int i = 0; i < pointList.size(); i++) {
            //多边形线1(line2):pointI-pointIPlus1; 多边形线2(line3):pointIPlus1-pointIPlus2; 多边形线3(line4):pointIPlus2-pointIPlus3
            Point pointI = pointList.get(i);
            Point pointIPlus1 = pointList.get((i + 1) % pointList.size());
            Point pointIPlus2 = pointList.get((i + 2) % pointList.size());
            Point pointIPlus3 = pointList.get((i + 3) % pointList.size());

            //射线line1和多边形上面的线line2是否相交
            boolean whetherLine1AndLine2IsIntersect = judgeWhetherTwoLineSegmentIsIntersect(line1_start, line1_end, pointI, pointIPlus1);
            //点pointIPlus1是否在射线上
            boolean whetherPointIPlus1InRadial = judgeWhetherThePointInTheLine(line1_start, line1_end, pointIPlus1);
            //点pointIPlus2是否在射线上
            boolean whetherPointIPlus2InRadial = judgeWhetherThePointInTheLine(line1_start, line1_end, pointIPlus2);
            if (whetherLine1AndLine2IsIntersect == true) {
                num++;
                int temp = 0;
            } else if (whetherPointIPlus1InRadial &&
                    (!whetherPointIPlus2InRadial && (xmulti(pointI, pointIPlus1, line1_start) * xmulti(pointIPlus1, pointIPlus2, line1_start) > 0) ||
                            (whetherPointIPlus2InRadial && (xmulti(pointI, pointIPlus2, line1_start) * xmulti(pointIPlus2, pointIPlus3, line1_start) > 0)))) {
                num++;
                int temp = 0;
            }
        }
        return num;
    }

    /**
     * 判断两线段是否相交,当两线段相交且交点不是其中一线段的端点时返回true
     *
     * @param line1_start
     * @param line1_end
     * @param line2_start
     * @param line2_end
     * @return
     */
    public static boolean judgeWhetherTwoLineSegmentIsIntersect(Point line1_start, Point line1_end, Point line2_start, Point line2_end) throws Exception {
        double line1_minY = Math.min(line1_start.getY(), line1_end.getY());
        double line1_maxY = Math.max(line1_start.getY(), line1_end.getY());
        double line1_minZ = Math.min(line1_start.getZ(), line1_end.getZ());
        double line1_maxZ = Math.max(line1_start.getZ(), line1_end.getZ());
        double line2_minY = Math.min(line2_start.getY(), line2_end.getY());
        double line2_maxY = Math.max(line2_start.getY(), line2_end.getY());
        double line2_minZ = Math.min(line2_start.getZ(), line2_end.getZ());
        double line2_maxZ = Math.max(line2_start.getZ(), line2_end.getZ());
        if ((line1_minY > line2_maxY || line2_minY > line1_maxY || line1_minZ > line2_maxZ || line2_minZ > line1_maxZ)) {
            return false;
        } else {
            boolean isTwoLineIntersect = lsinterls(line1_start, line1_end, line2_start, line2_end);
            if (isTwoLineIntersect == false) {
                return false;
            }
            //求交点,用来判断该交点是否为端点
            Point intersectPoint = PolygonUtil.getIntersectPointOfTwoStraightLine(line1_start, line1_end, line2_start, line2_end, -1);
            return ((isTwoLineIntersect) &&
                    (!PointUtil.judgeWhetherTwoPointsIsSame(intersectPoint, line1_start)) &&
                    (!PointUtil.judgeWhetherTwoPointsIsSame(intersectPoint, line1_end)) &&
                    (!PointUtil.judgeWhetherTwoPointsIsSame(intersectPoint, line2_start)) &&
                    (!PointUtil.judgeWhetherTwoPointsIsSame(intersectPoint, line2_end)));
        }
    }

    /**
     * 求两条直线的交点
     *
     * @param line1_start
     * @param line1_end
     * @param line2_start
     * @param line2_end
     * @param polygonIndex
     * @return
     * @throws Exception
     */
    public static Point getIntersectPointOfTwoStraightLine(Point line1_start, Point line1_end, Point line2_start, Point line2_end, int polygonIndex) throws Exception {

        //获取两线段的k和b
        double k1 = PolygonUtil.getKOfLine(line1_start, line1_end);
        double b1 = PolygonUtil.getBOfLine(line1_start, k1);
        double k2 = PolygonUtil.getKOfLine(line2_start, line2_end);
        double b2 = PolygonUtil.getBOfLine(line2_start, k2);

        if (k1 == 0 && k2 == 0) {
            //斜率相同也有可能相交
            if (PointUtil.judgeWhetherTwoPointsIsSame(line1_start, line2_start) || PointUtil.judgeWhetherTwoPointsIsSame(line1_start, line2_end)) {
                return line1_start;
            } else if (PointUtil.judgeWhetherTwoPointsIsSame(line1_end, line2_start) || PointUtil.judgeWhetherTwoPointsIsSame(line1_end, line2_end)) {
                return line1_end;
            } else {
                return null;
            }
        }
        //获取两线段的交点
        double y = 0;
        double z = 0;
        double offSet = 0.000000001;
        if ((Math.abs(k1 - 0) <= offSet) || (Math.abs(k2 - 0) <= offSet) || (Math.abs(k1 - Double.MAX_VALUE) <= offSet) || (Math.abs(k2 - Double.MAX_VALUE) <= offSet)) {
            if (Math.abs(k1 - 0) <= offSet) {
                //line1为水平线时,line2必不可能为水平线,因为已经证明两线相交,平行线必不相交
                z = line1_start.getZ();
                y = (z - b2) / k2;
                if (Math.abs(k2 - Double.MAX_VALUE) < offSet) {
                    y = line2_start.getY();
                } else {
                    y = (z - b2) / k2;
                }
            } else if (Math.abs(k2 - 0) <= offSet) {
                z = line2_start.getZ();
                y = (z - b1) / k1;
                if (Math.abs(k1 - Double.MAX_VALUE) < offSet) {
                    y = line1_start.getY();
                } else {
                    y = (z - b1) / k1;
                }
            } else if (Math.abs(k1 - Double.MAX_VALUE) <= offSet) {
                y = line1_start.getY();
                z = k2 * y + b2;
            } else if (Math.abs(k2 - Double.MAX_VALUE) <= offSet) {
                y = line2_start.getY();
                z = k1 * y + b1;
            } else {
                throw new Exception("判断情况考虑不全面,请仔细考虑");
            }
        } else {
            y = (b2 - b1) / (k1 - k2);
            z = (k2 * b1 - k1 * b2) / (k2 - k1);
        }

        if (Math.abs(z - 290.45) < 1) {
            int temp = 0;
        }
        if (Double.isInfinite(y)) {
            throw new Exception("y为无穷");
        }
        Point point = new Point(0, y, z);
        return point;
    }

    /**
     * 求一条直线的截距
     *
     * @param p 线的一个点
     * @param k 线的斜率
     * @return
     */
    public static double getBOfLine(Point p, double k) {
        return MathUtil.getBOfLine(p.getY(), p.getZ(), k);
    }

    /**
     * 确定两条线段是否相交,端点相交也是相交
     *
     * @param line1_start
     * @param line1_end
     * @param line2_start
     * @param line2_end
     * @return
     */
    public static boolean lsinterls(Point line1_start, Point line1_end, Point line2_start, Point line2_end) {

        double k1 = PolygonUtil.getKOfLine(line1_start, line1_end);
        double k2 = PolygonUtil.getKOfLine(line2_start, line2_end);
        if (Math.abs(k1 - k2) < 0.000001) {
            return false;
        }

        return ((Math.max(line1_start.getY(), line1_end.getY()) >= Math.min(line2_start.getY(), line2_end.getY())) &&
                (Math.max(line2_start.getY(), line2_end.getY()) >= Math.min(line1_start.getY(), line1_end.getY())) &&
                (Math.max(line1_start.getZ(), line1_end.getZ()) >= Math.min(line2_start.getZ(), line2_end.getZ())) &&
                (Math.max(line2_start.getZ(), line2_end.getZ()) >= Math.min(line1_start.getZ(), line1_end.getZ())) &&
                (xmulti(line2_start, line1_end, line1_start) * xmulti(line1_end, line2_end, line1_start) >= 0) &&
                (xmulti(line1_start, line2_end, line2_start) * xmulti(line2_end, line1_end, line2_start) >= 0));
    }

    /**
     * (P1-P0)*(P2-P0)的叉积
     * 若结果为正,则<P0,P1>在<P0,P2>的顺时针方向;
     * 若结果为零,则<P0,P1><P0,P2>共线;
     * 若结果为负,则<P0,P1>在<P0,P2>的在逆时针方向;
     * <p>
     * 可以根据这个函数确定两条线段在交点处的转向,
     * 比如确定p0p1和p1p2在p1处是左转还是右转,只要求
     * (p2-p0)*(p1-p0),若<0则左转,>0则右转,=0则共线
     *
     * @param p1
     * @param p2
     * @param p0
     * @return <0:p2在p1处左拐 =0:直走 >0:p2在p1处右拐
     */
    private static double xmulti(Point p1, Point p2, Point p0) {
        return (
                (p1.getY() - p0.getY()) * (p2.getZ() - p0.getZ())
                        -
                        (p2.getY() - p0.getY()) * (p1.getZ() - p0.getZ())
        );
    }

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

}

主要算法类

java 复制代码
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() {
    }
}

说明

本文的算法算是比较粗暴的求解算法,加上本人代码能力不够强,编码时间短,部分地方的处理还有待提高,可能导致部分数据计算时间较长,假如读者们有什么更好的建议也希望可以不吝赐教。如果你们觉得有帮助,也希望可以点个赞

相关推荐
做运维的阿瑞2 小时前
Python零基础入门:30分钟掌握核心语法与实战应用
开发语言·后端·python·算法·系统架构
猿究院-陆昱泽3 小时前
Redis 五大核心数据结构知识点梳理
redis·后端·中间件
yuriy.wang3 小时前
Spring IOC源码篇五 核心方法obtainFreshBeanFactory.doLoadBeanDefinitions
java·后端·spring
咖啡教室5 小时前
程序员应该掌握的网络命令telnet、ping和curl
运维·后端
你的人类朋友6 小时前
Let‘s Encrypt 免费获取 SSL、TLS 证书的原理
后端
老葱头蒸鸡6 小时前
(14)ASP.NET Core2.2 中的日志记录
后端·asp.net
李昊哲小课6 小时前
Spring Boot 基础教程
java·大数据·spring boot·后端
码事漫谈7 小时前
C++内存越界的幽灵:为什么代码运行正常,free时却崩溃了?
后端
Swift社区7 小时前
Spring Boot 3.x + Security + OpenFeign:如何避免内部服务调用被重复拦截?
java·spring boot·后端
90后的晨仔7 小时前
Mac 上配置多个 Gitee 账号的完整教程
前端·后端