220 lines
6.5 KiB
C++
220 lines
6.5 KiB
C++
#include "CoordinateTransform.h"
|
||
#include <cmath>
|
||
|
||
// 静态辅助:预计算的子午线弧长系数
|
||
static inline double computeA0(double e2) {
|
||
return 1.0 + 3.0/4.0 * e2 + 45.0/64.0 * e2*e2 + 175.0/256.0 * e2*e2*e2
|
||
+ 11025.0/16384.0 * e2*e2*e2*e2;
|
||
}
|
||
static inline double computeA2(double e2) {
|
||
return 3.0/4.0 * e2 + 15.0/16.0 * e2*e2 + 525.0/512.0 * e2*e2*e2
|
||
+ 2205.0/2048.0 * e2*e2*e2*e2;
|
||
}
|
||
static inline double computeA4(double e2) {
|
||
return 15.0/64.0 * e2*e2 + 105.0/256.0 * e2*e2*e2
|
||
+ 2205.0/4096.0 * e2*e2*e2*e2;
|
||
}
|
||
static inline double computeA6(double e2) {
|
||
return 35.0/512.0 * e2*e2*e2 + 315.0/2048.0 * e2*e2*e2*e2;
|
||
}
|
||
static inline double computeA8(double e2) {
|
||
return 315.0/16384.0 * e2*e2*e2*e2;
|
||
}
|
||
|
||
int CoordinateTransform::detectZoneFromCgcsY(double cgcsY)
|
||
{
|
||
// 中国范围内 CGCS2000 坐标 Y 通常为正值且包含带号
|
||
// 格式:zone * 1,000,000 + 500,000 + easting
|
||
double absY = std::abs(cgcsY);
|
||
if (absY > 1e6) {
|
||
int zone = static_cast<int>(std::floor(absY / 1e6));
|
||
if (zone >= 1 && zone <= 60)
|
||
return zone;
|
||
}
|
||
// 如果无法推断,返回 0(表示需要手动指定)
|
||
return 0;
|
||
}
|
||
|
||
double CoordinateTransform::centralMeridianFromZone(int zone)
|
||
{
|
||
// 3度带:中央经线 = 带号 * 3°
|
||
return zone * 3.0;
|
||
}
|
||
|
||
double CoordinateTransform::meridianArc(double latRad)
|
||
{
|
||
const double e2 = E2;
|
||
const double a = A;
|
||
|
||
const double A0 = computeA0(e2);
|
||
const double A2 = computeA2(e2);
|
||
const double A4 = computeA4(e2);
|
||
const double A6 = computeA6(e2);
|
||
const double A8 = computeA8(e2);
|
||
|
||
double s2 = std::sin(2.0 * latRad);
|
||
double s4 = std::sin(4.0 * latRad);
|
||
double s6 = std::sin(6.0 * latRad);
|
||
double s8 = std::sin(8.0 * latRad);
|
||
|
||
return a * (1.0 - e2) * (A0 * latRad - A2/2.0 * s2 + A4/4.0 * s4
|
||
- A6/6.0 * s6 + A8/8.0 * s8);
|
||
}
|
||
|
||
double CoordinateTransform::meridianArcInverse(double x)
|
||
{
|
||
const double e2 = E2;
|
||
const double a = A;
|
||
|
||
const double A0 = computeA0(e2);
|
||
const double A2 = computeA2(e2);
|
||
const double A4 = computeA4(e2);
|
||
const double A6 = computeA6(e2);
|
||
const double A8 = computeA8(e2);
|
||
|
||
const double c0 = a * (1.0 - e2) * A0;
|
||
|
||
double bf = x / c0;
|
||
for (int i = 0; i < 8; ++i) {
|
||
double s2 = std::sin(2.0 * bf);
|
||
double s4 = std::sin(4.0 * bf);
|
||
double s6 = std::sin(6.0 * bf);
|
||
double s8 = std::sin(8.0 * bf);
|
||
|
||
double fx = a * (1.0 - e2) * (A0 * bf - A2/2.0 * s2 + A4/4.0 * s4
|
||
- A6/6.0 * s6 + A8/8.0 * s8) - x;
|
||
double fpx = a * (1.0 - e2) * (A0 - A2 * std::cos(2.0 * bf)
|
||
+ A4 * std::cos(4.0 * bf)
|
||
- A6 * std::cos(6.0 * bf)
|
||
+ A8 * std::cos(8.0 * bf));
|
||
|
||
double delta = fx / fpx;
|
||
bf -= delta;
|
||
if (std::abs(delta) < 1e-12)
|
||
break;
|
||
}
|
||
return bf;
|
||
}
|
||
|
||
void CoordinateTransform::cgcs2000ToWgs84(double cgcsX, double cgcsY, int zone,
|
||
double &latRad, double &lonRad)
|
||
{
|
||
const double a = A;
|
||
const double e2 = E2;
|
||
const double e12 = E12;
|
||
|
||
// 提取东偏移(去掉带号和 500km 常数)
|
||
double easting = cgcsY;
|
||
if (zone > 0) {
|
||
easting = cgcsY - zone * 1e6 - FALSE_EASTING;
|
||
} else {
|
||
easting = cgcsY - FALSE_EASTING;
|
||
}
|
||
double northing = cgcsX;
|
||
double l0 = centralMeridianFromZone(zone) * DEG2RAD;
|
||
|
||
// 底点纬度
|
||
double bf = meridianArcInverse(northing);
|
||
|
||
double sinBf = std::sin(bf);
|
||
double cosBf = std::cos(bf);
|
||
double tanBf = std::tan(bf);
|
||
|
||
double Nf = a / std::sqrt(1.0 - e2 * sinBf * sinBf);
|
||
double etaf2 = e12 * cosBf * cosBf;
|
||
double tf = tanBf;
|
||
double Vf2 = 1.0 + etaf2;
|
||
|
||
double y = easting;
|
||
double y2 = y * y;
|
||
double y4 = y2 * y2;
|
||
double y6 = y4 * y2;
|
||
|
||
double Nf2 = Nf * Nf;
|
||
double Nf4 = Nf2 * Nf2;
|
||
double Nf6 = Nf4 * Nf2;
|
||
|
||
// 纬度修正
|
||
double dB = tf / Vf2 * (
|
||
y2 / (2.0 * Nf2) * (1.0 + etaf2)
|
||
- y4 / (24.0 * Nf4) * (5.0 + 3.0 * tf*tf + 6.0 * etaf2
|
||
- 6.0 * tf*tf * etaf2
|
||
- 3.0 * etaf2*etaf2
|
||
- 9.0 * tf*tf * etaf2*etaf2)
|
||
+ y6 / (720.0 * Nf6) * (61.0 + 90.0 * tf*tf + 45.0 * tf*tf*tf*tf)
|
||
);
|
||
|
||
latRad = bf - dB;
|
||
|
||
// 经差
|
||
double dl = y / (Nf * cosBf) * (
|
||
1.0
|
||
- y2 / (6.0 * Nf2) * (1.0 + 2.0 * tf*tf + etaf2)
|
||
+ y4 / (120.0 * Nf4) * (5.0 + 28.0 * tf*tf + 24.0 * tf*tf*tf*tf
|
||
+ 6.0 * etaf2 + 8.0 * tf*tf * etaf2)
|
||
- y6 / (5040.0 * Nf6) * (61.0 + 662.0 * tf*tf + 1320.0 * tf*tf*tf*tf
|
||
+ 720.0 * tf*tf*tf*tf*tf*tf)
|
||
);
|
||
|
||
lonRad = l0 + dl;
|
||
}
|
||
|
||
void CoordinateTransform::wgs84ToCgcs2000(double latRad, double lonRad, int zone,
|
||
double &cgcsX, double &cgcsY)
|
||
{
|
||
const double a = A;
|
||
const double e2 = E2;
|
||
const double e12 = E12;
|
||
|
||
double l0 = centralMeridianFromZone(zone) * DEG2RAD;
|
||
double l = lonRad - l0;
|
||
|
||
double sinB = std::sin(latRad);
|
||
double cosB = std::cos(latRad);
|
||
double tanB = std::tan(latRad);
|
||
|
||
double N = a / std::sqrt(1.0 - e2 * sinB * sinB);
|
||
double t2 = tanB * tanB;
|
||
double t4 = t2 * t2;
|
||
double eta2 = e12 * cosB * cosB;
|
||
double eta4 = eta2 * eta2;
|
||
|
||
double l2 = l * l;
|
||
double l4 = l2 * l2;
|
||
double l6 = l4 * l2;
|
||
|
||
// 子午线弧长
|
||
double X = meridianArc(latRad);
|
||
|
||
// 北坐标
|
||
cgcsX = X + N / 2.0 * sinB * cosB * l2 * (
|
||
1.0 + l2 / 12.0 * (5.0 - t2 + 9.0 * eta2 + 4.0 * eta4)
|
||
+ l4 / 360.0 * (61.0 - 58.0 * t2 + t4)
|
||
);
|
||
|
||
// 东坐标(自然值,不含带号和 500km)
|
||
double yNatural = N * cosB * l * (
|
||
1.0 + l2 / 6.0 * (1.0 - t2 + eta2)
|
||
+ l4 / 120.0 * (5.0 - 18.0 * t2 + t4 + 14.0 * eta2 - 58.0 * t2 * eta2)
|
||
);
|
||
|
||
// 加上带号和 500km 常数
|
||
if (zone > 0) {
|
||
cgcsY = zone * 1e6 + FALSE_EASTING + yNatural;
|
||
} else {
|
||
cgcsY = FALSE_EASTING + yNatural;
|
||
}
|
||
}
|
||
|
||
void CoordinateTransform::wgs84ToWebMercator(double latRad, double lonRad, double &mx, double &my)
|
||
{
|
||
mx = EARTH_RADIUS * lonRad;
|
||
my = EARTH_RADIUS * std::log(std::tan(PI / 4.0 + latRad / 2.0));
|
||
}
|
||
|
||
void CoordinateTransform::webMercatorToWgs84(double mx, double my, double &latRad, double &lonRad)
|
||
{
|
||
lonRad = mx / EARTH_RADIUS;
|
||
latRad = 2.0 * std::atan(std::exp(my / EARTH_RADIUS)) - PI / 2.0;
|
||
}
|