Java求geometry的面积最小外接矩形

Posted 程序媛一枚~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java求geometry的面积最小外接矩形相关的知识,希望对你有一定的参考价值。

Java求geometry的面积最小外接矩形

geom.getEnvelope() 得到外接矩形,不一定是面积最小;可以对多边形的每一条边求外接矩形,然后比较得到最小外接矩形

  • geom.getEnvelope()); // 外接矩形4个点
  • geom.getEnvelopeInternal()); // 外接矩形对角线俩点
  • geom.getBoundary()); // 首尾俩点
  • (new ConvexHull(geom)).getConvexHull(); // 获取凸包(凸包可以理解为线,多线闭合后的多边形)

这篇博客将分为3步进行求解;
1. 获取Envelope的外接矩形,获取面积;
2. 获取凸包,旋转获取每一条边对应的外接矩形,得到凸包面积最小外接矩形;
3. 将1获取的矩形与2获取的矩形面积进行比较,得到最终的面积最小外接矩形;

以上可得出,Envelope获取的外接矩形和凸包旋转获取的外接矩形均有可能为面积最小矩形。下边用俩个例子证明这一点;

1. 效果图

原始geom 黄色线 VS 凸包淡紫色填充:

原始geom VS Envelope外接矩形 VS 凸包旋转后最小面积矩形 VS EnvelopeInternal对角线 VS Boundary首尾点效果图如下:
呈现了俩组的结果,外接矩形面以淡紫色填充为:geom.getEnvelope()); // 外接矩形4个点以polygon面渲染;
矩形的对角线黄色线为:geom.getEnvelopeInternal()); // 外接矩形对角线俩点以线渲染;
原始geom为多点的黄色线;
geom.getBoundary()); // 首尾俩点以蓝色点渲染
可视化可参考之前的博客: Mapbox HTML可视化点,线,多线,面带底图

原始几何 VS Envelope外接矩形 VS 凸包旋转后最小面积矩形 VS EnvelopeInternal对角线 VS Boundary首尾点效果图如下:
在上图的基础上增加了凸包旋转后最小面积矩形,以淡紫色面填充;

1.1 凸包旋转后的面积最小外接矩形为面积最小矩形可视化如图:

如下图很明显,凸包旋转后的矩形面积比Envelope获取到的矩形面积要小。凸包旋转后的矩形是面积最小矩形。
代码验证下确实如此:

1.2 Envelope矩形为面积最小矩形

凸包旋转后的外接矩形不是面积最小矩形可视化如图:
如下图所示,直观来看,凸包旋转后的面积最小矩形 与 Envelope获取到的矩形面积看起来基本差不多,事实是Envelope获取到的矩形是面积最小矩形。

代码验证如下:Envelope获取到的矩形是面积最小矩形。

凸包旋转后面积最小 VS Envelope外接矩形 VS 原始几何 VS EnvelopeInternal对角线 VS Boundary首尾点效果图如下:
外接矩形面以淡紫色填充为:geom.getEnvelope()); // 外接矩形4个点以polygon面渲染;
旋转后的面积最小矩形以淡紫色填充;
矩形的对角线黄色线为:geom.getEnvelopeInternal()); // 外接矩形对角线俩点以线渲染;
geom.getBoundary()); // 首尾俩点以蓝色点渲染;

2. java源码

<dependency>
    <groupId>com.vividsolutions</groupId>
    <artifactId>jts</artifactId>
    <version>1.13</version>
</dependency>
import com.vividsolutions.jts.algorithm.ConvexHull;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

/*************************************
 *Class Name: TestGeomMinRectangle
 *Description: <测试获取Geometry面积最小外接矩形>
 *@author: Seminar
 *@create: 2022/9/25
 *@since 1.0.0
 *************************************/
