【未完成】GPS坐标转换为平面坐标

最近基于位置服务比较火,为了赶个时髦,我也尝试研究了一下位置数据。

想到的第一件事就是如果把GPS所代表的球面坐标转换为我们熟悉平面坐标。但是就这第一步的想法可就把我难坏了,因为理论上并不能把球面展开为平面,最后翻阅各种论文发现需要借助“高斯-克吕格坐标系转换算法”将球面局部近似展开为平面。

不得不提及一些老一辈的学者,本文主要参考杨启和教授的论文,他治学的严谨远远超过我的想象。当时计算机并不普及,公式的推导完全依靠人工,后面程序里将会展示公式有多么复杂。

代码如下:

package location.maputil;

import java.text.DecimalFormat;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// 参考《GPS经纬度坐标转平面坐标的简化计算方法及精度分析》
// 参考《WGS84、北京54、高斯-克吕格坐标系转换算法》
// 参考《高斯-克吕格投影常系数和变系数正反解公式的讨论和应用》
// 球面坐标理论上不能展开为平面坐标,高斯-克吕格坐标系是将球面坐标近似展开为多平面坐标系,本工具将每个城市作为一个独立的坐标系
// 本工具目前只针对北京市、上海市
// 北京:B~[39.4, 41.0] L~[115.4, 117.5] L0=116
// 上海:B~[30.6,32] L~[120.7,122.5] L0=121.5
// 重庆:B~[28.1,32.25] L~[105.1,110.2] L0=107.5
// 需要其他城市的坐标系请与程序开发者联系

public class Transformer {
	private static final Logger LOGGER = LoggerFactory
			.getLogger(Transformer.class);
	private static int Datum = 84; // 坐标系,默认为WGS84

	DecimalFormat dGPS = new DecimalFormat("0.000000");
	DecimalFormat dXY = new DecimalFormat("0");

	/* 参考椭球体基本量 */
	private double a; // 长半轴
	private double f; // 扁率 f=(a-b)/a
	private double b; // 短半轴 b=(1-f)a
	private double e; // 第一偏心率
	private double e1; // 第二偏心率

	private double L0; // 中央经度
	private double W0; // 原点维度
	private double k0; // 比例因子
	private double FE; // 东偏移
	private double FN; // 北偏移

	double PI = 3.141592653589794;
	double iPI = 0.0174532925199433; // PI/180

	public Transformer(String city) {
		LOGGER.info(city + " map is loading");
		// Datum 投影基准面类型:北京54基准面为54,西安80基准面为80,WGS84基准面为84
		if (Datum == 84) {
			a = 6378137;
			f = 1 / 298.257223563;
		} else if (Datum == 54) {
			a = 6378245;
			f = 1 / 298.3;
		} else if (Datum == 80) {
			a = 6378140;
			f = 1 / 298.257;
		} else {
			// 其他参数按照WGS84基准面为84处理
			a = 6378137;
			f = 1 / 298.257223563;
		}
		b = (1 - f) * a;
		e = Math.sqrt(2 * f - f * f);
		e1 = e / Math.sqrt(1 - e * e);

		L0 = 0; // 暂时处理
		W0 = 0; //
		k0 = 1; //
		FE = 0; //
		FN = 0; //
		if ("beijing".equals(city)) {
			L0 = 116;
		} else if ("shanghai".equals(city)) {
			L0 = 121.5;
		} else if ("chongqing".equals(city)) {
			L0 = 107.5;
		} else {
			LOGGER.info(city + " is not found, precision may be very low");
		}
		LOGGER.info(city + " map is loaded");
	}

