feat: fix gps map projection and update map config

This commit is contained in:
2026-03-23 10:05:41 +08:00
parent a4c426df8b
commit 51740761f5
11 changed files with 214 additions and 55 deletions

View File

@@ -1,18 +1,25 @@
import { type CameraState } from '../camera/camera' import { calibratedLonLatToWorldTile } from '../../utils/projection'
import { lonLatToWorldTile } from '../../utils/projection' import { worldToScreen, type CameraState } from '../camera/camera'
import { worldToScreen } from '../camera/camera'
import { type MapLayer, type LayerRenderContext } from './mapLayer' import { type MapLayer, type LayerRenderContext } from './mapLayer'
import { type MapScene } from '../renderer/mapRenderer' import { type MapScene } from '../renderer/mapRenderer'
import { type ScreenPoint } from './trackLayer' 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 { export class GpsLayer implements MapLayer {
projectPoint(scene: MapScene, camera: CameraState): ScreenPoint { projectPoint(scene: MapScene): ScreenPoint {
const worldPoint = lonLatToWorldTile(scene.gpsPoint, scene.zoom) const camera = buildVectorCamera(scene)
const screenPoint = worldToScreen(camera, worldPoint, false) const worldPoint = calibratedLonLatToWorldTile(scene.gpsPoint, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin)
return { return worldToScreen(camera, worldPoint, false)
x: screenPoint.x + scene.translateX,
y: screenPoint.y + scene.translateY,
}
} }
getPulseRadius(pulseFrame: number): number { getPulseRadius(pulseFrame: number): number {
@@ -20,8 +27,8 @@ export class GpsLayer implements MapLayer {
} }
draw(context: LayerRenderContext): void { draw(context: LayerRenderContext): void {
const { ctx, camera, scene, pulseFrame } = context const { ctx, scene, pulseFrame } = context
const gpsScreenPoint = this.projectPoint(scene, camera) const gpsScreenPoint = this.projectPoint(scene)
const pulse = this.getPulseRadius(pulseFrame) const pulse = this.getPulseRadius(pulseFrame)
ctx.save() ctx.save()

View File

@@ -1,5 +1,5 @@
import { type CameraState } from '../camera/camera' import { type CameraState } from '../camera/camera'
import { lonLatToWorldTile } from '../../utils/projection' import { calibratedLonLatToWorldTile } from '../../utils/projection'
import { worldToScreen } from '../camera/camera' import { worldToScreen } from '../camera/camera'
import { type MapLayer, type LayerRenderContext } from './mapLayer' import { type MapLayer, type LayerRenderContext } from './mapLayer'
import { type MapScene } from '../renderer/mapRenderer' import { type MapScene } from '../renderer/mapRenderer'
@@ -9,21 +9,29 @@ export interface ScreenPoint {
y: number 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 { export class TrackLayer implements MapLayer {
projectPoints(scene: MapScene, camera: CameraState): ScreenPoint[] { projectPoints(scene: MapScene): ScreenPoint[] {
const camera = buildVectorCamera(scene)
return scene.track.map((point) => { return scene.track.map((point) => {
const worldPoint = lonLatToWorldTile(point, scene.zoom) const worldPoint = calibratedLonLatToWorldTile(point, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin)
const screenPoint = worldToScreen(camera, worldPoint, false) return worldToScreen(camera, worldPoint, false)
return {
x: screenPoint.x + scene.translateX,
y: screenPoint.y + scene.translateY,
}
}) })
} }
draw(context: LayerRenderContext): void { draw(context: LayerRenderContext): void {
const { ctx, camera, scene } = context const { ctx, scene } = context
const points = this.projectPoints(scene, camera) const points = this.projectPoints(scene)
ctx.save() ctx.save()
ctx.lineCap = 'round' ctx.lineCap = 'round'

View File

@@ -3,7 +3,7 @@ import { CompassHeadingController } from '../sensor/compassHeadingController'
import { LocationController } from '../sensor/locationController' import { LocationController } from '../sensor/locationController'
import { WebGLMapRenderer } from '../renderer/webglMapRenderer' import { WebGLMapRenderer } from '../renderer/webglMapRenderer'
import { type MapRendererStats } from '../renderer/mapRenderer' 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' import { isTileWithinBounds, type RemoteMapConfig, type TileZoomBounds } from '../../utils/remoteMapConfig'
const RENDER_MODE = 'Single WebGL Pipeline' 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_X = DEFAULT_TOP_LEFT_TILE_X + 1
const DEFAULT_CENTER_TILE_Y = DEFAULT_TOP_LEFT_TILE_Y + 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 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 MIN_PREVIEW_SCALE = 0.55
const MAX_PREVIEW_SCALE = 1.85 const MAX_PREVIEW_SCALE = 1.85
const INERTIA_FRAME_MS = 16 const INERTIA_FRAME_MS = 16
@@ -114,6 +122,8 @@ export interface MapEngineViewState {
gpsTracking: boolean gpsTracking: boolean
gpsTrackingText: string gpsTrackingText: string
gpsCoordText: string gpsCoordText: string
osmReferenceEnabled: boolean
osmReferenceText: string
} }
export interface MapEngineCallbacks { export interface MapEngineCallbacks {
@@ -158,6 +168,8 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
'gpsTracking', 'gpsTracking',
'gpsTrackingText', 'gpsTrackingText',
'gpsCoordText', 'gpsCoordText',
'osmReferenceEnabled',
'osmReferenceText',
] ]
function buildCenterText(zoom: number, x: number, y: number): string { function buildCenterText(zoom: number, x: number, y: number): string {
@@ -524,6 +536,8 @@ export class MapEngine {
gpsTracking: false, gpsTracking: false,
gpsTrackingText: '持续定位待启动', gpsTrackingText: '持续定位待启动',
gpsCoordText: '--', gpsCoordText: '--',
osmReferenceEnabled: false,
osmReferenceText: 'OSM参考关',
} }
this.previewScale = 1 this.previewScale = 1
this.previewOriginX = 0 this.previewOriginX = 0
@@ -575,7 +589,7 @@ export class MapEngine {
handleLocationUpdate(longitude: number, latitude: number, accuracyMeters: number | null): void { 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 const lastTrackPoint = this.currentGpsTrack.length ? this.currentGpsTrack[this.currentGpsTrack.length - 1] : null
if (!lastTrackPoint || getApproxDistanceMeters(lastTrackPoint, nextPoint) >= GPS_TRACK_MIN_STEP_METERS) { if (!lastTrackPoint || getApproxDistanceMeters(lastTrackPoint, nextPoint) >= GPS_TRACK_MIN_STEP_METERS) {
this.currentGpsTrack = [...this.currentGpsTrack, nextPoint].slice(-GPS_TRACK_MAX_POINTS) this.currentGpsTrack = [...this.currentGpsTrack, nextPoint].slice(-GPS_TRACK_MAX_POINTS)
@@ -612,6 +626,16 @@ export class MapEngine {
this.syncRenderer() 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 { handleToggleGpsTracking(): void {
if (this.locationController.listening) { if (this.locationController.listening) {
this.locationController.stop() this.locationController.stop()
@@ -1364,11 +1388,15 @@ export class MapEngine {
} }
buildScene() { buildScene() {
const exactCenter = this.getExactCenterFromTranslate(this.state.tileTranslateX, this.state.tileTranslateY)
return { return {
tileSource: this.state.tileSource, tileSource: this.state.tileSource,
osmTileSource: OSM_TILE_SOURCE,
zoom: this.state.zoom, zoom: this.state.zoom,
centerTileX: this.state.centerTileX, centerTileX: this.state.centerTileX,
centerTileY: this.state.centerTileY, centerTileY: this.state.centerTileY,
exactCenterWorldX: exactCenter.x,
exactCenterWorldY: exactCenter.y,
tileBoundsByZoom: this.tileBoundsByZoom, tileBoundsByZoom: this.tileBoundsByZoom,
viewportWidth: this.state.stageWidth, viewportWidth: this.state.stageWidth,
viewportHeight: this.state.stageHeight, viewportHeight: this.state.stageHeight,
@@ -1382,6 +1410,10 @@ export class MapEngine {
previewOriginY: this.previewOriginY || this.state.stageHeight / 2, previewOriginY: this.previewOriginY || this.state.stageHeight / 2,
track: this.currentGpsTrack.length ? this.currentGpsTrack : SAMPLE_TRACK_WGS84, track: this.currentGpsTrack.length ? this.currentGpsTrack : SAMPLE_TRACK_WGS84,
gpsPoint: this.currentGpsPoint, 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 { getCameraState(rotationDeg = this.state.rotationDeg): CameraState {
return { return {
centerWorldX: this.state.centerTileX, centerWorldX: this.state.centerTileX + 0.5,
centerWorldY: this.state.centerTileY, centerWorldY: this.state.centerTileY + 0.5,
viewportWidth: this.state.stageWidth, viewportWidth: this.state.stageWidth,
viewportHeight: this.state.stageHeight, viewportHeight: this.state.stageHeight,
visibleColumns: DESIRED_VISIBLE_COLUMNS, visibleColumns: DESIRED_VISIBLE_COLUMNS,
@@ -1410,10 +1442,10 @@ export class MapEngine {
return normalizeRotationDeg(rotationDeg) * Math.PI / 180 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 { return {
centerWorldX, centerWorldX: centerTileX + 0.5,
centerWorldY, centerWorldY: centerTileY + 0.5,
viewportWidth: this.state.stageWidth, viewportWidth: this.state.stageWidth,
viewportHeight: this.state.stageHeight, viewportHeight: this.state.stageHeight,
visibleColumns: DESIRED_VISIBLE_COLUMNS, visibleColumns: DESIRED_VISIBLE_COLUMNS,
@@ -1437,8 +1469,8 @@ export class MapEngine {
getExactCenterFromTranslate(translateX: number, translateY: number): { x: number; y: number } { getExactCenterFromTranslate(translateX: number, translateY: number): { x: number; y: number } {
if (!this.state.stageWidth || !this.state.stageHeight) { if (!this.state.stageWidth || !this.state.stageHeight) {
return { return {
x: this.state.centerTileX, x: this.state.centerTileX + 0.5,
y: this.state.centerTileY, y: this.state.centerTileY + 0.5,
} }
} }
@@ -1456,8 +1488,8 @@ export class MapEngine {
tileTranslateX: number tileTranslateX: number
tileTranslateY: number tileTranslateY: number
} { } {
const nextCenterTileX = Math.round(centerWorldX) const nextCenterTileX = Math.floor(centerWorldX)
const nextCenterTileY = Math.round(centerWorldY) const nextCenterTileY = Math.floor(centerWorldY)
if (!this.state.stageWidth || !this.state.stageHeight) { if (!this.state.stageWidth || !this.state.stageHeight) {
return { return {
@@ -1763,6 +1795,13 @@ export class MapEngine {

View File

@@ -1,13 +1,16 @@
import { type CameraState } from '../camera/camera' import { type CameraState } from '../camera/camera'
import { type TileStoreStats } from '../tile/tileStore' 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' import { type TileZoomBounds } from '../../utils/remoteMapConfig'
export interface MapScene { export interface MapScene {
tileSource: string tileSource: string
osmTileSource: string
zoom: number zoom: number
centerTileX: number centerTileX: number
centerTileY: number centerTileY: number
exactCenterWorldX: number
exactCenterWorldY: number
tileBoundsByZoom: Record<number, TileZoomBounds> | null tileBoundsByZoom: Record<number, TileZoomBounds> | null
viewportWidth: number viewportWidth: number
viewportHeight: number viewportHeight: number
@@ -21,6 +24,10 @@ export interface MapScene {
previewOriginY: number previewOriginY: number
track: LonLatPoint[] track: LonLatPoint[]
gpsPoint: LonLatPoint gpsPoint: LonLatPoint
gpsCalibration: MapCalibration
gpsCalibrationOrigin: LonLatPoint
osmReferenceEnabled: boolean
overlayOpacity: number
} }
export type MapRendererStats = TileStoreStats export type MapRendererStats = TileStoreStats
@@ -34,8 +41,8 @@ export interface MapRenderer {
export function buildCamera(scene: MapScene): CameraState { export function buildCamera(scene: MapScene): CameraState {
return { return {
centerWorldX: scene.centerTileX, centerWorldX: scene.centerTileX + 0.5,
centerWorldY: scene.centerTileY, centerWorldY: scene.centerTileY + 0.5,
viewportWidth: scene.viewportWidth, viewportWidth: scene.viewportWidth,
viewportHeight: scene.viewportHeight, viewportHeight: scene.viewportHeight,
visibleColumns: scene.visibleColumns, visibleColumns: scene.visibleColumns,
@@ -44,5 +51,3 @@ export function buildCamera(scene: MapScene): CameraState {
rotationRad: scene.rotationRad, rotationRad: scene.rotationRad,
} }
} }

View File

@@ -11,7 +11,9 @@ const ANIMATION_FRAME_MS = 33
export class WebGLMapRenderer implements MapRenderer { export class WebGLMapRenderer implements MapRenderer {
tileStore: TileStore tileStore: TileStore
osmTileStore: TileStore
tileLayer: TileLayer tileLayer: TileLayer
osmTileLayer: TileLayer
trackLayer: TrackLayer trackLayer: TrackLayer
gpsLayer: GpsLayer gpsLayer: GpsLayer
tileRenderer: WebGLTileRenderer tileRenderer: WebGLTileRenderer
@@ -40,10 +42,19 @@ export class WebGLMapRenderer implements MapRenderer {
this.scheduleRender() this.scheduleRender()
}, },
} satisfies TileStoreCallbacks) } satisfies TileStoreCallbacks)
this.osmTileStore = new TileStore({
onTileReady: () => {
this.scheduleRender()
},
onTileError: () => {
this.scheduleRender()
},
} satisfies TileStoreCallbacks)
this.tileLayer = new TileLayer() this.tileLayer = new TileLayer()
this.osmTileLayer = new TileLayer()
this.trackLayer = new TrackLayer() this.trackLayer = new TrackLayer()
this.gpsLayer = new GpsLayer() 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.vectorRenderer = new WebGLVectorRenderer(this.trackLayer, this.gpsLayer)
this.scene = null this.scene = null
this.renderTimer = 0 this.renderTimer = 0
@@ -94,6 +105,7 @@ export class WebGLMapRenderer implements MapRenderer {
this.vectorRenderer.destroy() this.vectorRenderer.destroy()
this.tileRenderer.destroy() this.tileRenderer.destroy()
this.tileStore.destroy() this.tileStore.destroy()
this.osmTileStore.destroy()
this.scene = null this.scene = null
} }

View File

@@ -53,25 +53,31 @@ export class WebGLTileRenderer {
gl: any gl: any
tileLayer: TileLayer tileLayer: TileLayer
tileStore: TileStore tileStore: TileStore
osmTileLayer: TileLayer
osmTileStore: TileStore
dpr: number dpr: number
program: any program: any
positionBuffer: any positionBuffer: any
texCoordBuffer: any texCoordBuffer: any
positionLocation: number positionLocation: number
texCoordLocation: number texCoordLocation: number
opacityLocation: any
textureCache: Map<string, TextureRecord> textureCache: Map<string, TextureRecord>
constructor(tileLayer: TileLayer, tileStore: TileStore) { constructor(tileLayer: TileLayer, tileStore: TileStore, osmTileLayer: TileLayer, osmTileStore: TileStore) {
this.canvas = null this.canvas = null
this.gl = null this.gl = null
this.tileLayer = tileLayer this.tileLayer = tileLayer
this.tileStore = tileStore this.tileStore = tileStore
this.osmTileLayer = osmTileLayer
this.osmTileStore = osmTileStore
this.dpr = 1 this.dpr = 1
this.program = null this.program = null
this.positionBuffer = null this.positionBuffer = null
this.texCoordBuffer = null this.texCoordBuffer = null
this.positionLocation = -1 this.positionLocation = -1
this.texCoordLocation = -1 this.texCoordLocation = -1
this.opacityLocation = null
this.textureCache = new Map<string, TextureRecord>() this.textureCache = new Map<string, TextureRecord>()
} }
@@ -90,12 +96,13 @@ export class WebGLTileRenderer {
this.program = createProgram( this.program = createProgram(
gl, 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; }', '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.positionBuffer = gl.createBuffer()
this.texCoordBuffer = gl.createBuffer() this.texCoordBuffer = gl.createBuffer()
this.positionLocation = gl.getAttribLocation(this.program, 'a_position') this.positionLocation = gl.getAttribLocation(this.program, 'a_position')
this.texCoordLocation = gl.getAttribLocation(this.program, 'a_texCoord') 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.viewport(0, 0, canvasNode.width, canvasNode.height)
gl.disable(gl.DEPTH_TEST) gl.disable(gl.DEPTH_TEST)
@@ -103,6 +110,7 @@ export class WebGLTileRenderer {
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
this.tileStore.attachCanvas(canvasNode) this.tileStore.attachCanvas(canvasNode)
this.osmTileStore.attachCanvas(canvasNode)
} }
destroy(): void { destroy(): void {
@@ -135,24 +143,46 @@ export class WebGLTileRenderer {
} }
const gl = this.gl 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.viewport(0, 0, this.canvas.width, this.canvas.height)
gl.clearColor(0.8588, 0.9333, 0.8314, 1) gl.clearColor(0.8588, 0.9333, 0.8314, 1)
gl.clear(gl.COLOR_BUFFER_BIT) gl.clear(gl.COLOR_BUFFER_BIT)
gl.useProgram(this.program) 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) { 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) { 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.drawEntry(readyEntry, tile.url, 0, 0, readyEntry.image.width || 256, readyEntry.image.height || 256, tile.leftPx, tile.topPx, tile.sizePx, tile.sizePx, scene, opacity)
this.tileLayer.lastReadyTileCount += 1 tileLayer.lastReadyTileCount += 1
continue continue
} }
const parentFallback = this.tileStore.getParentFallbackSlice(tile, scene) const parentFallback = tileStore.getParentFallbackSlice(tile, scene)
if (parentFallback) { if (parentFallback) {
this.drawEntry( this.drawEntry(
parentFallback.entry, parentFallback.entry,
@@ -166,10 +196,11 @@ export class WebGLTileRenderer {
tile.sizePx, tile.sizePx,
tile.sizePx, tile.sizePx,
scene, scene,
opacity,
) )
} }
const childFallback = this.tileStore.getChildFallback(tile, scene) const childFallback = tileStore.getChildFallback(tile, scene)
if (!childFallback) { if (!childFallback) {
continue continue
} }
@@ -189,6 +220,7 @@ export class WebGLTileRenderer {
cellWidth, cellWidth,
cellHeight, cellHeight,
scene, scene,
opacity,
) )
} }
} }
@@ -206,6 +238,7 @@ export class WebGLTileRenderer {
drawWidth: number, drawWidth: number,
drawHeight: number, drawHeight: number,
scene: MapScene, scene: MapScene,
opacity: number,
): void { ): void {
if (!this.gl || !entry.image) { if (!this.gl || !entry.image) {
return return
@@ -246,6 +279,7 @@ export class WebGLTileRenderer {
texRight, texBottom, texRight, texBottom,
]) ])
gl.uniform1f(this.opacityLocation, opacity)
gl.bindTexture(gl.TEXTURE_2D, texture.texture) gl.bindTexture(gl.TEXTURE_2D, texture.texture)
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer) gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer)
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STREAM_DRAW) gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STREAM_DRAW)

View File

@@ -1,4 +1,4 @@
import { buildCamera, type MapScene } from './mapRenderer' import { type MapScene } from './mapRenderer'
import { TrackLayer } from '../layer/trackLayer' import { TrackLayer } from '../layer/trackLayer'
import { GpsLayer } from '../layer/gpsLayer' import { GpsLayer } from '../layer/gpsLayer'
@@ -123,9 +123,8 @@ export class WebGLVectorRenderer {
} }
const gl = this.gl const gl = this.gl
const camera = buildCamera(scene) const trackPoints = this.trackLayer.projectPoints(scene)
const trackPoints = this.trackLayer.projectPoints(scene, camera) const gpsPoint = this.gpsLayer.projectPoint(scene)
const gpsPoint = this.gpsLayer.projectPoint(scene, camera)
const positions: number[] = [] const positions: number[] = []
const colors: number[] = [] const colors: number[] = []
@@ -232,3 +231,4 @@ export class WebGLVectorRenderer {
} }
} }
} }

View File

@@ -89,6 +89,7 @@ export class LocationController {
startBackgroundLocation(): void { startBackgroundLocation(): void {
wx.startLocationUpdateBackground({ wx.startLocationUpdateBackground({
type: 'wgs84',
success: () => { success: () => {
this.bindLocationListener() this.bindLocationListener()
this.listening = true this.listening = true
@@ -149,3 +150,4 @@ export class LocationController {
wx.onLocationChange(this.boundLocationHandler) wx.onLocationChange(this.boundLocationHandler)
} }
} }

View File

@@ -5,8 +5,8 @@ type MapPageData = MapEngineViewState & {
showDebugPanel: boolean showDebugPanel: boolean
} }
const INTERNAL_BUILD_VERSION = 'map-build-88' const INTERNAL_BUILD_VERSION = 'map-build-99'
const REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/wxmini/test/qyds-001/game.json' const REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/wxmini/test/game.json'
let mapEngine: MapEngine | null = null let mapEngine: MapEngine | null = null
@@ -201,6 +201,12 @@ Page({
} }
}, },
handleToggleOsmReference() {
if (mapEngine) {
mapEngine.handleToggleOsmReference()
}
},
handleToggleDebugPanel() { handleToggleDebugPanel() {
this.setData({ this.setData({
showDebugPanel: !this.data.showDebugPanel, showDebugPanel: !this.data.showDebugPanel,
@@ -240,5 +246,6 @@ Page({

View File

@@ -158,6 +158,7 @@
</view> </view>
<view class="control-row"> <view class="control-row">
<view class="control-chip {{gpsTracking ? 'control-chip--active' : 'control-chip--secondary'}}" bindtap="handleToggleGpsTracking">{{gpsTracking ? '停止定位' : '开启定位'}}</view> <view class="control-chip {{gpsTracking ? 'control-chip--active' : 'control-chip--secondary'}}" bindtap="handleToggleGpsTracking">{{gpsTracking ? '停止定位' : '开启定位'}}</view>
<view class="control-chip {{osmReferenceEnabled ? 'control-chip--active' : 'control-chip--secondary'}}" bindtap="handleToggleOsmReference">{{osmReferenceText}}</view>
</view> </view>
<view class="control-row control-row--triple"> <view class="control-row control-row--triple">
<view class="control-chip {{orientationMode === 'manual' ? 'control-chip--active' : ''}}" bindtap="handleSetManualMode">手动</view> <view class="control-chip {{orientationMode === 'manual' ? 'control-chip--active' : ''}}" bindtap="handleSetManualMode">手动</view>

View File

@@ -13,6 +13,13 @@ export interface WorldTilePoint {
y: number y: number
} }
export interface MapCalibration {
offsetEastMeters: number
offsetNorthMeters: number
rotationDeg: number
scale: number
}
const MAX_LATITUDE = 85.05112878 const MAX_LATITUDE = 85.05112878
const EARTH_RADIUS = 6378137 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_AXIS = 6378245
const CHINA_EE = 0.00669342162296594323 const CHINA_EE = 0.00669342162296594323