求多边形的最小包络矩形【java实现+原理讲解】

简介: 求多边形的最小包络矩形【java实现+原理讲解】

问题

给定一个多边形的点集,如何找出一个矩形,该矩形可以将整个多边形包住,且矩形的面积最小。


方法

整个问题的求解分为两个过程,如果多边形为凹多边形,需要先将凹多边形转化为凸多边形,紧接着开始寻找凸多边形的最小包络矩形。


将凹多边形转化为凸多边形

如果不了解凹多边形和凸边形如何定义,以及凹多边形转化为凸多边形的具体操作,可以参考文章凹多边形处理成凸多边形


寻找最小包络矩形

尝试将凸多边形的每条边都旋转到水平方向,然后求旋转之后的矩形的水平最小包络矩形,在遍历过程中不断更新最小包络矩形。

/**
 * 获取多边形的 最小包络矩形
 */
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.

目录
相关文章
|
23天前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
43 5
|
1月前
|
存储 算法 Java
Java HashSet:底层工作原理与实现机制
本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。
|
12天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
13天前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
15天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
21天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
40 2
|
24天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
21天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
33 1
|
27天前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
|
1月前
|
开发框架 Java 程序员
揭开Java反射的神秘面纱:从原理到实战应用!
本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
47 1
下一篇
无影云桌面