求多边形的最小包络矩形【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.

目录
相关文章
|
3月前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
107 5
|
8天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
8天前
|
存储 算法 Java
【JAVA】生成accessToken原理
在Java中,生成accessToken用于身份验证和授权,确保合法用户访问受保护资源。流程包括:1. 身份验证(如用户名密码、OAuth 2.0);2. 生成唯一且安全的令牌;3. 设置令牌有效期并存储;4. 客户端传递令牌,服务器验证其有效性。常见场景为OAuth 2.0协议,涉及客户端注册、用户授权、获取授权码和换取accessToken。示例代码展示了使用Apache HttpClient库模拟OAuth 2.0获取accessToken的过程。
|
2月前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
59 3
|
2月前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
84 2
|
3月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
3月前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
3月前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
3月前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
92 2
|
3月前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
89 1