JTS 求几何质心,外包矩形

前言

在 GIS 开发中,常常会用到求几何对象的外包矩形,求中心点,求质心等等,今天我让AI写了一些求质心的方法,可以看看,当作参考,最终实际使用个人的建议是:如果只是求质心,并且要使其在内部,那么用geometry.getInteriorPoint()就可以了

接下来一起看看AI分析了哪些东西(二,三,四,六,七里的方法是AI给的):

用到的Maven依赖

java 复制代码
        <dependency>
            <groupId>org.locationtech.jts</groupId>
            <artifactId>jts-core</artifactId>
            <version>1.19.0</version>
        </dependency>

一、JTS 原生质心(基准方案)

java 复制代码
public static Point jtsCentroid(Geometry geometry) {
    if (geometry == null || geometry.isEmpty()) {
        return null;
    }
    return geometry.getCentroid();
}

特点:

  • ✅ 简单、稳定、性能好
  • ✅ 对任意 Geometry 类型都可用
  • ❌ 对 MultiPolygon / 碎面不友好
  • ❌ 凹多边形可能落在外部

适合场景: 单 Polygon、数据预览、快速计算

其实个人认为这个方法基本能覆盖大部分情况了,如果图省事的话其实是能直接用的,或者用我前言里说的geometry.getInteriorPoint()

二、MultiPolygon:按面积加权质心

当 MultiPolygon 中存在多个面时,更合理的做法是按面积加权平均:

Cx = ∑(Ai × xi) / ∑Ai Cy = ∑(Ai × yi) / ∑Ai

java 复制代码
public static Point weightedCentroidByArea(MultiPolygon mp) {
    double totalArea = 0.0, cx = 0.0, cy = 0.0;
    for (int i = 0; i < mp.getNumGeometries(); i++) {
        Polygon p = (Polygon) mp.getGeometryN(i);
        double area = p.getArea();
        Point c = p.getCentroid();
        cx += c.getX() * area;
        cy += c.getY() * area;
        totalArea += area;
    }
    return geometryFactory.createPoint(
        new Coordinate(cx / totalArea, cy / totalArea));
}
  • ✅ 优点:比原生质心更符合"整体重心"直觉
  • ❌ 缺点:仍会被极小碎面轻微影响

三、忽略极小面的加权质心

在很多业务数据中,MultiPolygon 常伴随大量噪声碎面(如总面积 1% 以内的微面),对质心计算引入了不必要的干扰。

java 复制代码
public static Point weightedCentroidIgnoreSmall(
        MultiPolygon mp, double minAreaRatio) {
    double totalArea = mp.getArea();
    double usedArea = 0.0, cx = 0.0, cy = 0.0;
    for (int i = 0; i < mp.getNumGeometries(); i++) {
        Polygon p = (Polygon) mp.getGeometryN(i);
        double area = p.getArea();
        if (area / totalArea < minAreaRatio) continue;
        Point c = p.getCentroid();
        cx += c.getX() * area;
        cy += c.getY() * area;
        usedArea += area;
    }
    return geometryFactory.createPoint(
        new Coordinate(cx / usedArea, cy / usedArea));
}

经验值: minAreaRatio = 0.01(忽略小于总面积 1% 的面)

工程实践中最常用、最稳健的方案

四、仅取最大面的质心(极端简化)

java 复制代码
public static Point largestPolygonCentroid(MultiPolygon mp) {
    Polygon largest = null;
    double maxArea = 0.0;
    for (int i = 0; i < mp.getNumGeometries(); i++) {
        Polygon p = (Polygon) mp.getGeometryN(i);
        double area = p.getArea();
        if (area > maxArea) {
            maxArea = area;
            largest = p;
        }
    }
    return largest == null ? null : largest.getCentroid();
}
  • ✅ 优点:极度稳定,不受碎面干扰
  • ❌ 缺点:完全忽略次要面

适合场景: 行政区划、地块主区域标注

五、凹多边形:质心 vs 内部点

凹多边形的质心可能落在图形外部,这在做点标注时是个严重问题。JTS 提供了另一个方法:

java 复制代码
public static Point interiorPoint(Geometry geometry) {
    return geometry.getInteriorPoint();
}

对比:

方法 是否一定在内 稳定性
getCentroid()
getInteriorPoint()

💡 标注点 / 名称显示优先用 interiorPoint

六、MultiLineString:按长度加权质心

线要素没有"面积",应使用长度加权:

java 复制代码
public static Point weightedCentroidByLength(MultiLineString mls) {
    double totalLen = 0.0, cx = 0.0, cy = 0.0;
    for (int i = 0; i < mls.getNumGeometries(); i++) {
        LineString ls = (LineString) mls.getGeometryN(i);
        double len = ls.getLength();
        Point c = ls.getCentroid();
        cx += c.getX() * len;
        cy += c.getY() * len;
        totalLen += len;
    }
    return geometryFactory.createPoint(
        new Coordinate(cx / totalLen, cy / totalLen));
}

适合场景: 道路网、管线、河流中心线

七、自定义权重质心(通用解法)

当不同几何对象本身就有业务权重(如人口、产值):

java 复制代码
public static Point customWeightedCentroid(
        Geometry[] geometries, double[] weights) {
    double wx = 0.0, wy = 0.0, totalWeight = 0.0;
    for (int i = 0; i < geometries.length; i++) {
        Point c = geometries[i].getCentroid();
        double w = weights[i];
        wx += c.getX() * w;
        wy += c.getY() * w;
        totalWeight += w;
    }
    return geometryFactory.createPoint(
        new Coordinate(wx / totalWeight, wy / totalWeight));
}

这是最通用的质心抽象模型 ✅

八、外包矩形

java 复制代码
// 外包矩形
public static Geometry boundingBox(Geometry geometry) {
    Envelope env = geometry.getEnvelopeInternal();
    return geometryFactory.toGeometry(env);
}

这个没什么好说,比较常规了

总结

场景 推荐方法
单 Polygon getCentroid()
MultiPolygon(通用) weightedCentroidIgnoreSmall()
强抗噪需求 largestPolygonCentroid()
凹多边形标注 getInteriorPoint()
线要素 weightedCentroidByLength()
业务加权 customWeightedCentroid()
使结果在内部一般使用 getInteriorPoint()

有试了下AI给的几个方法,个人觉得都一般,不如jts的getCentroid和getInteriorPoint,实际生产使用,建议还是用这两个,除非说计算和实际业务有关联,这种情况就要自己写方法了。