给定一个多边形的点集,如何找出一个矩形,该矩形可以将整个多边形包住,且矩形的面积最小。
整个问题的求解分为两个过程,如果多边形为凹多边形,需要先将凹多边形转化为凸多边形,紧接着开始寻找凸多边形的最小包络矩形。
如果不了解凹多边形和凸边形如何定义,以及凹多边形转化为凸多边形的具体操作,可以参考文章凹多边形处理成凸多边形
尝试将凸多边形的每条边都旋转到水平方向,然后求旋转之后的矩形的水平最小包络矩形,在遍历过程中不断更新最小包络矩形。
/**
* 获取多边形的 最小包络矩形
*/
public class GetMinimumEnvelopeRectangleApi {
/**
* 获取多边形的最小包络矩形
*
* @param pointList 多边形点集
* @return
*/
public static Rectangle getMinimumEnvelopeRectangle(List<Point> pointList) {
将凹多边形转化为凸多边形
pointList = new ConcaveToConvexApi().concaveToConvex(pointList);
寻找最小包络矩形
double minRectangleArea = Double.MAX_VALUE;
double bestLen = 0;
double bestWid = 0;
Point[] bestPointArr = new Point[4];
for (int m = 0; m < pointList.size(); m++) {
int n = (m + 1) % pointList.size();
float mX = pointList.get(m).getX();
float mY = pointList.get(m).getY();
//求当前边和向量(1,0)的角度
double angle = -MathUtil.calculateAngleOfVector(
pointList.get(n).getX() - mX, pointList.get(n).getY() - mY
);
///将除了 点m 之外的每个点绕着 点m 旋转, 然后构造矩形
double minX = mX;
double minY = mY;
double maxX = mX;
double maxY = mY;
for (int i = 0; i < pointList.size(); i++) {
if (i == m) {
continue;
}
Point pointI = pointList.get(i);
//旋转
double[] rotate = MathUtil.rotate(pointI.getX(), pointI.getY(), mX, mY, angle);
minX = Math.min(minX, rotate[0]);
maxX = Math.max(maxX, rotate[0]);
minY = Math.min(minY, rotate[1]);
maxY = Math.max(maxY, rotate[1]);
}
///如果找到更小的矩形,更新最小矩形
if ((maxX - minX) * (maxY - minY) < minRectangleArea) {
minRectangleArea = (maxX - minX) * (maxY - minY);
bestLen = maxX - minX;
bestWid = maxY - minY;
if (bestLen < bestWid) {
double temp = bestLen;
bestLen = bestWid;
bestWid = temp;
}
//存储矩形的左下角
bestPointArr[0] = new Point((float) minX, (float) minY);
//存储矩形的右下角
bestPointArr[1] = new Point((float) maxX, (float) minY);
//存储矩形的右上角
bestPointArr[2] = new Point((float) maxX, (float) maxY);
//存储矩形的左上角
bestPointArr[3] = new Point((float) minX, (float) maxY);
//将矩形的点位旋转回来
for (Point point : bestPointArr) {
double[] rotateArr = MathUtil.rotate(point.getX(), point.getY(), mX, mY, -angle);
point.setX((float) rotateArr[0]);
point.setY((float) rotateArr[1]);
}
}
}
List<Float> pointXList = new ArrayList<>();
List<Float> pointYList = new ArrayList<>();
for (Point point : pointList) {
pointXList.add(point.getX());
pointYList.add(point.getY());
}
pointXList.add(pointList.get(0).getX());
pointYList.add(pointList.get(0).getY());
return new Rectangle((float) bestLen, (float) bestWid, bestPointArr, pointXList, pointYList);
}
}
package com.dam;
/**
* 数学工具
*/
public class MathUtil {
/**
* 计算向量的模
*
* @param x
* @param y
* @return
*/
public static double calculateModulusOfVector(double x, double y) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
}
/**
* 求(x,y)的角度(0,360),从x坐标轴正方向开始计算
*
* @param x2
* @param y2
* @return
*/
public static double calculateAngleOfVector(double x2, double y2) {
double x1 = 1;
double y1 = 0;
double radian = Math.acos((x1 * x2 + y1 * y2) / (MathUtil.calculateModulusOfVector(x1, y1) * MathUtil.calculateModulusOfVector(x2, y2)));
double angle = Math.toDegrees(radian);
return y2 > 0 ? angle : 360 - angle;
}
/**
* 将(x1,y1)绕着(x2,y2)逆时针旋转rotateDegree
*
* @param x1
* @param y1
* @param x2
* @param y2
* @param rotateDegree
* @return
*/
public static double[] rotate(double x1, double y1, double x2, double y2, double 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;
}
}

[1]曹新明,蒋瑞斌.不规则零件最小包络矩形的求解研究[J].科技通报,2007(01):102-105.