|
|
@@ -0,0 +1,163 @@
|
|
|
+interface LngLat {
|
|
|
+ lng: number
|
|
|
+ lat: number
|
|
|
+}
|
|
|
+
|
|
|
+const PI = 3.14159265358979324
|
|
|
+const X_PI = (PI * 3000.0) / 180.0
|
|
|
+const A = 6378245.0
|
|
|
+const EE = 0.00669342162296594323
|
|
|
+
|
|
|
+const LLBAND = [75, 60, 45, 30, 15, 0] as const
|
|
|
+const LL2MC: readonly [number, number, number, number, number, number, number, number, number, number][] = [
|
|
|
+ [-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340, 26112667856603880, -35149669176653700, 26595700718403920, -10725012454188240, 1800819912950474, 82.5],
|
|
|
+ [0.0008277824516172526, 111320.7020463578, 647795574.6671607, -4082003173.641316, 10774905663.51142, -15171875531.51559, 12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5],
|
|
|
+ [0.00337398766765, 111320.7020202162, 4481351.045890365, -23393751.19931662, 79682215.47186455, -115964993.2797253, 97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5],
|
|
|
+ [0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245, 992013.7397791013, -1221952.21711287, 1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5],
|
|
|
+ [-0.0003441963504368392, 111320.7020576856, 278.2353980772752, 2485758.690035394, 6070.750963243378, 54821.18345352118, 9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5],
|
|
|
+ [-0.0003218135878613132, 111320.7020701615, 0.00369383431289, 823725.6402795718, 0.46104986909093, 2351.343141331292, 1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45],
|
|
|
+]
|
|
|
+
|
|
|
+const MCBAND = [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0]
|
|
|
+const MC2LL: readonly [number, number, number, number, number, number, number, number, number, number][] = [
|
|
|
+ [1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331, 200.9824383106796, -187.2403703815547, 91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2],
|
|
|
+ [-7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289, 96.32687599759846, -1.85204757529826, -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86],
|
|
|
+ [-3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616, 59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37],
|
|
|
+ [-1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591, 40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06],
|
|
|
+ [3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062, 23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4],
|
|
|
+ [2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8, 7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5],
|
|
|
+]
|
|
|
+
|
|
|
+function getRange(cC: number, cB: number | null, T: number | null): number {
|
|
|
+ if (cB != null) cC = Math.max(cC, cB)
|
|
|
+ if (T != null) cC = Math.min(cC, T)
|
|
|
+ return cC
|
|
|
+}
|
|
|
+
|
|
|
+function getLoop(cC: number, cB: number, T: number): number {
|
|
|
+ while (cC > T) cC -= T - cB
|
|
|
+ while (cC < cB) cC += T - cB
|
|
|
+ return cC
|
|
|
+}
|
|
|
+
|
|
|
+function convertor(cC: { x: number; y: number }, cD: readonly number[]): [number, number] | null {
|
|
|
+ if (!cC || !cD) return null
|
|
|
+ let T = cD[0] + cD[1] * Math.abs(cC.x)
|
|
|
+ const cB = Math.abs(cC.y) / cD[9]
|
|
|
+ let cE = cD[2] + cD[3] * cB + cD[4] * cB * cB + cD[5] * cB * cB * cB + cD[6] * cB * cB * cB * cB + cD[7] * cB * cB * cB * cB * cB + cD[8] * cB * cB * cB * cB * cB * cB
|
|
|
+ T *= cC.x < 0 ? -1 : 1
|
|
|
+ cE *= cC.y < 0 ? -1 : 1
|
|
|
+ return [T, cE]
|
|
|
+}
|
|
|
+
|
|
|
+function isOutOfChina(lng: number, lat: number): boolean {
|
|
|
+ return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271
|
|
|
+}
|
|
|
+
|
|
|
+function transformLat(x: number, y: number): number {
|
|
|
+ let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x))
|
|
|
+ ret += ((20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0) / 3.0
|
|
|
+ ret += ((20.0 * Math.sin(y * PI) + 40.0 * Math.sin((y / 3.0) * PI)) * 2.0) / 3.0
|
|
|
+ ret += ((160.0 * Math.sin((y / 12.0) * PI) + 320 * Math.sin((y * PI) / 30.0)) * 2.0) / 3.0
|
|
|
+ return ret
|
|
|
+}
|
|
|
+
|
|
|
+function transformLng(x: number, y: number): number {
|
|
|
+ let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x))
|
|
|
+ ret += ((20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0) / 3.0
|
|
|
+ ret += ((20.0 * Math.sin(x * PI) + 40.0 * Math.sin((x / 3.0) * PI)) * 2.0) / 3.0
|
|
|
+ ret += ((150.0 * Math.sin((x / 12.0) * PI) + 300.0 * Math.sin((x / 30.0) * PI)) * 2.0) / 3.0
|
|
|
+ return ret
|
|
|
+}
|
|
|
+
|
|
|
+function delta(lat: number, lng: number): { lat: number; lng: number } {
|
|
|
+ const dLat = transformLat(lng - 105.0, lat - 35.0)
|
|
|
+ const dLng = transformLng(lng - 105.0, lat - 35.0)
|
|
|
+ const radLat = (lat / 180.0) * PI
|
|
|
+ let magic = Math.sin(radLat)
|
|
|
+ magic = 1 - EE * magic * magic
|
|
|
+ const sqrtMagic = Math.sqrt(magic)
|
|
|
+ return {
|
|
|
+ lat: (dLat * 180.0) / (((A * (1 - EE)) / (magic * sqrtMagic)) * PI),
|
|
|
+ lng: (dLng * 180.0) / ((A / sqrtMagic) * Math.cos(radLat) * PI),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 百度墨卡托坐标 → 百度经纬度坐标 */
|
|
|
+export function convertBdMC2LL(lnglat: LngLat): LngLat {
|
|
|
+ const cC = { x: Math.abs(lnglat.lng), y: Math.abs(lnglat.lat) }
|
|
|
+ let cE = MC2LL[0]
|
|
|
+ for (let cD = 0; cD < MCBAND.length; cD++) {
|
|
|
+ if (cC.y >= MCBAND[cD]) {
|
|
|
+ cE = MC2LL[cD]
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const [lng, lat] = convertor({ x: lnglat.lng, y: lnglat.lat }, cE)!
|
|
|
+ return { lng, lat }
|
|
|
+}
|
|
|
+
|
|
|
+/** 百度BD09经纬度坐标 → 百度墨卡托坐标 */
|
|
|
+export function convertBdLL2MC(lnglat: LngLat): LngLat {
|
|
|
+ const T = { x: getLoop(lnglat.lng, -180, 180), y: getRange(lnglat.lat, -74, 74) }
|
|
|
+ let cD: readonly number[] | undefined
|
|
|
+ for (let cC = 0; cC < LLBAND.length; cC++) {
|
|
|
+ if (T.y >= LLBAND[cC]) {
|
|
|
+ cD = LL2MC[cC]
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!cD) {
|
|
|
+ for (let cC = LLBAND.length - 1; cC >= 0; cC--) {
|
|
|
+ if (T.y <= -LLBAND[cC]) {
|
|
|
+ cD = LL2MC[cC]
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const [lng, lat] = convertor(T, cD!)!
|
|
|
+ return { lng, lat }
|
|
|
+}
|
|
|
+
|
|
|
+/** WGS-84 → GCJ-02(火星坐标系) */
|
|
|
+export function wgs84ToGcj02(lnglat: LngLat): LngLat {
|
|
|
+ const { lat: wgLat, lng: wgLng } = lnglat
|
|
|
+ if (isOutOfChina(wgLng, wgLat)) {
|
|
|
+ return { lng: wgLng, lat: wgLat }
|
|
|
+ }
|
|
|
+ const dLat = transformLat(wgLng - 105.0, wgLat - 35.0)
|
|
|
+ const dLng = transformLng(wgLng - 105.0, wgLat - 35.0)
|
|
|
+ const radLat = (wgLat / 180.0) * PI
|
|
|
+ let magic = Math.sin(radLat)
|
|
|
+ magic = 1 - EE * magic * magic
|
|
|
+ const sqrtMagic = Math.sqrt(magic)
|
|
|
+ return {
|
|
|
+ lat: wgLat + (dLat * 180.0) / (((A * (1 - EE)) / (magic * sqrtMagic)) * PI),
|
|
|
+ lng: wgLng + (dLng * 180.0) / ((A / sqrtMagic) * Math.cos(radLat) * PI),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** GCJ-02(火星坐标系) → WGS-84 */
|
|
|
+export function gcj02ToWgs84(lnglat: LngLat): LngLat {
|
|
|
+ const { lat: gcjLat, lng: gcjLng } = lnglat
|
|
|
+ const d = delta(gcjLat, gcjLng)
|
|
|
+ return { lat: gcjLat - d.lat, lng: gcjLng - d.lng }
|
|
|
+}
|
|
|
+
|
|
|
+/** BD-09(百度坐标) → GCJ-02(火星坐标) */
|
|
|
+export function bd09ToGcj02(lnglat: LngLat): LngLat {
|
|
|
+ const x = lnglat.lng - 0.0065
|
|
|
+ const y = lnglat.lat - 0.006
|
|
|
+ const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI)
|
|
|
+ const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI)
|
|
|
+ return { lng: z * Math.cos(theta), lat: z * Math.sin(theta) }
|
|
|
+}
|
|
|
+
|
|
|
+/** GCJ-02(火星坐标) → BD-09(百度坐标) */
|
|
|
+export function gcj02ToBd09(lnglat: LngLat): LngLat {
|
|
|
+ const x = lnglat.lng
|
|
|
+ const y = lnglat.lat
|
|
|
+ const z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * X_PI)
|
|
|
+ const theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * X_PI)
|
|
|
+ return { lng: z * Math.cos(theta) + 0.0065, lat: z * Math.sin(theta) + 0.006 }
|
|
|
+}
|