@Slf4j
public class TestGeomMinRectangle 

    private static GeometryFactory gf = new GeometryFactory();

    // 多边形几何
    String polygon;
    // 线几何
    String lineString;
    // 多边几何
    String multiPoint;

    /**
     * 初始化wkt
     */
    public void initWkt() 
        this.polygon = "POLYGON ((116.42486572265626 39.99500778093748, 116.51962280273439 39.886557705928475, 116.44546508789064 39.78110197709871, 116.31637573242189 39.818029898770206, 116.27655029296876 39.93817189499188, 116.42486572265626 39.99500778093748))";

        this.lineString = "LINESTRING (117.18292236328126 40.16208338164619, 119.01489257812501 39.48284540453334)";

        this.multiPoint = "MULTIPOINT ((115.48690795898439 40.12639098502455), (115.80139160156251 40.148438503139076), (115.83847045898439 39.9665957444875), (115.90850830078126 39.854937988531276), " +
                "(115.98403930664062 39.816975090490004), (115.84808349609376 39.769491963709), (115.60638427734376 39.707186656826565), (115.44708251953126 39.82119422647455), " +
                "(115.32348632812501 39.961332959837826), (115.36605834960939 40.09593265290902), (115.59951782226564 39.940277770390324), (115.74371337890626 39.842286020743394), " +
                "(115.58715820312501 39.79271003204449), (115.76019287109376 39.7631584037253), (115.87280273437501 39.67759833072648), (115.96481323242189 40.18307014852534), " +
                "(115.69152832031251 40.2155868104582), (115.63934326171876 40.073868105094846), (115.43060302734376 39.56547053068436), (115.66955566406251 39.470125122358176), " +
                "(115.83709716796875 39.55911824217187), (115.91949462890626 39.44679856427205), (115.54046630859376 39.42346418978385), (115.07354736328125 39.63319206567459), " +
                "(115.11474609375 40.092781012494065), (115.19439697265626 40.287906612507406), (114.98291015625001 39.83385008019448), (114.97467041015626 39.47224533091451), " +
                "(115.27130126953126 39.38101803294523), (115.59265136718751 39.34067026099156))";
    

    /**
     * wkt 转geometry
     *
     * @param wkt
     * @return
     * @throws ParseException
     */
    public static Geometry wkt2Geo(String wkt) throws ParseException 
        WKTReader reader = new WKTReader(gf);
        Geometry geom = reader.read(wkt);
        return geom;
    

    /**
     * 旋转
     *
     * @param coord  旋转坐标
     * @param center 旋转中心
     * @param angle  角度
     * @return 旋转后结果
     */
    public static Coordinate[] get(Coordinate[] coord, Coordinate center, double angle) 
        Coordinate[] newCoord = new Coordinate[coord.length];
        double cos = Math.cos(angle), sin = Math.sin(angle);
        double xc = center.x, yc = center.y;
        Coordinate ci;
        double x, y;
        for (int i = 0; i < coord.length; i++) 
            ci = coord[i];
            x = ci.x;
            y = ci.y;
            newCoord[i] = new Coordinate(xc + cos * (x - xc) - sin * (y - yc),
                    yc + sin * (x - xc) + cos * (y - yc));
        
        return newCoord;
    

    /**
     * 旋转点
     *
     * @param point  被旋转的点
     * @param center 旋转中心
     * @param angle  角度
     * @return 旋转后坐标
     */
    public static Coordinate get(Coordinate point, Coordinate center, double angle) 
        double cos = Math.cos(angle);
        double sin = Math.sin(angle);
        double x = point.x;
        double y = point.y;
        double centerX = center.x;
        double centerY = center.y;
        return new Coordinate(centerX + cos * (x - centerX) - sin * (y - centerY),
                centerY + sin * (x - centerX) + cos * (y - centerY));
    

    /**
     * 旋转
     *
     * @param geom   geometry
     * @param center 旋转中心
     * @param angle  旋转角度
     * @param gf     构造器
     * @return 旋转结果
     */
    public static Geometry get(Geometry geom, Coordinate center, double angle, GeometryFactory gf) 
        if (geom instanceof Point) 
            return get((Point) geom, center, angle, gf);
         else if (geom instanceof Polygon) 
            return get((Polygon) geom, center, angle, gf);
         else if (geom instanceof LineString) 
            return get((LineString) geom, center, angle, gf);
         else if (geom instanceof LinearRing) 
            return get((LinearRing) geom, center, angle, gf);
        
        return null;
    

    /**
     * 旋转
     *
     * @param linearRing 旋转环
     * @param center     旋转中心
     * @param angle      旋转角度
     * @param gf         构造器
     * @return 旋转后结果
     */
    public static LinearRing get(LinearRing linearRing, Coordinate center, double angle,
                                 GeometryFactory gf) 
        return gf.createLinearRing(get(linearRing.getCoordinates(), center, angle));
    

    /**
     * x旋转
     *
     * @param geom   旋转面
     * @param center 旋转中心
     * @param angle  旋转角度
     * @param gf     构造器
     * @return 旋转结果
     */
    public static Polygon rotationPolygon(Polygon geom, Coordinate center, double angle, GeometryFactory gf) 
        LinearRing linearRing = get((LinearRing) geom.getExteriorRing(), center, angle, gf);
        LinearRing[] linearRings = new LinearRing[geom.getNumInteriorRing()];
        for (int j = 0; j < geom.getNumInteriorRing(); j++) 
            linearRings[j] = get((LinearRing) geom.getInteriorRingN(j), center, angle, gf);
        
        return gf.createPolygon(linearRing, linearRings);
    

    public static Polygon getRotatedMinRectangle(Geometry geom, GeometryFactory gf) 
        // 获取凸包算法
        Geometry hull = (new ConvexHull(geom)).getConvexHull();
        if (!(hull instanceof Polygon)) 
            return null;
        
        Polygon convexHull = (Polygon) hull;
//        System.out.println(convexHull);

        // 直接使用中心值
        Coordinate c = geom.getCentroid().getCoordinate();
