diff --git a/miniprogram/engine/layer/gpsLayer.ts b/miniprogram/engine/layer/gpsLayer.ts index 5c78d71..c0e8e88 100644 --- a/miniprogram/engine/layer/gpsLayer.ts +++ b/miniprogram/engine/layer/gpsLayer.ts @@ -1,18 +1,25 @@ -import { type CameraState } from '../camera/camera' -import { lonLatToWorldTile } from '../../utils/projection' -import { worldToScreen } from '../camera/camera' +import { calibratedLonLatToWorldTile } from '../../utils/projection' +import { worldToScreen, type CameraState } from '../camera/camera' import { type MapLayer, type LayerRenderContext } from './mapLayer' import { type MapScene } from '../renderer/mapRenderer' import { type ScreenPoint } from './trackLayer' +function buildVectorCamera(scene: MapScene): CameraState { + return { + centerWorldX: scene.exactCenterWorldX, + centerWorldY: scene.exactCenterWorldY, + viewportWidth: scene.viewportWidth, + viewportHeight: scene.viewportHeight, + visibleColumns: scene.visibleColumns, + rotationRad: scene.rotationRad, + } +} + export class GpsLayer implements MapLayer { - projectPoint(scene: MapScene, camera: CameraState): ScreenPoint { - const worldPoint = lonLatToWorldTile(scene.gpsPoint, scene.zoom) - const screenPoint = worldToScreen(camera, worldPoint, false) - return { - x: screenPoint.x + scene.translateX, - y: screenPoint.y + scene.translateY, - } + projectPoint(scene: MapScene): ScreenPoint { + const camera = buildVectorCamera(scene) + const worldPoint = calibratedLonLatToWorldTile(scene.gpsPoint, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin) + return worldToScreen(camera, worldPoint, false) } getPulseRadius(pulseFrame: number): number { @@ -20,8 +27,8 @@ export class GpsLayer implements MapLayer { } draw(context: LayerRenderContext): void { - const { ctx, camera, scene, pulseFrame } = context - const gpsScreenPoint = this.projectPoint(scene, camera) + const { ctx, scene, pulseFrame } = context + const gpsScreenPoint = this.projectPoint(scene) const pulse = this.getPulseRadius(pulseFrame) ctx.save() diff --git a/miniprogram/engine/layer/trackLayer.ts b/miniprogram/engine/layer/trackLayer.ts index fcb58ca..307a2c6 100644 --- a/miniprogram/engine/layer/trackLayer.ts +++ b/miniprogram/engine/layer/trackLayer.ts @@ -1,5 +1,5 @@ import { type CameraState } from '../camera/camera' -import { lonLatToWorldTile } from '../../utils/projection' +import { calibratedLonLatToWorldTile } from '../../utils/projection' import { worldToScreen } from '../camera/camera' import { type MapLayer, type LayerRenderContext } from './mapLayer' import { type MapScene } from '../renderer/mapRenderer' @@ -9,21 +9,29 @@ export interface ScreenPoint { y: number } +function buildVectorCamera(scene: MapScene): CameraState { + return { + centerWorldX: scene.exactCenterWorldX, + centerWorldY: scene.exactCenterWorldY, + viewportWidth: scene.viewportWidth, + viewportHeight: scene.viewportHeight, + visibleColumns: scene.visibleColumns, + rotationRad: scene.rotationRad, + } +} + export class TrackLayer implements MapLayer { - projectPoints(scene: MapScene, camera: CameraState): ScreenPoint[] { + projectPoints(scene: MapScene): ScreenPoint[] { + const camera = buildVectorCamera(scene) return scene.track.map((point) => { - const worldPoint = lonLatToWorldTile(point, scene.zoom) - const screenPoint = worldToScreen(camera, worldPoint, false) - return { - x: screenPoint.x + scene.translateX, - y: screenPoint.y + scene.translateY, - } + const worldPoint = calibratedLonLatToWorldTile(point, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin) + return worldToScreen(camera, worldPoint, false) }) } draw(context: LayerRenderContext): void { - const { ctx, camera, scene } = context - const points = this.projectPoints(scene, camera) + const { ctx, scene } = context + const points = this.projectPoints(scene) ctx.save() ctx.lineCap = 'round' diff --git a/miniprogram/engine/map/mapEngine.ts b/miniprogram/engine/map/mapEngine.ts index 91256af..04dde4c 100644 --- a/miniprogram/engine/map/mapEngine.ts +++ b/miniprogram/engine/map/mapEngine.ts @@ -3,7 +3,7 @@ import { CompassHeadingController } from '../sensor/compassHeadingController' import { LocationController } from '../sensor/locationController' import { WebGLMapRenderer } from '../renderer/webglMapRenderer' import { type MapRendererStats } from '../renderer/mapRenderer' -import { gcj02ToWgs84, lonLatToWorldTile, worldTileToLonLat, type LonLatPoint } from '../../utils/projection' +import { lonLatToWorldTile, worldTileToLonLat, type LonLatPoint, type MapCalibration } from '../../utils/projection' import { isTileWithinBounds, type RemoteMapConfig, type TileZoomBounds } from '../../utils/remoteMapConfig' const RENDER_MODE = 'Single WebGL Pipeline' @@ -21,6 +21,14 @@ const DEFAULT_TOP_LEFT_TILE_Y = 51199 const DEFAULT_CENTER_TILE_X = DEFAULT_TOP_LEFT_TILE_X + 1 const DEFAULT_CENTER_TILE_Y = DEFAULT_TOP_LEFT_TILE_Y + 1 const TILE_SOURCE = 'https://oss-mbh5.colormaprun.com/wxMap/lcx/{z}/{x}/{y}.png' +const OSM_TILE_SOURCE = 'https://tiles.mymarsgo.xyz/{z}/{x}/{y}.png' +const MAP_OVERLAY_OPACITY = 0.72 +const GPS_MAP_CALIBRATION: MapCalibration = { + offsetEastMeters: 0, + offsetNorthMeters: 0, + rotationDeg: 0, + scale: 1, +} const MIN_PREVIEW_SCALE = 0.55 const MAX_PREVIEW_SCALE = 1.85 const INERTIA_FRAME_MS = 16 @@ -114,6 +122,8 @@ export interface MapEngineViewState { gpsTracking: boolean gpsTrackingText: string gpsCoordText: string + osmReferenceEnabled: boolean + osmReferenceText: string } export interface MapEngineCallbacks { @@ -158,6 +168,8 @@ const VIEW_SYNC_KEYS: Array = [ 'gpsTracking', 'gpsTrackingText', 'gpsCoordText', + 'osmReferenceEnabled', + 'osmReferenceText', ] function buildCenterText(zoom: number, x: number, y: number): string { @@ -524,6 +536,8 @@ export class MapEngine { gpsTracking: false, gpsTrackingText: '持续定位待启动', gpsCoordText: '--', + osmReferenceEnabled: false, + osmReferenceText: 'OSM参考:关', } this.previewScale = 1 this.previewOriginX = 0 @@ -575,7 +589,7 @@ export class MapEngine { handleLocationUpdate(longitude: number, latitude: number, accuracyMeters: number | null): void { - const nextPoint: LonLatPoint = gcj02ToWgs84({ lon: longitude, lat: latitude }) + const nextPoint: LonLatPoint = { lon: longitude, lat: latitude } const lastTrackPoint = this.currentGpsTrack.length ? this.currentGpsTrack[this.currentGpsTrack.length - 1] : null if (!lastTrackPoint || getApproxDistanceMeters(lastTrackPoint, nextPoint) >= GPS_TRACK_MIN_STEP_METERS) { this.currentGpsTrack = [...this.currentGpsTrack, nextPoint].slice(-GPS_TRACK_MAX_POINTS) @@ -612,6 +626,16 @@ export class MapEngine { this.syncRenderer() } + handleToggleOsmReference(): void { + const nextEnabled = !this.state.osmReferenceEnabled + this.setState({ + osmReferenceEnabled: nextEnabled, + osmReferenceText: nextEnabled ? 'OSM参考:开' : 'OSM参考:关', + statusText: nextEnabled ? `OSM参考底图已开启 (${this.buildVersion})` : `OSM参考底图已关闭 (${this.buildVersion})`, + }, true) + this.syncRenderer() + } + handleToggleGpsTracking(): void { if (this.locationController.listening) { this.locationController.stop() @@ -1364,11 +1388,15 @@ export class MapEngine { } buildScene() { + const exactCenter = this.getExactCenterFromTranslate(this.state.tileTranslateX, this.state.tileTranslateY) return { tileSource: this.state.tileSource, + osmTileSource: OSM_TILE_SOURCE, zoom: this.state.zoom, centerTileX: this.state.centerTileX, centerTileY: this.state.centerTileY, + exactCenterWorldX: exactCenter.x, + exactCenterWorldY: exactCenter.y, tileBoundsByZoom: this.tileBoundsByZoom, viewportWidth: this.state.stageWidth, viewportHeight: this.state.stageHeight, @@ -1382,6 +1410,10 @@ export class MapEngine { previewOriginY: this.previewOriginY || this.state.stageHeight / 2, track: this.currentGpsTrack.length ? this.currentGpsTrack : SAMPLE_TRACK_WGS84, gpsPoint: this.currentGpsPoint, + gpsCalibration: GPS_MAP_CALIBRATION, + gpsCalibrationOrigin: worldTileToLonLat({ x: this.defaultCenterTileX, y: this.defaultCenterTileY }, this.defaultZoom), + osmReferenceEnabled: this.state.osmReferenceEnabled, + overlayOpacity: MAP_OVERLAY_OPACITY, } } @@ -1395,8 +1427,8 @@ export class MapEngine { getCameraState(rotationDeg = this.state.rotationDeg): CameraState { return { - centerWorldX: this.state.centerTileX, - centerWorldY: this.state.centerTileY, + centerWorldX: this.state.centerTileX + 0.5, + centerWorldY: this.state.centerTileY + 0.5, viewportWidth: this.state.stageWidth, viewportHeight: this.state.stageHeight, visibleColumns: DESIRED_VISIBLE_COLUMNS, @@ -1410,10 +1442,10 @@ export class MapEngine { return normalizeRotationDeg(rotationDeg) * Math.PI / 180 } - getBaseCamera(centerWorldX = this.state.centerTileX, centerWorldY = this.state.centerTileY, rotationDeg = this.state.rotationDeg): CameraState { + getBaseCamera(centerTileX = this.state.centerTileX, centerTileY = this.state.centerTileY, rotationDeg = this.state.rotationDeg): CameraState { return { - centerWorldX, - centerWorldY, + centerWorldX: centerTileX + 0.5, + centerWorldY: centerTileY + 0.5, viewportWidth: this.state.stageWidth, viewportHeight: this.state.stageHeight, visibleColumns: DESIRED_VISIBLE_COLUMNS, @@ -1437,8 +1469,8 @@ export class MapEngine { getExactCenterFromTranslate(translateX: number, translateY: number): { x: number; y: number } { if (!this.state.stageWidth || !this.state.stageHeight) { return { - x: this.state.centerTileX, - y: this.state.centerTileY, + x: this.state.centerTileX + 0.5, + y: this.state.centerTileY + 0.5, } } @@ -1456,8 +1488,8 @@ export class MapEngine { tileTranslateX: number tileTranslateY: number } { - const nextCenterTileX = Math.round(centerWorldX) - const nextCenterTileY = Math.round(centerWorldY) + const nextCenterTileX = Math.floor(centerWorldX) + const nextCenterTileY = Math.floor(centerWorldY) if (!this.state.stageWidth || !this.state.stageHeight) { return { @@ -1763,6 +1795,13 @@ export class MapEngine { + + + + + + + diff --git a/miniprogram/engine/renderer/mapRenderer.ts b/miniprogram/engine/renderer/mapRenderer.ts index fba7a9c..4b84a18 100644 --- a/miniprogram/engine/renderer/mapRenderer.ts +++ b/miniprogram/engine/renderer/mapRenderer.ts @@ -1,13 +1,16 @@ import { type CameraState } from '../camera/camera' import { type TileStoreStats } from '../tile/tileStore' -import { type LonLatPoint } from '../../utils/projection' +import { type LonLatPoint, type MapCalibration } from '../../utils/projection' import { type TileZoomBounds } from '../../utils/remoteMapConfig' export interface MapScene { tileSource: string + osmTileSource: string zoom: number centerTileX: number centerTileY: number + exactCenterWorldX: number + exactCenterWorldY: number tileBoundsByZoom: Record | null viewportWidth: number viewportHeight: number @@ -21,6 +24,10 @@ export interface MapScene { previewOriginY: number track: LonLatPoint[] gpsPoint: LonLatPoint + gpsCalibration: MapCalibration + gpsCalibrationOrigin: LonLatPoint + osmReferenceEnabled: boolean + overlayOpacity: number } export type MapRendererStats = TileStoreStats @@ -34,8 +41,8 @@ export interface MapRenderer { export function buildCamera(scene: MapScene): CameraState { return { - centerWorldX: scene.centerTileX, - centerWorldY: scene.centerTileY, + centerWorldX: scene.centerTileX + 0.5, + centerWorldY: scene.centerTileY + 0.5, viewportWidth: scene.viewportWidth, viewportHeight: scene.viewportHeight, visibleColumns: scene.visibleColumns, @@ -44,5 +51,3 @@ export function buildCamera(scene: MapScene): CameraState { rotationRad: scene.rotationRad, } } - - diff --git a/miniprogram/engine/renderer/webglMapRenderer.ts b/miniprogram/engine/renderer/webglMapRenderer.ts index c21898a..1bc6d4b 100644 --- a/miniprogram/engine/renderer/webglMapRenderer.ts +++ b/miniprogram/engine/renderer/webglMapRenderer.ts @@ -11,7 +11,9 @@ const ANIMATION_FRAME_MS = 33 export class WebGLMapRenderer implements MapRenderer { tileStore: TileStore + osmTileStore: TileStore tileLayer: TileLayer + osmTileLayer: TileLayer trackLayer: TrackLayer gpsLayer: GpsLayer tileRenderer: WebGLTileRenderer @@ -40,10 +42,19 @@ export class WebGLMapRenderer implements MapRenderer { this.scheduleRender() }, } satisfies TileStoreCallbacks) + this.osmTileStore = new TileStore({ + onTileReady: () => { + this.scheduleRender() + }, + onTileError: () => { + this.scheduleRender() + }, + } satisfies TileStoreCallbacks) this.tileLayer = new TileLayer() + this.osmTileLayer = new TileLayer() this.trackLayer = new TrackLayer() this.gpsLayer = new GpsLayer() - this.tileRenderer = new WebGLTileRenderer(this.tileLayer, this.tileStore) + this.tileRenderer = new WebGLTileRenderer(this.tileLayer, this.tileStore, this.osmTileLayer, this.osmTileStore) this.vectorRenderer = new WebGLVectorRenderer(this.trackLayer, this.gpsLayer) this.scene = null this.renderTimer = 0 @@ -94,6 +105,7 @@ export class WebGLMapRenderer implements MapRenderer { this.vectorRenderer.destroy() this.tileRenderer.destroy() this.tileStore.destroy() + this.osmTileStore.destroy() this.scene = null } diff --git a/miniprogram/engine/renderer/webglTileRenderer.ts b/miniprogram/engine/renderer/webglTileRenderer.ts index b40875c..82c126e 100644 --- a/miniprogram/engine/renderer/webglTileRenderer.ts +++ b/miniprogram/engine/renderer/webglTileRenderer.ts @@ -53,25 +53,31 @@ export class WebGLTileRenderer { gl: any tileLayer: TileLayer tileStore: TileStore + osmTileLayer: TileLayer + osmTileStore: TileStore dpr: number program: any positionBuffer: any texCoordBuffer: any positionLocation: number texCoordLocation: number + opacityLocation: any textureCache: Map - constructor(tileLayer: TileLayer, tileStore: TileStore) { + constructor(tileLayer: TileLayer, tileStore: TileStore, osmTileLayer: TileLayer, osmTileStore: TileStore) { this.canvas = null this.gl = null this.tileLayer = tileLayer this.tileStore = tileStore + this.osmTileLayer = osmTileLayer + this.osmTileStore = osmTileStore this.dpr = 1 this.program = null this.positionBuffer = null this.texCoordBuffer = null this.positionLocation = -1 this.texCoordLocation = -1 + this.opacityLocation = null this.textureCache = new Map() } @@ -90,12 +96,13 @@ export class WebGLTileRenderer { this.program = createProgram( gl, 'attribute vec2 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_texCoord; }', - 'precision mediump float; varying vec2 v_texCoord; uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, v_texCoord); }', + 'precision mediump float; varying vec2 v_texCoord; uniform sampler2D u_texture; uniform float u_opacity; void main() { vec4 color = texture2D(u_texture, v_texCoord); gl_FragColor = vec4(color.rgb, color.a * u_opacity); }', ) this.positionBuffer = gl.createBuffer() this.texCoordBuffer = gl.createBuffer() this.positionLocation = gl.getAttribLocation(this.program, 'a_position') this.texCoordLocation = gl.getAttribLocation(this.program, 'a_texCoord') + this.opacityLocation = gl.getUniformLocation(this.program, 'u_opacity') gl.viewport(0, 0, canvasNode.width, canvasNode.height) gl.disable(gl.DEPTH_TEST) @@ -103,6 +110,7 @@ export class WebGLTileRenderer { gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) this.tileStore.attachCanvas(canvasNode) + this.osmTileStore.attachCanvas(canvasNode) } destroy(): void { @@ -135,24 +143,46 @@ export class WebGLTileRenderer { } const gl = this.gl - const camera = buildCamera(scene) - const tiles = this.tileLayer.prepareTiles(scene, camera, this.tileStore) - gl.viewport(0, 0, this.canvas.width, this.canvas.height) gl.clearColor(0.8588, 0.9333, 0.8314, 1) gl.clear(gl.COLOR_BUFFER_BIT) gl.useProgram(this.program) + if (scene.osmReferenceEnabled) { + this.renderTilePass( + { + ...scene, + tileSource: scene.osmTileSource, + tileBoundsByZoom: null, + }, + 1, + this.osmTileLayer, + this.osmTileStore, + ) + } + + this.renderTilePass( + scene, + scene.osmReferenceEnabled ? scene.overlayOpacity : 1, + this.tileLayer, + this.tileStore, + ) + } + + renderTilePass(scene: MapScene, opacity: number, tileLayer: TileLayer, tileStore: TileStore): void { + const camera = buildCamera(scene) + const tiles = tileLayer.prepareTiles(scene, camera, tileStore) + for (const tile of tiles) { - const readyEntry = this.tileStore.getEntry(tile.url) + const readyEntry = tileStore.getEntry(tile.url) if (readyEntry && readyEntry.status === 'ready' && readyEntry.image) { - this.drawEntry(readyEntry, tile.url, 0, 0, readyEntry.image.width || 256, readyEntry.image.height || 256, tile.leftPx, tile.topPx, tile.sizePx, tile.sizePx, scene) - this.tileLayer.lastReadyTileCount += 1 + this.drawEntry(readyEntry, tile.url, 0, 0, readyEntry.image.width || 256, readyEntry.image.height || 256, tile.leftPx, tile.topPx, tile.sizePx, tile.sizePx, scene, opacity) + tileLayer.lastReadyTileCount += 1 continue } - const parentFallback = this.tileStore.getParentFallbackSlice(tile, scene) + const parentFallback = tileStore.getParentFallbackSlice(tile, scene) if (parentFallback) { this.drawEntry( parentFallback.entry, @@ -166,10 +196,11 @@ export class WebGLTileRenderer { tile.sizePx, tile.sizePx, scene, + opacity, ) } - const childFallback = this.tileStore.getChildFallback(tile, scene) + const childFallback = tileStore.getChildFallback(tile, scene) if (!childFallback) { continue } @@ -189,6 +220,7 @@ export class WebGLTileRenderer { cellWidth, cellHeight, scene, + opacity, ) } } @@ -206,6 +238,7 @@ export class WebGLTileRenderer { drawWidth: number, drawHeight: number, scene: MapScene, + opacity: number, ): void { if (!this.gl || !entry.image) { return @@ -246,6 +279,7 @@ export class WebGLTileRenderer { texRight, texBottom, ]) + gl.uniform1f(this.opacityLocation, opacity) gl.bindTexture(gl.TEXTURE_2D, texture.texture) gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer) gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STREAM_DRAW) diff --git a/miniprogram/engine/renderer/webglVectorRenderer.ts b/miniprogram/engine/renderer/webglVectorRenderer.ts index 7e7f50e..c422633 100644 --- a/miniprogram/engine/renderer/webglVectorRenderer.ts +++ b/miniprogram/engine/renderer/webglVectorRenderer.ts @@ -1,4 +1,4 @@ -import { buildCamera, type MapScene } from './mapRenderer' +import { type MapScene } from './mapRenderer' import { TrackLayer } from '../layer/trackLayer' import { GpsLayer } from '../layer/gpsLayer' @@ -123,9 +123,8 @@ export class WebGLVectorRenderer { } const gl = this.gl - const camera = buildCamera(scene) - const trackPoints = this.trackLayer.projectPoints(scene, camera) - const gpsPoint = this.gpsLayer.projectPoint(scene, camera) + const trackPoints = this.trackLayer.projectPoints(scene) + const gpsPoint = this.gpsLayer.projectPoint(scene) const positions: number[] = [] const colors: number[] = [] @@ -232,3 +231,4 @@ export class WebGLVectorRenderer { } } } + diff --git a/miniprogram/engine/sensor/locationController.ts b/miniprogram/engine/sensor/locationController.ts index 0742c41..c17ee92 100644 --- a/miniprogram/engine/sensor/locationController.ts +++ b/miniprogram/engine/sensor/locationController.ts @@ -89,6 +89,7 @@ export class LocationController { startBackgroundLocation(): void { wx.startLocationUpdateBackground({ + type: 'wgs84', success: () => { this.bindLocationListener() this.listening = true @@ -149,3 +150,4 @@ export class LocationController { wx.onLocationChange(this.boundLocationHandler) } } + diff --git a/miniprogram/pages/map/map.ts b/miniprogram/pages/map/map.ts index a8a7a69..715f052 100644 --- a/miniprogram/pages/map/map.ts +++ b/miniprogram/pages/map/map.ts @@ -5,8 +5,8 @@ type MapPageData = MapEngineViewState & { showDebugPanel: boolean } -const INTERNAL_BUILD_VERSION = 'map-build-88' -const REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/wxmini/test/qyds-001/game.json' +const INTERNAL_BUILD_VERSION = 'map-build-99' +const REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/wxmini/test/game.json' let mapEngine: MapEngine | null = null @@ -201,6 +201,12 @@ Page({ } }, + handleToggleOsmReference() { + if (mapEngine) { + mapEngine.handleToggleOsmReference() + } + }, + handleToggleDebugPanel() { this.setData({ showDebugPanel: !this.data.showDebugPanel, @@ -240,5 +246,6 @@ Page({ + diff --git a/miniprogram/pages/map/map.wxml b/miniprogram/pages/map/map.wxml index f696ddd..78a9650 100644 --- a/miniprogram/pages/map/map.wxml +++ b/miniprogram/pages/map/map.wxml @@ -158,6 +158,7 @@ {{gpsTracking ? '停止定位' : '开启定位'}} + {{osmReferenceText}} 手动 diff --git a/miniprogram/utils/projection.ts b/miniprogram/utils/projection.ts index 26ccf62..7d3c9f1 100644 --- a/miniprogram/utils/projection.ts +++ b/miniprogram/utils/projection.ts @@ -13,6 +13,13 @@ export interface WorldTilePoint { y: number } +export interface MapCalibration { + offsetEastMeters: number + offsetNorthMeters: number + rotationDeg: number + scale: number +} + const MAX_LATITUDE = 85.05112878 const EARTH_RADIUS = 6378137 @@ -60,6 +67,43 @@ export function worldTileToLonLat(point: WorldTilePoint, zoom: number): LonLatPo } } +export function applyMapCalibration(point: LonLatPoint, calibration: MapCalibration, origin: LonLatPoint): LonLatPoint { + const scale = calibration.scale || 1 + const rotationDeg = calibration.rotationDeg || 0 + const offsetEastMeters = calibration.offsetEastMeters || 0 + const offsetNorthMeters = calibration.offsetNorthMeters || 0 + + if ( + Math.abs(scale - 1) < 0.000001 + && Math.abs(rotationDeg) < 0.000001 + && Math.abs(offsetEastMeters) < 0.000001 + && Math.abs(offsetNorthMeters) < 0.000001 + ) { + return point + } + + const originMercator = lonLatToWebMercator(origin) + const pointMercator = lonLatToWebMercator(point) + const deltaX = pointMercator.x - originMercator.x + const deltaY = pointMercator.y - originMercator.y + const scaledX = deltaX * scale + const scaledY = deltaY * scale + const rotationRad = rotationDeg * Math.PI / 180 + const cos = Math.cos(rotationRad) + const sin = Math.sin(rotationRad) + + const calibratedMercator: WebMercatorPoint = { + x: originMercator.x + scaledX * cos - scaledY * sin + offsetEastMeters, + y: originMercator.y + scaledX * sin + scaledY * cos + offsetNorthMeters, + } + + return webMercatorToLonLat(calibratedMercator) +} + +export function calibratedLonLatToWorldTile(point: LonLatPoint, zoom: number, calibration: MapCalibration, origin: LonLatPoint): WorldTilePoint { + return lonLatToWorldTile(applyMapCalibration(point, calibration, origin), zoom) +} + const CHINA_AXIS = 6378245 const CHINA_EE = 0.00669342162296594323