	// 正解
	public String getXY(String lat, String lng) {
		double latitude = Double.valueOf(lat); // 纬度 B W
		double longitude = Double.valueOf(lng); // 经度 L J

		/* 必要变量准备 */
		double B = (latitude - W0) * iPI; // 纬差弧度
		double L = (longitude - L0) * iPI; // 经差弧度
		double sinB = Math.sin(B);
		double cosB = Math.cos(B);
		double tanB = Math.tan(B);
		double N = a / Math.sqrt(1 - Math.pow(e * sinB, 2)); // 卯酉圈曲率半径
		double g = e1 * cosB;

		/* 求解参数s */
		double s; // 赤道至纬度latitude的经线弧长
		double B0;
		double B2;
		double B4;
		double B6;
		double B8;
		double C = Math.pow(a, 2) / b;
		B0 = 1 - 3.0 / 4.0 * Math.pow(e1, 2) + 45.0 / 64.0 * Math.pow(e1, 4)
				- 175.0 / 256.0 * Math.pow(e1, 6) + 11025.0 / 16384.0
				* Math.pow(e1, 8);
		B2 = B0 - 1;
		B4 = 15.0 / 32.0 * Math.pow(e1, 4) - 175.0 / 384.0 * Math.pow(e1, 6)
				+ 3675.0 / 8192.0 * Math.pow(e1, 8);
		B6 = 0 - 35.0 / 96.0 * Math.pow(e1, 6) + 735.0 / 2048.0
				* Math.pow(e1, 8);
		B8 = 315.0 / 1024.0 * Math.pow(e1, 8);
		s = C
				* (B0 * B + sinB
						* (B2 * cosB + B4 * Math.pow(cosB, 3) + B6
								* Math.pow(cosB, 5) + B8 * Math.pow(cosB, 7)));

		/* 求解平面直角坐标系坐标 */
		double xTemp = s
				+ Math.pow(L, 2)
				* N
				* sinB
				* cosB
				/ 2.0
				+ Math.pow(L, 4)
				* N
				* sinB
				* Math.pow(cosB, 3)
				* (5 - Math.pow(tanB, 2) + 9 * Math.pow(g, 2) + 4 * Math.pow(g,
						4)) / 24.0 + Math.pow(L, 6) * N * sinB
				* Math.pow(cosB, 5)
				* (61 - 58 * Math.pow(tanB, 2) + Math.pow(tanB, 4)) / 720.0;
		double yTemp = L
				* N
				* cosB
				+ Math.pow(L, 3)
				* N
				* Math.pow(cosB, 3)
				* (1 - Math.pow(tanB, 2) + Math.pow(g, 2))
				/ 6.0
				+ Math.pow(L, 5)
				* N
				* Math.pow(cosB, 5)
				* (5 - 18 * Math.pow(tanB, 2) + Math.pow(tanB, 4) + 14
						* Math.pow(g, 2) - 58 * Math.pow(g, 2)
						* Math.pow(tanB, 2)) / 120.0;

		double x = xTemp + FN;
		double y = yTemp + FE;

		return dXY.format(x) + "," + dXY.format(y);
	}

