版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
最近项目用到:在不规则任意多边形的中心点加一个图标。(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()方法。
通过这张图,就可以发现中心和重心的区别
项目实际表现: