projection.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. export interface LonLatPoint {
  2. lon: number
  3. lat: number
  4. }
  5. export interface WebMercatorPoint {
  6. x: number
  7. y: number
  8. }
  9. export interface WorldTilePoint {
  10. x: number
  11. y: number
  12. }
  13. export interface MapCalibration {
  14. offsetEastMeters: number
  15. offsetNorthMeters: number
  16. rotationDeg: number
  17. scale: number
  18. }
  19. const MAX_LATITUDE = 85.05112878
  20. const EARTH_RADIUS = 6378137
  21. function clampLatitude(lat: number): number {
  22. return Math.max(-MAX_LATITUDE, Math.min(MAX_LATITUDE, lat))
  23. }
  24. export function lonLatToWebMercator(point: LonLatPoint): WebMercatorPoint {
  25. const latitude = clampLatitude(point.lat)
  26. const lonRad = point.lon * Math.PI / 180
  27. const latRad = latitude * Math.PI / 180
  28. return {
  29. x: EARTH_RADIUS * lonRad,
  30. y: EARTH_RADIUS * Math.log(Math.tan(Math.PI / 4 + latRad / 2)),
  31. }
  32. }
  33. export function webMercatorToLonLat(point: WebMercatorPoint): LonLatPoint {
  34. return {
  35. lon: point.x / EARTH_RADIUS * 180 / Math.PI,
  36. lat: (2 * Math.atan(Math.exp(point.y / EARTH_RADIUS)) - Math.PI / 2) * 180 / Math.PI,
  37. }
  38. }
  39. export function lonLatToWorldTile(point: LonLatPoint, zoom: number): WorldTilePoint {
  40. const latitude = clampLatitude(point.lat)
  41. const scale = Math.pow(2, zoom)
  42. const latRad = latitude * Math.PI / 180
  43. return {
  44. x: (point.lon + 180) / 360 * scale,
  45. y: (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * scale,
  46. }
  47. }
  48. export function worldTileToLonLat(point: WorldTilePoint, zoom: number): LonLatPoint {
  49. const scale = Math.pow(2, zoom)
  50. const lon = point.x / scale * 360 - 180
  51. const latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * point.y / scale)))
  52. return {
  53. lon,
  54. lat: latRad * 180 / Math.PI,
  55. }
  56. }
  57. export function applyMapCalibration(point: LonLatPoint, calibration: MapCalibration, origin: LonLatPoint): LonLatPoint {
  58. const scale = calibration.scale || 1
  59. const rotationDeg = calibration.rotationDeg || 0
  60. const offsetEastMeters = calibration.offsetEastMeters || 0
  61. const offsetNorthMeters = calibration.offsetNorthMeters || 0
  62. if (
  63. Math.abs(scale - 1) < 0.000001
  64. && Math.abs(rotationDeg) < 0.000001
  65. && Math.abs(offsetEastMeters) < 0.000001
  66. && Math.abs(offsetNorthMeters) < 0.000001
  67. ) {
  68. return point
  69. }
  70. const originMercator = lonLatToWebMercator(origin)
  71. const pointMercator = lonLatToWebMercator(point)
  72. const deltaX = pointMercator.x - originMercator.x
  73. const deltaY = pointMercator.y - originMercator.y
  74. const scaledX = deltaX * scale
  75. const scaledY = deltaY * scale
  76. const rotationRad = rotationDeg * Math.PI / 180
  77. const cos = Math.cos(rotationRad)
  78. const sin = Math.sin(rotationRad)
  79. const calibratedMercator: WebMercatorPoint = {
  80. x: originMercator.x + scaledX * cos - scaledY * sin + offsetEastMeters,
  81. y: originMercator.y + scaledX * sin + scaledY * cos + offsetNorthMeters,
  82. }
  83. return webMercatorToLonLat(calibratedMercator)
  84. }
  85. export function calibratedLonLatToWorldTile(point: LonLatPoint, zoom: number, calibration: MapCalibration, origin: LonLatPoint): WorldTilePoint {
  86. return lonLatToWorldTile(applyMapCalibration(point, calibration, origin), zoom)
  87. }
  88. const CHINA_AXIS = 6378245
  89. const CHINA_EE = 0.00669342162296594323
  90. function isOutsideChina(point: LonLatPoint): boolean {
  91. return point.lon < 72.004 || point.lon > 137.8347 || point.lat < 0.8293 || point.lat > 55.8271
  92. }
  93. function transformLat(x: number, y: number): number {
  94. let result = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x))
  95. result += (20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2 / 3
  96. result += (20 * Math.sin(y * Math.PI) + 40 * Math.sin(y / 3 * Math.PI)) * 2 / 3
  97. result += (160 * Math.sin(y / 12 * Math.PI) + 320 * Math.sin(y * Math.PI / 30)) * 2 / 3
  98. return result
  99. }
  100. function transformLon(x: number, y: number): number {
  101. let result = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x))
  102. result += (20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2 / 3
  103. result += (20 * Math.sin(x * Math.PI) + 40 * Math.sin(x / 3 * Math.PI)) * 2 / 3
  104. result += (150 * Math.sin(x / 12 * Math.PI) + 300 * Math.sin(x / 30 * Math.PI)) * 2 / 3
  105. return result
  106. }
  107. export function gcj02ToWgs84(point: LonLatPoint): LonLatPoint {
  108. if (isOutsideChina(point)) {
  109. return point
  110. }
  111. const dLat = transformLat(point.lon - 105, point.lat - 35)
  112. const dLon = transformLon(point.lon - 105, point.lat - 35)
  113. const radLat = point.lat / 180 * Math.PI
  114. const magic = Math.sin(radLat)
  115. const sqrtMagic = Math.sqrt(1 - CHINA_EE * magic * magic)
  116. const latOffset = (dLat * 180) / ((CHINA_AXIS * (1 - CHINA_EE)) / (sqrtMagic * sqrtMagic * sqrtMagic) * Math.PI)
  117. const lonOffset = (dLon * 180) / (CHINA_AXIS / sqrtMagic * Math.cos(radLat) * Math.PI)
  118. return {
  119. lon: point.lon - lonOffset,
  120. lat: point.lat - latOffset,
  121. }
  122. }