	// 反解
	public String getGPS(String xStr, String yStr) {
		double x = Double.valueOf(xStr);
		double y = Double.valueOf(yStr);

		double El1 = (1 - Math.sqrt(1 - Math.pow(e, 2)))
				/ (1 + Math.sqrt(1 - Math.pow(e, 2)));
		double Mf = (x - FN) / k0; // 真实坐标值
		double Q = Mf
				/ (a * (1 - Math.pow(e, 2) / 4.0 - 3 * Math.pow(e, 4) / 64.0 - 5 * Math
						.pow(e, 6) / 256.0));
		double Bf = Q + (3 * El1 / 2.0 - 27 * Math.pow(El1, 3) / 32.0)
				* Math.sin(2 * Q)
				+ (21 * Math.pow(El1, 2) / 16.0 - 55 * Math.pow(El1, 4) / 32.0)
				* Math.sin(4 * Q) + (151 * Math.pow(El1, 3) / 96.0)
				* Math.sin(6 * Q) + 1097 / 512.0 * Math.pow(El1, 4)
				* Math.sin(8 * Q);
		double sinBf = Math.sin(Bf);
		double tanBf = Math.tan(Bf);
		double cosBf = Math.cos(Bf);
		double Rf = a * (1 - Math.pow(e, 2))
				/ Math.sqrt(Math.pow(1 - Math.pow(e * sinBf, 2), 3));
		double Nf = a / Math.sqrt(1 - Math.pow(e * sinBf, 2)); // 卯酉圈曲率半径
		double Tf = Math.pow(tanBf, 2);
		double D = (y - FE) / (k0 * Nf);
		double Cf = Math.pow(e1, 2) * Math.pow(cosBf, 2);

		double B = Bf
				- Nf
				* tanBf
				/ Rf
				* (Math.pow(D, 2)
						/ 2.0
						- (5 + 3 * Tf + 10 * Cf - 9 * Tf * Cf - 4
								* Math.pow(Cf, 2) - 9 * Math.pow(e1, 2))
						* Math.pow(D, 4) / 24.0 + (61 + 90 * Tf + 45
						* Math.pow(Tf, 2) - 256 * Math.pow(e1, 2) - 3 * Math
						.pow(Cf, 2)) * Math.pow(D, 6) / 720.0);
		double L = L0
				* iPI
				+ 1
				/ cosBf
				* (D - (1 + 2 * Tf + Cf) * Math.pow(D, 3) / 6.0 + (5 - 2 * Cf
						+ 28 * Tf - 3 * Math.pow(Cf, 2) + 8 * Math.pow(e1, 2) + 24 * Math
						.pow(Tf, 2)) * Math.pow(D, 5) / 120.0);

		double Bangle = B / iPI;
		double Langle = L / iPI;

		double latitude = Bangle + W0; // 纬度 B W x
		double longitude = Langle; // 经度 L J y

		return dGPS.format(latitude) + "," + dGPS.format(longitude);
	}

	public static double distacne(String str1, String str2) {
		String[] strArr1 = str1.split(",");
		String[] strArr2 = str2.split(",");
		double x1 = Double.valueOf(strArr1[0]);
		double y1 = Double.valueOf(strArr1[1]);
		double x2 = Double.valueOf(strArr2[0]);
		double y2 = Double.valueOf(strArr2[1]);

		return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
	}

	@Test
	public void test() {
		Transformer transfer = new Transformer("beijing");
		// String str1 = transfer.getGPS("39.927938","116.338967");
		// String str2 = transfer.getGPS("39.926573","116.338581");
		// System.out.println(distacne(str1, str2));
	}

	public static void main(String[] args) {
		Transformer transfer = new Transformer("beijing");
		System.out.println(transfer.getXY("41", "120"));
		System.out.println(transfer.getGPS("4548288", "336579"));

		Transformer transfer1 = new Transformer("shanghai");
		System.out.println(transfer1.getXY("30.7", "122"));
		System.out.println(transfer1.getGPS("3397821", "47901"));

		Transformer transfer2 = new Transformer("chongqing");
		System.out.println(transfer2.getXY("29.490295", "106.486654"));
		System.out.println(transfer2.getXY("29.615467", "106.581515"));

		System.out.println(distacne("3359469,-1464555", "3372354,-1453338"));

		Transformer transfer3 = new Transformer("c");
		System.out.println(transfer3.getXY("10", "127"));

		Transformer transfer4 = new Transformer("beijing");
		String str1 = transfer4.getXY("39.927938", "116.338967");
		String str2 = transfer4.getXY("41", "116.338581");
		System.out.println(distacne(str1, str2));
	}
}



之后会讲讲原理和推导,敬请期待


参考《GPS经纬度坐标转平面坐标的简化计算方法及精度分析》

参考《WGS84、北京54、高斯-克吕格坐标系转换算法》

参考《高斯-克吕格投影常系数和变系数正反解公式的讨论和应用》

你可能感兴趣的:(数据挖掘)