计算任意多边形的面积、中心、重心
最近项目用到:在不规则任意多边形的中心点加一个图标。(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
通过这张图,就可以发现中心和重心的区别
本文转自:https://blog.csdn.net/shao941122/article/details/53671643