[几何] 计算任意多边形的面积、中心、重心

最近项目用到:在不规则任意多边形的中心点加一个图标。(e.g: xx地区发生暴雪,暴雪区域是多边形,给多边形中心加一个暴雪的图标)

之前的设计是,计算不规则多边形范围矩形bounds的中心点。这个比较简单,对于一些圆,矩形,凸多边形都比较适合。但是遇到凹多边形就会出现问题,比如一个月牙型的不规则多边形,bounds的中心点,就落到月牙外了。就有点难以接受了。

经过讨论,决定将中心改为重心。

下面上代码,

计算不规则任意多边形的中心


    /**
     * 获取不规则多边形几何中心点
     *
     * @param mPoints
     * @return
     */
    public static LatLng getCenterPoint(List<LatLng> mPoints) {
        // 1 自己计算
        // 2 使用Google map API提供的方法(推荐)
        LatLngBounds.Builder boundsBuilder = LatLngBounds.builder();
        for (LatLng ll : mPoints)
            boundsBuilder.include(ll);
        return boundsBuilder.build().getCenter();
    }

 计算不规则任意多边形的重心:

    /**
     * 获取不规则多边形重心点
     *
     * @param mPoints
     * @return
     */
    public static LatLng getCenterOfGravityPoint(List<LatLng> mPoints) {
        double area = 0.0;//多边形面积
        double Gx = 0.0, Gy = 0.0;// 重心的x、y
        for (int i = 1; i <= mPoints.size(); i++) {
            double iLat = mPoints.get(i % mPoints.size()).latitude;
            double iLng = mPoints.get(i % mPoints.size()).longitude;
            double nextLat = mPoints.get(i - 1).latitude;
            double nextLng = mPoints.get(i - 1).longitude;
            double temp = (iLat * nextLng - iLng * nextLat) / 2.0;
            area += temp;
            Gx += temp * (iLat + nextLat) / 3.0;
            Gy += temp * (iLng + nextLng) / 3.0;
        }
        Gx = Gx / area;
        Gy = Gy / area;
        return new LatLng(Gx, Gy);
    }

其中LatLng类就是一个包含经纬度点的简单类。可以自己创建一个包含 x ,y 的类代替。

    public class LatLng {
        public final double latitude;
        public final double longitude;
    }

好多人说不知道LatLngBounds类的具体实现,其实这是Google map包中的一个类,内部功能很简单,就是提供了一个构造器Builder可以不断的往里面添加经纬度点LatLng,不断计算更新Bounds范围和center中心点。

下面贴上整理后的LatLngBounds类,可以减去导入Google map包的麻烦

/**
 * 经纬度范围类
 *
 * 复写com.google.android.gms.maps.model.LatLngBounds中核心方法
 *
 * @author maple
 */
@Deprecated("条件允许,请使用com.google.android.gms.maps.model.LatLngBounds")
class LatLngBounds constructor(
        private val southwest: LatLng,// 左下角 点
        private val northeast: LatLng // 右上角 点
) {

    val center: LatLng
        get() {
            // 计算中心点纬度
            val centerLat = (this.southwest.latitude + this.northeast.latitude) / 2.0
            // 计算中心点经度
            val neLng = this.northeast.longitude // 右上角 经度
            val swLng: Double = this.southwest.longitude // 左下角 经度
            val centerLng: Double = if (swLng <= neLng) {
                (neLng + swLng) / 2.0
            } else {
                (neLng + 360.0 + swLng) / 2.0
            }
            return LatLng(centerLat, centerLng)
        }

    // 某个点是否在该范围内(包含边界)
    fun contains(point: LatLng): Boolean {
        return latContains(point.latitude) && this.lngContains(point.longitude)
    }

    // 某个纬度值是否在该范围内(包含边界)
    private fun latContains(lat: Double): Boolean {
        return this.southwest.latitude <= lat && lat <= this.northeast.latitude
    }

    // 某个经度值是否在该范围内(包含边界)
    private fun lngContains(lng: Double): Boolean {
        return if (this.southwest.longitude <= this.northeast.longitude) {
            this.southwest.longitude <= lng && lng <= this.northeast.longitude
        } else {
            this.southwest.longitude <= lng || lng <= this.northeast.longitude
        }
    }

    // 小数据量可以使用该方法,大数据量建议使用Builder中的include()
    fun including(point: LatLng): LatLngBounds {
        val swLat = Math.min(this.southwest.latitude, point.latitude)
        val neLat = Math.max(this.northeast.latitude, point.latitude)
        var neLng = this.northeast.longitude
        var swLng = this.southwest.longitude
        val pLng = point.longitude
        if (!this.lngContains(pLng)) {
            if (zza(swLng, pLng) < zzb(neLng, pLng)) {
                swLng = pLng
            } else {
                neLng = pLng
            }
        }
        return LatLngBounds(LatLng(swLat, swLng), LatLng(neLat, neLng))
    }

    /**
     * LatLngBounds生成器
     */
    class Builder {
        private var swLat = 1.0 / 0.0   // 左下角 纬度
        private var swLng = 0.0 / 0.0   // 左下角 经度
        private var neLat = -1.0 / 0.0  // 右上角 纬度
        private var neLng = 0.0 / 0.0   // 右上角 经度

        fun include(point: LatLng): Builder {
            this.swLat = Math.min(this.swLat, point.latitude)
            this.neLat = Math.max(this.neLat, point.latitude)
            val pLng = point.longitude
            if (java.lang.Double.isNaN(this.swLng)) {
                this.swLng = pLng
            } else {
                if (lngContains(pLng)) {
                    return this
                }
                if (zza(this.swLng, pLng) < zzb(this.neLng, pLng)) {
                    this.swLng = pLng
                    return this
                }
            }
            this.neLng = pLng
            return this
        }

        // 某个经度值是否在该范围内(包含边界)
        private fun lngContains(lng: Double): Boolean {
            return if (this.swLng <= this.neLng) {
                this.swLng <= lng && lng <= this.neLng
            } else {
                this.swLng <= lng || lng <= this.neLng
            }
        }

        fun build(): LatLngBounds {
            // Preconditions.checkState(!java.lang.Double.isNaN(this.swLng), "no included points")
            return LatLngBounds(LatLng(this.swLat, this.swLng), LatLng(this.neLat, this.neLng))
        }
    }

    companion object {
        fun builder(): Builder {
            return Builder()
        }

        // 前者 - 后者
        private fun zza(var0: Double, var2: Double): Double {
            return (var0 - var2 + 360.0) % 360.0
        }

        // 后者 - 前者
        private fun zzb(var0: Double, var2: Double): Double {
            return (var2 - var0 + 360.0) % 360.0
        }
    }

}

注意⚠️:LatLngBounds中including()方法  和  Builder中include()方法在具体实现上是相同,但是LatLngBounds每次都会new一个新的对象,所以不适合大批量数据时使用。

e.g:在计算一个点比较多的Polygon的范围时,建议使用Builder中的include()方法。

 

Demo地址:https://github.com/shaoshuai904/GoogleMap_Demo

通过这张图,就可以发现中心和重心的区别

     

项目实际表现:

     

 

 

  • 9
    点赞
  • 68
    收藏
  • 打赏
    打赏
  • 32
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论 32

打赏作者

Maple_Shao

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值