//        System.out.println("==============旋转基点==============");
//        System.out.println(new GeometryFactory().createPoint(c));
//        System.out.println("==============旋转基点==============");
        Coordinate[] coords = convexHull.getExteriorRing().getCoordinates();

        double minArea = Double.MAX_VALUE;
        double minAngle = 0;
        Polygon ssr = null;
        Coordinate ci = coords[0];
        Coordinate cii;
        for (int i = 0; i < coords.length - 1; i++) 
            cii = coords[i + 1];
            double angle = Math.atan2(cii.y - ci.y, cii.x - ci.x);
            Polygon rect = (Polygon) rotationPolygon(convexHull, c, -1 * angle, gf).getEnvelope();
            double area = rect.getArea();
//            此处可以将 rotationPolygon 放到list中求最小值
//            Polygon rotationPolygon = Rotation.get(rect, c, angle, gf);
//            System.out.println(rotationPolygon);
            if (area < minArea) 
                minArea = area;
                ssr = rect;
                minAngle = angle;
            
            ci = cii;
        

        return rotationPolygon(ssr, c, minAngle, gf);
    

    /**
     * 获取geom的面积最小外接矩形
     * 1. 获取Envelope的外接矩形,获取面积;
     * 2. 获取凸包,旋转获取每一条边对应的外接矩形,得到凸包面积最小外接矩形;
     * 3. 将1获取的矩形与2获取的矩形面积进行比较,得到最终的面积最小外接矩形;
     *
     * @param geom
     * @return
     */
    private Geometry getMinRectangle(Geometry geom) 
        double envelopeArea = geom.getEnvelope().getArea();
        double rotateMinArea = getRotatedMinRectangle(geom, gf).getArea();
        return envelopeArea > rotateMinArea ? getRotatedMinRectangle(geom, gf) : geom.getEnvelope();
    

    @Test
    public void testGeom() throws ParseException 
        String lineString = "LINESTRING(120.067304 30.39167,120.067304 30.36469," +
                "120.04022 30.3512,120.013136 30.36469," +
                "120.013136 30.39167,120.04022 30.40516)";
        lineString = "LINESTRING(120.14322240845 30.236064370321," +
                "120.1233608862 30.224531990576,120.0831192649 30.238661839459," +
                "120.07632605996 30.2524671110650)";
        Geometry geom = wkt2Geo(lineString);
        log.info("origin  : ", lineString);
        log.info("Envelope:  ", geom.getEnvelope().getArea(), geom.getEnvelope()); // 外接矩形4个点
        log.info("", geom.getEnvelopeInternal()); // 外接矩形对角线俩点
        log.info("", geom.getBoundary()); // 首尾俩点
        log.info("", (new ConvexHull(geom)).getConvexHull()); // 凸包
        log.info("Rotated :  ", getRotatedMinRectangle(geom, gf).getArea(), getRotatedMinRectangle(geom, gf));
        log.info("Min     :  ", getMinRectangle(geom).getArea(), getMinRectangle(geom));
    

3. html源码

multi2.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Add multiple geometries from one GeoJSON source</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
    <link href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css" rel="stylesheet">
    <script src="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js"></script>
    <style>
        body 
            margin: 0;
            padding: 0;
        

        #map 
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        
    </style>
</head>
<body>
<div id="map"></div>
<script>

    mapboxgl.accessToken = 'pk.eyJ1IjoibGl1bWsiLCJhIjoiY2phY2pyZWp6MGFtMjJ6cGw2YnhqM2F6aCJ9.7ganszcmTQ4q_yRYeLc99g';
    var map = new mapboxgl.Map(
        container: 'map',
        style: 'mapbox://styles/mapbox/streets-v11',
        center: [120.06022, 30.37818], //地图中心点
        zoom: 10
    );

    map.on('load', function () 
        map.addSource('national-park', 
            'type': 'geojson',
            'data': 
                'type': 'FeatureCollection',
                'features': [
                    //凸包
                    
                        'type': 'Feature',
                        'geometry': 
                            'type': 'Polygon',
                            'coordinates': [
                                [
                                    [120.04022, 30.3512], [120.013136, 30.36469],
                                    [120.013136, 30.39167], [120.04022, 30.40516],
                                    [120.067304, 30.39167],
                                    [120.067304, 30.36469], [120.04022, 30.3512]
                                ]
                            ]
                        
                    ,
                    // Envelope外接矩形
                    
                        'type': 'Feature',
                        'geometry': 
                            'type': 'Polygon',
                            'coordinates': [
                                [
                                    [120.013136, 30.3512], [120.013136, 30.40516],
                                    [120.067304, 30.40516], [120.067304, 30.3512]
                                ]
                            ]
                        
                    ,
                    // 旋转后最小面积外接矩形
                    
                        'type': 'Feature',
                        'geometry': 
                            'type': 'Polygon',
                            'coordinates'以上是关于Java求geometry的面积最小外接矩形的主要内容,如果未能解决你的问题,请参考以下文章

最小外接矩形的简介

OpenCV中的四边形运算(一)——最小外接矩形

UVA 12307 Smallest Enclosing Rectangle

最小外接矩形

求二值图像的最小外接矩形

OpenCV求最小外接圆最小外接矩形椭圆拟合直线拟合