【技术碎片】【Java】计算椭圆的外接矩形坐标

news2024/10/7 18:31:59

在这里插入图片描述

目录

  • 前言
  • 原生实现(错误方法)
  • 精确实现(数学解)
  • 参考

前言

遇到一个需要计算一般椭圆(斜椭圆)的外接矩形坐标的问题,在此记录一下

已知椭圆的中心点坐标centerX centerY,椭圆的长轴,短轴majorRadius minorRadius,和旋转角度 angle。

按理说java有原生的计算外接矩形的函数,先看看 java.awt.geom怎么实现的。

原生实现(错误方法)

java.awt.geom提供了 Ellipse2D对象,我们通过Ellipse2D对象的 setFrameFromCenter 方法可以直接创建相应尺寸的椭圆:

           
            // 一般椭圆的入参
            double majorRadius = 108;
            double minorRadius = 207;
            double centerX = 836;
            double centerY = 473;
            double angle = 45.5;
            
            // 创建椭圆 ellipse
            Ellipse2D ellipse = new Ellipse2D.Double();
            ellipse.setFrameFromCenter(centerX, centerY, centerX + majorRadius, centerY + minorRadius);
            

我们再创建AffineTransform对象,将ellipse进行旋转变换,就能得到最终的椭圆,再通过Shape对象的getBounds2D()方法,可以直接得到外接矩形。


            // 旋转椭圆得到 transformedEllipse
            AffineTransform transform = new AffineTransform();
            transform.rotate(Math.toRadians(45.5), centerX, centerY);
            Shape transformedEllipse = transform.createTransformedShape(ellipse);
            Rectangle2D bounds2D = transformedEllipse.getBounds2D();

为了更直观展示,我们通过 Graphics2D 把图像画出来。

完整代码如下:


import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;

public class BoundingBoxUtil2 {


    /**
     * 绘图
     */
    static class DrawFrame extends JFrame {
        public DrawFrame() {
            add(new DrawComponent());
            pack();
        }
    }

    static class DrawComponent extends JComponent {

        // 绘图窗口的尺寸
        private static final int DEFAULT_WIDTH = 2000;
        private static final int DEFAULT_HEIGHT = 1000;

        // 绘图内容
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;

            // 一般椭圆的入参
            double majorRadius = 108;
            double minorRadius = 207;
            double centerX = 836;
            double centerY = 473;
            double angle = 45.5;
            
            // 创建椭圆 ellipse
            Ellipse2D ellipse = new Ellipse2D.Double();
            ellipse.setFrameFromCenter(centerX, centerY, centerX + 108, centerY + 207);
            g2.draw(ellipse);

            // 旋转椭圆得到 transformedEllipse
            AffineTransform transform = new AffineTransform();
            transform.rotate(Math.toRadians(45.5), centerX, centerY);
            Shape transformedEllipse = transform.createTransformedShape(ellipse);
            // 绘制旋转后的椭圆
            g2.draw(transformedEllipse);
            // 绘制旋转后的椭圆的外接矩形
            g2.draw(transformedEllipse.getBounds2D());
        }

        public Dimension getPreferredSize() {
            return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        }
    }


    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new DrawFrame();
            frame.setTitle("DrawTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }

}



运行结果如下:

在这里插入图片描述

可以看到这种方法是不行的。

如果真的这么简单就好了,可以看到getBounds2D()得到的外接矩形并不是精确的,。我们看看源码描述:


    /**
     * Returns a high precision and more accurate bounding box of
     * the {@code Shape} than the {@code getBounds} method.
     * Note that there is no guarantee that the returned
     * {@link Rectangle2D} is the smallest bounding box that encloses
     * the {@code Shape}, only that the {@code Shape} lies
     * entirely within the indicated {@code Rectangle2D}.  The
     * bounding box returned by this method is usually tighter than that
     * returned by the {@code getBounds} method and never fails due
     * to overflow problems since the return value can be an instance of
     * the {@code Rectangle2D} that uses double precision values to
     * store the dimensions.
     *
     * <p>
     * Note that the
     * <a href="{@docRoot}/java.desktop/java/awt/Shape.html#def_insideness">
     * definition of insideness</a> can lead to situations where points
     * on the defining outline of the {@code shape} may not be considered
     * contained in the returned {@code bounds} object, but only in cases
     * where those points are also not considered contained in the original
     * {@code shape}.
     * </p>
     * <p>
     * If a {@code point} is inside the {@code shape} according to the
     * {@link #contains(Point2D p) contains(point)} method, then it must
     * be inside the returned {@code Rectangle2D} bounds object according
     * to the {@link #contains(Point2D p) contains(point)} method of the
     * {@code bounds}. Specifically:
     * </p>
     * <p>
     *  {@code shape.contains(p)} requires {@code bounds.contains(p)}
     * </p>
     * <p>
     * If a {@code point} is not inside the {@code shape}, then it might
     * still be contained in the {@code bounds} object:
     * </p>
     * <p>
     *  {@code bounds.contains(p)} does not imply {@code shape.contains(p)}
     * </p>
     * @return an instance of {@code Rectangle2D} that is a
     *                 high-precision bounding box of the {@code Shape}.
     * @see #getBounds
     * @since 1.2
     */
    public Rectangle2D getBounds2D();
    

大意为:

返回Shape的高精度且比getBounds方法更精确的边界框。请注意,不能保证返回的Rectangle2D是包围该形状的最小边界框,只能保证该形状完全位于指示的Rectangle 2D内。此方法返回的边界框通常比getBounds方法返回的更紧,并且从不因溢出问题而失败,因为返回值可以是使用双精度值来存储尺寸的Rectangle2D的实例。

事实上,如果直接生成不旋转的椭圆,通过getBounds2D()方法是可以找到准确的外接矩形的。

在这里插入图片描述

但是java.awt.geom没有考虑到一般椭圆(斜椭圆)的情况。

精确实现(数学解)

其实椭圆的外接矩形有数学解,我们通过还原椭圆一般式的参数,从而可以直接求外接矩形坐标。

中心点位于原点时,椭圆一般方程为:Ax^2 + Bxy + Cy^2 + F=0

因此可以通过已知短轴,长轴,旋转角,确定一般方程的参数:


    /**
     * 计算一般椭圆(斜椭圆)的参数A,B,C,F
     * 中心点位于原点的椭圆一般方程为:Ax^2+Bxy+Cy^2+F=0
     * @param majorRadius 长轴
     * @param minorRadius 短轴
     * @param angle 旋转角
     * @return
     */
    public static double[] getEllipseParam(double majorRadius, double minorRadius, double angle) {
        double a = majorRadius;
        double b = minorRadius;
        double sinTheta = Math.sin(-angle);
        double cosTheta = Math.cos(-angle);
        double A = Math.pow(a, 2) * Math.pow(sinTheta, 2) + Math.pow(b, 2) * Math.pow(cosTheta, 2);
        double B = 2 * (Math.pow(a, 2) - Math.pow(b, 2)) * sinTheta * cosTheta;
        double C = Math.pow(a, 2) * Math.pow(cosTheta, 2) + Math.pow(b, 2) * Math.pow(sinTheta, 2);
        double F = -1 * Math.pow(a, 2) * Math.pow(b, 2);
        return new double[]{A, B, C, F};
    }

因此可以计算中心点位于原点时,外接矩形的坐标:


    /**
     * 计算中心点位于原点的一般椭圆的外接矩形坐标
     * @param A
     * @param B
     * @param C
     * @param F
     * @return
     */
    public static Point2D[] calculateRectangle(double A, double B, double C, double F) {
        double y = Math.sqrt(4 * A * F / (Math.pow(B, 2) - 4 * A * C));
        double y1 = -1 * Math.abs(y);
        double y2 = Math.abs(y);

        double x = Math.sqrt(4 * C * F / (Math.pow(B, 2) - 4 * C * A));
        double x1 = -1 * Math.abs(x);
        double x2 = Math.abs(x);

        Point2D p1 = new Point2D.Double(x1, y1);
        Point2D p2 = new Point2D.Double(x2, y2);
        return new Point2D[]{p1, p2};
    }

中心点位于原点的椭圆外接矩形能算了,原来的椭圆的外接矩形其实就是按照中心点平移罢了:


    /**
     * 计算一般椭圆的外接矩形实际坐标
     * 根据一般椭圆的实际中心点坐标,短轴,长轴,旋转角参数,计算一般椭圆的外接矩形实际坐标
     * @param majorRadius 长轴
     * @param minorRadius 短轴
     * @param angle 旋转角
     * @param centerX 中心点横坐标
     * @param centerY 中心点纵坐标
     * @return
     */
    public static Point2D[] getCircumscribedRectangle(double majorRadius, double minorRadius, double angle, double centerX, double centerY) {
        double[] param = getEllipseParam(majorRadius, minorRadius, angle);
        Point2D[] points = calculateRectangle(param[0], param[1], param[2], param[3]);
        Point2D p1 = new Point2D.Double(centerX + points[0].getX(), centerY + points[0].getY());
        Point2D p2 = new Point2D.Double(centerX + points[1].getX(), centerY + points[1].getY());
        return new Point2D[] { p1, p2 };
    }


