feat: fix gps map projection and update map config
This commit is contained in:
@@ -1,27 +1,34 @@
|
|||||||
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'
|
||||||
|
|
||||||
export class GpsLayer implements MapLayer {
|
function buildVectorCamera(scene: MapScene): CameraState {
|
||||||
projectPoint(scene: MapScene, camera: CameraState): ScreenPoint {
|
|
||||||
const worldPoint = lonLatToWorldTile(scene.gpsPoint, scene.zoom)
|
|
||||||
const screenPoint = worldToScreen(camera, worldPoint, false)
|
|
||||||
return {
|
return {
|
||||||
x: screenPoint.x + scene.translateX,
|
centerWorldX: scene.exactCenterWorldX,
|
||||||
y: screenPoint.y + scene.translateY,
|
centerWorldY: scene.exactCenterWorldY,
|
||||||
|
viewportWidth: scene.viewportWidth,
|
||||||
|
viewportHeight: scene.viewportHeight,
|
||||||
|
visibleColumns: scene.visibleColumns,
|
||||||
|
rotationRad: scene.rotationRad,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GpsLayer implements MapLayer {
|
||||||
|
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 {
|
getPulseRadius(pulseFrame: number): number {
|
||||||
return 18 + 6 * (0.5 + 0.5 * Math.sin(pulseFrame / 6))
|
return 18 + 6 * (0.5 + 0.5 * Math.sin(pulseFrame / 6))
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TrackLayer implements MapLayer {
|
function buildVectorCamera(scene: MapScene): CameraState {
|
||||||
projectPoints(scene: MapScene, camera: CameraState): ScreenPoint[] {
|
|
||||||
return scene.track.map((point) => {
|
|
||||||
const worldPoint = lonLatToWorldTile(point, scene.zoom)
|
|
||||||
const screenPoint = worldToScreen(camera, worldPoint, false)
|
|
||||||
return {
|
return {
|
||||||
x: screenPoint.x + scene.translateX,
|
centerWorldX: scene.exactCenterWorldX,
|
||||||
y: screenPoint.y + scene.translateY,
|
centerWorldY: scene.exactCenterWorldY,
|
||||||
|
viewportWidth: scene.viewportWidth,
|
||||||
|
viewportHeight: scene.viewportHeight,
|
||||||
|
visibleColumns: scene.visibleColumns,
|
||||||
|
rotationRad: scene.rotationRad,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TrackLayer implements MapLayer {
|
||||||
|
projectPoints(scene: MapScene): ScreenPoint[] {
|
||||||
|
const camera = buildVectorCamera(scene)
|
||||||
|
return scene.track.map((point) => {
|
||||||
|
const worldPoint = calibratedLonLatToWorldTile(point, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin)
|
||||||
|
return worldToScreen(camera, worldPoint, false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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'
|
||||||
|
|||||||
@@ -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 {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user