-
坐标系的加密特性
WGS84到BD-09需经过两次加密(WGS84 → GCJ-02 → BD-09),而逆向转换需解密两次(BD-09 → GCJ-02 → WGS84)。GCJ-02的加密算法包含非线性偏移,直接逆向公式可能导致误差。使用迭代法逆向计算WGS84坐标。 -
高精度计算
使用BigDecimal类型存储坐标值,避免中间过程的精度截断。确保所有数学运算(如三角函数)使用高精度库(采用maven中的Apfloat库)。 -
可靠的转换算法
使用经过广泛验证的开源实现,而非自行推导公式,以降低算法误差。 -
测试与验证
通过大规模坐标回环测试(WGS84 → BD-09 → WGS84)验证误差范围,确保精度可接受。
package org.example;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatMath;
import org.apfloat.ApfloatRuntimeException;
public class HighPrecisionCoordinateConverter {
// 精度配置
private static final MathContext MC = MathContext.DECIMAL128;
private static final int APFLOAT_PRECISION = 50;
// 常量定义(使用字符串初始化)
private static final BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510");
private static final BigDecimal X_PI = PI.multiply(new BigDecimal("3000")).divide(new BigDecimal("180"), MC);
private static final BigDecimal A = new BigDecimal("6378245.0");
private static final BigDecimal EE = new BigDecimal("0.00669342162296594323");
private static final BigDecimal MAGIC_NUMBER = new BigDecimal("0.00002");
private static final BigDecimal BD_OFFSET = new BigDecimal("0.0065");
// 国内坐标范围
private static final BigDecimal CHINA_LNG_MIN = new BigDecimal("72.004");
private static final BigDecimal CHINA_LNG_MAX = new BigDecimal("137.8347");
private static final BigDecimal CHINA_LAT_MIN = new BigDecimal("0.8293");
private static final BigDecimal CHINA_LAT_MAX = new BigDecimal("55.8271");
//======================== 公有接口 ========================
public static double[] bd09ToWgs84(double lng, double lat) {
BigDecimal[] gcj = bd09ToGcj02(new BigDecimal(lng), new BigDecimal(lat));
BigDecimal[] wgs = gcj02ToWgs84(gcj[0], gcj[1]);
return convertToDoubleArray(wgs);
}
public static double[] wgs84ToBd09(double lng, double lat) {
BigDecimal[] gcj = wgs84ToGcj02(new BigDecimal(lng), new BigDecimal(lat));
BigDecimal[] bd = gcj02ToBd09(gcj[0], gcj[1]);
return convertToDoubleArray(bd);
}
//======================== 核心算法 ========================
private static BigDecimal[] bd09ToGcj02(BigDecimal bdLng, BigDecimal bdLat) {
BigDecimal x = bdLng.subtract(BD_OFFSET, MC);
BigDecimal y = bdLat.subtract(new BigDecimal("0.006"), MC);
BigDecimal z = sqrt(x.pow(2).add(y.pow(2)))
.subtract(sin(y.multiply(X_PI)).multiply(MAGIC_NUMBER), MC);
BigDecimal theta = atan2(y, x).subtract(
cos(x.multiply(X_PI)).multiply(new BigDecimal("0.000003")), MC);
return new BigDecimal[]{
z.multiply(cos(theta), MC),
z.multiply(sin(theta), MC)
};
}
private static BigDecimal[] gcj02ToBd09(BigDecimal gcjLng, BigDecimal gcjLat) {
BigDecimal z = sqrt(gcjLng.pow(2).add(gcjLat.pow(2)))
.add(sin(gcjLat.multiply(X_PI)).multiply(MAGIC_NUMBER), MC);
BigDecimal theta = atan2(gcjLat, gcjLng)
.add(cos(gcjLng.multiply(X_PI)).multiply(new BigDecimal("0.000003")), MC);
return new BigDecimal[]{
z.multiply(cos(theta), MC).add(BD_OFFSET, MC),
z.multiply(sin(theta), MC).add(new BigDecimal("0.006"), MC)
};
}
private static BigDecimal[] wgs84ToGcj02(BigDecimal lng, BigDecimal lat) {
if (isOutsideChina(lng, lat)) {
return new BigDecimal[]{lng, lat};
}
BigDecimal[] delta = calculateDelta(lng, lat);
return new BigDecimal[]{
lng.add(delta[0], MC),
lat.add(delta[1], MC)
};
}
private static BigDecimal[] gcj02ToWgs84(BigDecimal lng, BigDecimal lat) {
if (isOutsideChina(lng, lat)) {
return new BigDecimal[]{lng, lat};
}
BigDecimal[] delta = calculateDelta(lng, lat);
return new BigDecimal[]{
lng.subtract(delta[0], MC),
lat.subtract(delta[1], MC)
};
}
//======================== 数学工具方法 ========================
private static BigDecimal[] calculateDelta(BigDecimal lng, BigDecimal lat) {
BigDecimal baseLng = lng.subtract(new BigDecimal("105"), MC);
BigDecimal baseLat = lat.subtract(new BigDecimal("35"), MC);
BigDecimal radLat = radians(lat);
BigDecimal magic = sin(radLat).pow(2).multiply(EE, MC);
magic = BigDecimal.ONE.subtract(magic, MC);
BigDecimal sqrtMagic = sqrt(magic);
BigDecimal dLat = transformLat(baseLng, baseLat)
.multiply(new BigDecimal("180"), MC)
.divide(A.multiply(BigDecimal.ONE.subtract(EE))
.divide(magic.multiply(sqrtMagic), MC)
.divide(PI, MC),MC);
BigDecimal dLng = transformLng(baseLng, baseLat)
.multiply(new BigDecimal("180"), MC)
.divide(A.multiply(cos(radLat)), MC)
.divide(sqrtMagic.multiply(PI), MC);
return new BigDecimal[]{dLng, dLat};
}
private static BigDecimal transformLat(BigDecimal x, BigDecimal y) {
MathContext mc = MathContext.DECIMAL128;
// 第一项:-100 + 2x + 3y
BigDecimal term1 = new BigDecimal("-100")
.add(x.multiply(TWO, mc))
.add(y.multiply(THREE, mc), mc);
// 第二项:0.2y²
BigDecimal term2 = y.pow(2, mc)
.multiply(new BigDecimal("0.2"), mc);
// 第三项:0.1xy
BigDecimal term3 = x.multiply(y, mc)
.multiply(ONE_TENTH, mc);
// 第四项:0.2√|x|
BigDecimal term4 = sqrt(x.abs(), APFLOAT_PRECISION)
.multiply(new BigDecimal("0.2"), mc);
// 合并基础项
BigDecimal result = term1.add(term2, mc)
.add(term3, mc)
.add(term4, mc);
// 第五项:正弦波动项1 (20sin6πx + 20sin2πx) * 2/3
BigDecimal sin6x = sin(x.multiply(SIX).multiply(PI), APFLOAT_PRECISION);
BigDecimal sin2x = sin(x.multiply(TWO).multiply(PI), APFLOAT_PRECISION);
BigDecimal wave1 = TWENTY.multiply(sin6x.add(sin2x), mc)
.multiply(TWO_THIRDS, mc);
// 第六项:正弦波动项2 (20sinπy + 40sin(πy/3)) * 2/3
BigDecimal sinY = sin(y.multiply(PI), APFLOAT_PRECISION);
BigDecimal sinY3 = sin(y.divide(THREE, mc).multiply(PI), APFLOAT_PRECISION);
BigDecimal wave2 = TWENTY.multiply(sinY, mc)
.add(FORTY.multiply(sinY3, mc), mc)
.multiply(TWO_THIRDS, mc);
// 第七项:长周期波动项 (160sin(πy/12) + 320sin(πy/30)) * 2/3
BigDecimal sinY12 = sin(y.divide(new BigDecimal("12"), mc).multiply(PI), APFLOAT_PRECISION);
BigDecimal sinY30 = sin(y.divide(new BigDecimal("30"), mc).multiply(PI), APFLOAT_PRECISION);
BigDecimal wave3 = new BigDecimal("160").multiply(sinY12, mc)
.add(new BigDecimal("320").multiply(sinY30, mc), mc)
.multiply(TWO_THIRDS, mc);
// 累计所有分量
return result.add(wave1, mc)
.add(wave2, mc)
.add(wave3, mc);
}
/**
* 高精度经度转换
*/
private static BigDecimal transformLng(BigDecimal x, BigDecimal y) {
MathContext mc = MathContext.DECIMAL128;
// 基础项:300 + x + 2y
BigDecimal base = new BigDecimal("300")
.add(x, mc)
.add(y.multiply(TWO, mc), mc);
// 二次项:0.1x² + 0.1xy
BigDecimal quadTerm = x.pow(2, mc).multiply(ONE_TENTH, mc)
.add(x.multiply(y, mc).multiply(ONE_TENTH, mc), mc);
// 根号项:0.1√|x|
BigDecimal sqrtTerm = sqrt(x.abs(), APFLOAT_PRECISION)
.multiply(ONE_TENTH, mc);
BigDecimal result = base.add(quadTerm, mc)
.add(sqrtTerm, mc);
// 高频波动项1 (20sin6πx + 20sin2πx) * 2/3
BigDecimal sin6x = sin(x.multiply(SIX).multiply(PI), APFLOAT_PRECISION);
BigDecimal sin2x = sin(x.multiply(TWO).multiply(PI), APFLOAT_PRECISION);
BigDecimal wave1 = TWENTY.multiply(sin6x.add(sin2x), mc)
.multiply(TWO_THIRDS, mc);
// 中频波动项2 (20sinπx + 40sin(πx/3)) * 2/3
BigDecimal sinX = sin(x.multiply(PI), APFLOAT_PRECISION);
BigDecimal sinX3 = sin(x.divide(THREE, mc).multiply(PI), APFLOAT_PRECISION);
BigDecimal wave2 = TWENTY.multiply(sinX, mc)
.add(FORTY.multiply(sinX3, mc), mc)
.multiply(TWO_THIRDS, mc);
// 低频波动项3 (150sin(πx/12) + 300sin(πx/30)) * 2/3
BigDecimal sinX12 = sin(x.divide(new BigDecimal("12"), mc).multiply(PI), APFLOAT_PRECISION);
BigDecimal sinX30 = sin(x.divide(new BigDecimal("30"), mc).multiply(PI), APFLOAT_PRECISION);
BigDecimal wave3 = new BigDecimal("150").multiply(sinX12, mc)
.add(new BigDecimal("300").multiply(sinX30, mc), mc)
.multiply(TWO_THIRDS, mc);
return result.add(wave1, mc)
.add(wave2, mc)
.add(wave3, mc);
}
//======================== 高精度数学函数 ========================
private static BigDecimal sin(BigDecimal x) {
try {
return new BigDecimal(String.valueOf(ApfloatMath.sin(new Apfloat(x))));
} catch (ApfloatRuntimeException e) {
return new BigDecimal(Math.sin(x.doubleValue()));
}
}
private static BigDecimal cos(BigDecimal x) {
try {
return new BigDecimal(String.valueOf(ApfloatMath.cos(new Apfloat(x))));
} catch (ApfloatRuntimeException e) {
return new BigDecimal(Math.cos(x.doubleValue()));
}
}
//
private static BigDecimal atan2(BigDecimal y, BigDecimal x) {
try {
Apfloat apfY = new Apfloat(y.toString(), APFLOAT_PRECISION);
Apfloat apfX = new Apfloat(x.toString(), APFLOAT_PRECISION);
return new BigDecimal(ApfloatMath.atan2(apfY, apfX).toString());
} catch (ApfloatRuntimeException e) {
return new BigDecimal(Math.atan2(y.doubleValue(), x.doubleValue()));
}
}
private static BigDecimal sqrt(BigDecimal x) {
try {
return new BigDecimal(
ApfloatMath.sqrt(new Apfloat(x.toString(), APFLOAT_PRECISION))
.toString());
} catch (ApfloatRuntimeException e) {
return BigDecimal.valueOf(Math.sqrt(x.doubleValue()));
}
}
private static BigDecimal sin(BigDecimal x, int precision) {
try {
Apfloat radians = new Apfloat(x.toString(), precision);
return new BigDecimal(ApfloatMath.sin(radians).toString());
} catch (ApfloatRuntimeException e) {
// 异常时降级到JDK的双精度计算
return BigDecimal.valueOf(Math.sin(x.doubleValue()));
}
}
/**
* 高精度平方根 (使用Apfloat实现)
*/
private static BigDecimal sqrt(BigDecimal x, int precision) {
try {
Apfloat num = new Apfloat(x.toString(), precision);
return new BigDecimal(ApfloatMath.sqrt(num).toString());
} catch (ApfloatRuntimeException e) {
return BigDecimal.valueOf(Math.sqrt(x.doubleValue()));
}
}
//======================== 工具方法 ========================
private static boolean isOutsideChina(BigDecimal lng, BigDecimal lat) {
return lng.compareTo(CHINA_LNG_MIN) < 0 || lng.compareTo(CHINA_LNG_MAX) > 0 ||
lat.compareTo(CHINA_LAT_MIN) < 0 || lat.compareTo(CHINA_LAT_MAX) > 0;
}
private static BigDecimal radians(BigDecimal degrees) {
return degrees.multiply(PI).divide(new BigDecimal("180"), MC);
}
private static double[] convertToDoubleArray(BigDecimal[] arr) {
return new double[]{
arr[0].setScale(12, RoundingMode.HALF_UP).doubleValue(),
arr[1].setScale(12, RoundingMode.HALF_UP).doubleValue()
};
}
//======================== 常量缓存 ========================
private static final BigDecimal TWO = new BigDecimal("2");
private static final BigDecimal THREE = new BigDecimal("3");
private static final BigDecimal SIX = new BigDecimal("6");
private static final BigDecimal TWENTY = new BigDecimal("20");
private static final BigDecimal FORTY = new BigDecimal("40");
private static final BigDecimal ONE_TENTH = new BigDecimal("0.1");
private static final BigDecimal TWO_THIRDS = new BigDecimal("2").divide(new BigDecimal("3"), 34, RoundingMode.HALF_UP);
}