这样就能求得一般椭圆的外接矩形坐标了。

为了方便展示做一下绘图,完整代码如下:



import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;

public class BoundingBoxUtil2 {


    /**
     * 绘图
     */
    static class DrawFrame extends JFrame {
        public DrawFrame() {
            add(new DrawComponent());
            pack();
        }
    }

    static class DrawComponent extends JComponent {

        // 绘图窗口的尺寸
        private static final int DEFAULT_WIDTH = 2000;
        private static final int DEFAULT_HEIGHT = 1000;

        // 绘图内容
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;

            // 一般椭圆的入参
            double majorRadius = 108;
            double minorRadius = 207;
            double centerX = 836;
            double centerY = 473;
            double angle = 45.5;

            Point2D[] rectangle = getCircumscribedRectangle(majorRadius, minorRadius, Math.toRadians(angle), centerX, centerY);
            double x1 = rectangle[0].getX();
            double y1 = rectangle[0].getY();
            double x2 = rectangle[1].getX();
            double y2 = rectangle[1].getY();
            double width = x2 - x1;
            double height = y2 - y1;
            Rectangle2D circumscribedRectangle = new Rectangle2D.Double();
            circumscribedRectangle.setRect(x1, y1, width, height);


            // 创建椭圆 ellipse
            Ellipse2D ellipse = new Ellipse2D.Double();
            ellipse.setFrameFromCenter(centerX, centerY, centerX + majorRadius, centerY + minorRadius);
            g2.draw(ellipse);

            // 旋转椭圆得到 transformedEllipse
            AffineTransform transform = new AffineTransform();
            transform.rotate(Math.toRadians(angle), centerX, centerY);
            Shape transformedEllipse = transform.createTransformedShape(ellipse);
            // 绘制旋转后的椭圆
            g2.draw(transformedEllipse);
            // 绘制旋转后的椭圆的外接矩形
//            g2.draw(transformedEllipse.getBounds2D());

            // 绘制真正的外接矩形
            g2.draw(circumscribedRectangle);
        }

        public Dimension getPreferredSize() {
            return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        }
    }

    /**
     * 计算一般椭圆(斜椭圆)的参数A,B,C,F
     * 中心点位于原点的椭圆一般方程为:Ax^2+Bxy+Cy^2+F=0
     * @param majorRadius 长轴
     * @param minorRadius 短轴
     * @param angle 旋转角
     * @return
     */
    public static double[] getEllipseParam(double majorRadius, double minorRadius, double angle) {
        double a = majorRadius;
        double b = minorRadius;
        double sinTheta = Math.sin(-angle);
        double cosTheta = Math.cos(-angle);
        double A = Math.pow(a, 2) * Math.pow(sinTheta, 2) + Math.pow(b, 2) * Math.pow(cosTheta, 2);
        double B = 2 * (Math.pow(a, 2) - Math.pow(b, 2)) * sinTheta * cosTheta;
        double C = Math.pow(a, 2) * Math.pow(cosTheta, 2) + Math.pow(b, 2) * Math.pow(sinTheta, 2);
        double F = -1 * Math.pow(a, 2) * Math.pow(b, 2);
        return new double[]{A, B, C, F};
    }

    /**
     * 计算中心点位于原点的一般椭圆的外接矩形坐标
     * @param A
     * @param B
     * @param C
     * @param F
     * @return
     */
    public static Point2D[] calculateRectangle(double A, double B, double C, double F) {
        double y = Math.sqrt(4 * A * F / (Math.pow(B, 2) - 4 * A * C));
        double y1 = -1 * Math.abs(y);
        double y2 = Math.abs(y);

        double x = Math.sqrt(4 * C * F / (Math.pow(B, 2) - 4 * C * A));
        double x1 = -1 * Math.abs(x);
        double x2 = Math.abs(x);

        Point2D p1 = new Point2D.Double(x1, y1);
        Point2D p2 = new Point2D.Double(x2, y2);
        return new Point2D[]{p1, p2};
    }

    /**
     * 计算一般椭圆的外接矩形实际坐标
     * 根据一般椭圆的实际中心点坐标,短轴,长轴,旋转角参数,计算一般椭圆的外接矩形实际坐标
     * @param majorRadius 长轴
     * @param minorRadius 短轴
     * @param angle 旋转角
     * @param centerX 中心点横坐标
     * @param centerY 中心点纵坐标
     * @return
     */
    public static Point2D[] getCircumscribedRectangle(double majorRadius, double minorRadius, double angle, double centerX, double centerY) {
        double[] param = getEllipseParam(majorRadius, minorRadius, angle);
        Point2D[] points = calculateRectangle(param[0], param[1], param[2], param[3]);
        Point2D p1 = new Point2D.Double(centerX + points[0].getX(), centerY + points[0].getY());
        Point2D p2 = new Point2D.Double(centerX + points[1].getX(), centerY + points[1].getY());
        return new Point2D[] { p1, p2 };
    }


    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new DrawFrame();
            frame.setTitle("DrawTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }

}


运行一下:

在这里插入图片描述

可以看到,数学解是成功的。

参考

https://zhuanlan.zhihu.com/p/82184417

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/495905.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA - 7系列 FPGA内部结构之CLB -02- CLB功能详解

前言 本文翻译自UG474第二章&#xff0c;主要对7系列FPGAs CLB结构进行详细介绍。这些细节对设计优化和验证很有帮助。 CLB 排列 CLB 在 7 系列 FPGA 中按列排列。 7 系列是基于 ASMBL架构提供的独特柱状方法的第四代产品。ASMBL 架构 Xilinx 创建了高级硅模块块 (ASMBL) 架…

【hello Linux】线程互斥

目录 1. 互斥量mutex 2. 互斥量的接口 2.1 初始化互斥量 2.2 销毁互斥量 2.3 互斥量加锁和解锁 2.4 互斥量实现原理探究 3. 可重入VS线程安全 4. 常见锁概念 5. 多线程抢票系统 Linux&#x1f337; 在介绍线程互斥前&#xff0c;我们先来看几个专业性术语&#xff1a; 【临界资…

边缘计算节点是啥?边缘计算与CDN有什么关系?一文带你了解边缘计算节点BEC

边缘计算节点是基于CDN边缘节点构建&#xff0c;覆盖全国大部分地区&#xff0c;三大运营商全覆盖。将算力下沉到各城市级节点&#xff0c;提供离用户更近的算力资源。 那么可能有些小伙伴会问&#xff0c;CDN也是就近为用户提供服务&#xff0c;边缘计算节点和CDN有什么不同呢…

时序数据利用EEMD_LSTM模型进行预测(Python编程,数据集和代码均在压缩包,解压缩后可以直接运行,数据可以替换为股票数据,交通流量等时序数据)

运行效果(为减少录屏时间&#xff0c;视频中epoch设置为30&#xff0c;改为100效果更佳):利用EEMD_LSTM模型对时序数据进行预测&#xff08;视频中epoch为30&#xff0c;当为100 的时候效果更佳&#xff09;_哔哩哔哩_bilibili 1.数据介绍&#xff1a;以每天为间隔的时序数据 …

达梦:dts工具迁移mysql decimal(65,30)的字段,报精度超出定义

本文旨在分享迁移MySQL decimal字段​​​​​​​时遇到“精度超出定义”问题时&#xff0c;如何理解MySQL和达梦对于decimal 等这一类数值数据类型。 1.了解达梦的数值数据类型定义 ​​​​​​​​​​​​​​NUMERIC 类型 语法&#xff1a;NUMERIC[(精度 [, 标度])]功…

HBase基础

HBase基础 一、初识HBase HBase 是一个面向列式存储的分布式数据库&#xff0c;其设计思想来源于 Google 的 BigTable 论文。HBase 底层存储基于 HDFS 实现&#xff0c;集群的管理基于 ZooKeeper 实现。HBase 良好的分布式架构设计为海量数据的快速存储、随机访问提供了可能&…

实验二 存储器管理

实验二 存储器管理 实验目的&#xff1a; 理解各类置换算法的原理和虚拟存储器管理的方法。 实验内容&#xff1a; 编程实现LRU算法或CLOCK/改进算法等置换算法&#xff08;二选一&#xff09;&#xff0c;模拟实现虚拟存储器的地址变换过程。 实验步骤&#xff1a; 1…

C++之虚函数原理

对象数据和函数的存储方式 注意说的是对象。 C中的对象存储方式是 每个对象占用的存储空间只是该对象的数据部分&#xff08;虚函数指针和虚基类指针也属于数据部分&#xff09;&#xff0c;函数属于公共部分。 虚函数表 虚函数是通过虚函数表实现的。 C实现虚函数的方法是…

open3d io操作

目录 1. read_image, write_image 2. read_point_cloud, write_point_cloud 3. 深度相机IO操作 4. Mesh文件读取 1. read_image, write_image 读取jpg. png. bmp等文件 image_io.py import open3d as o3dif __name__ "__main__":img_data o3d.data.JuneauIma…

Redis持久化---RDBAOF

目录 一、什么是持久化&#xff0c;为什么要持久化&#xff1f; 二、RDB 2.1 配置文件 2.2 自动触发 2.3 手动触发 2.4 RDB优缺点 2.5 如何修复dump.rdb文件 2.6 哪些情况会触发快照 && 如何禁用RDB&#xff1f; 三、AOF 3.1 什么是AOF&#xff1f; 3.2 AO…

(四)Kubernetes - 手动部署(二进制方式安装)

Kubernetes - 手动部署 [ 3 ] 1 部署work node1.1 创建工作目录并拷贝二进制文件1.2 部署kubelet1.2.1 创建配置文件1.2.2 配置文件1.2.3 生成kubelet初次加入集群引导kubeconfig文件1.2.4 systemd管理kubelet1.2.5 启动并设置开机启动1.2.6 允许kubelet证书申请并加入集群 1.3…

猫狗训练集训练报错:Failed to find data adapter that can handle input

这里写自定义目录标题 Jupyter Notebook6.5.4 tensorflow 2.12.0 pillow 9.5.0 numpy 1.23.5 keras 2.12.0 报错详细内容&#xff1a; ValueError: Failed to find data adapter that can handle input: (<class ‘tuple’> containing values of types {“<class ‘k…

Midjourney关键词分享!附输出AI绘画参考图

Midjourney 关键词是指用于 Midjourney 这个 AI 绘画工具的文本提示&#xff0c;可以影响生成图像的风格、内容、细节等。Midjourney 关键词有一些基本的语法规则和套用公式&#xff0c;也有一些常用的风格词汇和描述词汇&#xff0c;这里我以10张不同风格和类型的美女图为例&a…

windows 下Node.js 版本管理工具

目录 1、概述&#xff1a; 2、下载安装 3、nvm命令 4、如何安装不在可用列表里面的版本 1、概述&#xff1a; 不同项目使用的nodejs版本和依赖等不同&#xff0c;需要进行nodejs的版本切换&#xff0c;使用nvm可以方便的切换当前的nodejs版本 windows可以使用 nvm-window…

AP360X 可充电多功能LED手电筒与移动照明控制ic和应用方案

产品展示 线路图如下&#xff1a; ​ AP360X芯片应用原理图和扩容1.8A应用&#xff1a; ​​ 1&#xff0c;产品介绍 AP360X 系列产品是一款多种模式可选 的单芯片 LED 手电筒控制芯片&#xff0c;集成了锂电 池充电管理模块、手电筒功能控制模块和保 护模块&#xff0c;关机…

剑指 Offer 34. 二叉树中和为某一值的路径 / LeetCode 113. 路径总和 II(深度优先搜索)

题目&#xff1a; 链接&#xff1a;剑指 Offer 34. 二叉树中和为某一值的路径&#xff1b;LeetCode 113. 路径总和 II 难度&#xff1a;中等 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 …

身为程序员,你有哪些提高写代码效率的黑科技?

目录 1、Google/Stackoverflow——搜索解决方案的能力 2、低代码平台——提供可复用的轮子 3、人工智能——帮你写代码 4、学会话术——消除烦恼 5、 按时上下班&#xff0c;一周工作 5 天&#xff0c;养足精神以更高效地写代码。 首先&#xff0c;每个程序员都是会利用工…

GPU理解

什么是GPU GPU(Graphics Processing Unit)代表图形处理单元。该术语通常与图形卡或视频卡等术语互换使用。从技术上讲&#xff0c;GPU 是第三方显卡或主板上的主要图形处理芯片。 GPU 与 CPU不同。CPU 是中央处理器&#xff0c;它是计算机的主要大脑。GPU 专用于执行在计算机…

操作系统内存管理笔记

计算机的硬件设备 计算机的硬件设备中&#xff0c;有三个部件最为关键&#xff0c;它们分别是中央处理器CPU、内存和I/O控制芯片。 系统软件 系统软件可以分成两块&#xff0c;一块是平台性的&#xff0c;比如操作系统内核、驱动程序、运行库和数以千计的系统工具&#xff1…

文献阅读(51)—— Transformer 用于中国空气质量检测

文献阅读&#xff08;51&#xff09;—— Transformer 用于中国空气质量检测 文章目录 文献阅读&#xff08;51&#xff09;—— Transformer 用于中国空气质量检测先验知识/知识拓展文章结构背景文章方法1. Dartboard Spatial MSA(DS-MSA)2. CT-MSA3. 自上而下的随机阶段 文章…