feat: load remote map config and constrain tile bounds
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createTileGrid, type TileItem } from '../../utils/tile'
|
||||
import { isTileWithinBounds } from '../../utils/remoteMapConfig'
|
||||
import { getTileSizePx, type CameraState } from '../camera/camera'
|
||||
import { type MapScene } from '../renderer/mapRenderer'
|
||||
import { type TileStore } from '../tile/tileStore'
|
||||
@@ -52,7 +53,7 @@ export class TileLayer implements MapLayer {
|
||||
viewportHeight: scene.viewportHeight,
|
||||
tileSize,
|
||||
overdraw: scene.overdraw,
|
||||
})
|
||||
}).filter((tile) => isTileWithinBounds(scene.tileBoundsByZoom, scene.zoom, tile.x, tile.y))
|
||||
}
|
||||
|
||||
tileStore.queueVisibleTiles(this.cachedTiles, scene, gridKey)
|
||||
@@ -122,3 +123,4 @@ export class TileLayer implements MapLayer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ import { CompassHeadingController } from '../sensor/compassHeadingController'
|
||||
import { WebGLMapRenderer } from '../renderer/webglMapRenderer'
|
||||
import { type MapRendererStats } from '../renderer/mapRenderer'
|
||||
import { worldTileToLonLat, type LonLatPoint } from '../../utils/projection'
|
||||
import { type RemoteMapConfig, type TileZoomBounds } from '../../utils/remoteMapConfig'
|
||||
|
||||
const RENDER_MODE = 'Single WebGL Pipeline'
|
||||
const PROJECTION_MODE = 'WGS84 -> WorldTile -> Camera -> Screen'
|
||||
const MAP_NORTH_OFFSET_DEG = 0
|
||||
const MAGNETIC_DECLINATION_DEG = -6.91
|
||||
const MAGNETIC_DECLINATION_TEXT = '6.91° W'
|
||||
let MAGNETIC_DECLINATION_DEG = -6.91
|
||||
let MAGNETIC_DECLINATION_TEXT = '6.91° W'
|
||||
const MIN_ZOOM = 15
|
||||
const MAX_ZOOM = 20
|
||||
const DEFAULT_ZOOM = 17
|
||||
@@ -70,6 +71,7 @@ export interface MapEngineViewState {
|
||||
mapReady: boolean
|
||||
mapReadyText: string
|
||||
mapName: string
|
||||
configStatusText: string
|
||||
zoom: number
|
||||
rotationDeg: number
|
||||
rotationText: string
|
||||
@@ -119,6 +121,7 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
|
||||
'mapReady',
|
||||
'mapReadyText',
|
||||
'mapName',
|
||||
'configStatusText',
|
||||
'zoom',
|
||||
'rotationDeg',
|
||||
'rotationText',
|
||||
@@ -383,6 +386,12 @@ export class MapEngine {
|
||||
autoRotateSourceMode: AutoRotateSourceMode
|
||||
autoRotateCalibrationOffsetDeg: number | null
|
||||
autoRotateCalibrationPending: boolean
|
||||
minZoom: number
|
||||
maxZoom: number
|
||||
defaultZoom: number
|
||||
defaultCenterTileX: number
|
||||
defaultCenterTileY: number
|
||||
tileBoundsByZoom: Record<number, TileZoomBounds> | null
|
||||
|
||||
constructor(buildVersion: string, callbacks: MapEngineCallbacks) {
|
||||
this.buildVersion = buildVersion
|
||||
@@ -405,6 +414,12 @@ export class MapEngine {
|
||||
this.handleCompassError(message)
|
||||
},
|
||||
})
|
||||
this.minZoom = MIN_ZOOM
|
||||
this.maxZoom = MAX_ZOOM
|
||||
this.defaultZoom = DEFAULT_ZOOM
|
||||
this.defaultCenterTileX = DEFAULT_CENTER_TILE_X
|
||||
this.defaultCenterTileY = DEFAULT_CENTER_TILE_Y
|
||||
this.tileBoundsByZoom = null
|
||||
this.state = {
|
||||
buildVersion: this.buildVersion,
|
||||
renderMode: RENDER_MODE,
|
||||
@@ -412,6 +427,7 @@ export class MapEngine {
|
||||
mapReady: false,
|
||||
mapReadyText: 'BOOTING',
|
||||
mapName: 'LCX 测试地图',
|
||||
configStatusText: '远程配置待加载',
|
||||
zoom: DEFAULT_ZOOM,
|
||||
rotationDeg: 0,
|
||||
rotationText: formatRotationText(0),
|
||||
@@ -526,6 +542,55 @@ export class MapEngine {
|
||||
this.compassController.start()
|
||||
}
|
||||
|
||||
applyRemoteMapConfig(config: RemoteMapConfig): void {
|
||||
MAGNETIC_DECLINATION_DEG = config.magneticDeclinationDeg
|
||||
MAGNETIC_DECLINATION_TEXT = config.magneticDeclinationText
|
||||
this.minZoom = config.minZoom
|
||||
this.maxZoom = config.maxZoom
|
||||
this.defaultZoom = config.defaultZoom
|
||||
this.defaultCenterTileX = config.initialCenterTileX
|
||||
this.defaultCenterTileY = config.initialCenterTileY
|
||||
this.tileBoundsByZoom = config.tileBoundsByZoom
|
||||
|
||||
const statePatch: Partial<MapEngineViewState> = {
|
||||
configStatusText: '远程配置已载入',
|
||||
projectionMode: config.projectionModeText,
|
||||
tileSource: config.tileSource,
|
||||
sensorHeadingText: formatHeadingText(this.smoothedSensorHeadingDeg === null ? null : getCompassReferenceHeadingDeg(this.northReferenceMode, this.smoothedSensorHeadingDeg)),
|
||||
compassDeclinationText: formatCompassDeclinationText(this.northReferenceMode),
|
||||
northReferenceButtonText: formatNorthReferenceButtonText(this.northReferenceMode),
|
||||
northReferenceText: formatNorthReferenceText(this.northReferenceMode),
|
||||
compassNeedleDeg: formatCompassNeedleDegForMode(this.northReferenceMode, this.smoothedSensorHeadingDeg),
|
||||
}
|
||||
|
||||
if (!this.state.stageWidth || !this.state.stageHeight) {
|
||||
this.setState({
|
||||
...statePatch,
|
||||
zoom: this.defaultZoom,
|
||||
centerTileX: this.defaultCenterTileX,
|
||||
centerTileY: this.defaultCenterTileY,
|
||||
centerText: buildCenterText(this.defaultZoom, this.defaultCenterTileX, this.defaultCenterTileY),
|
||||
statusText: `远程地图配置已载入 (${this.buildVersion})`,
|
||||
}, true)
|
||||
return
|
||||
}
|
||||
|
||||
this.commitViewport({
|
||||
...statePatch,
|
||||
zoom: this.defaultZoom,
|
||||
centerTileX: this.defaultCenterTileX,
|
||||
centerTileY: this.defaultCenterTileY,
|
||||
tileTranslateX: 0,
|
||||
tileTranslateY: 0,
|
||||
}, `远程地图配置已载入 (${this.buildVersion})`, true, () => {
|
||||
this.resetPreviewState()
|
||||
this.syncRenderer()
|
||||
if (this.state.orientationMode === 'heading-up' && this.refreshAutoRotateTarget()) {
|
||||
this.scheduleAutoRotate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleTouchStart(event: WechatMiniprogram.TouchEvent): void {
|
||||
this.clearInertiaTimer()
|
||||
this.clearPreviewResetTimer()
|
||||
@@ -696,9 +761,9 @@ export class MapEngine {
|
||||
this.renderer.setAnimationPaused(false)
|
||||
this.commitViewport(
|
||||
{
|
||||
zoom: DEFAULT_ZOOM,
|
||||
centerTileX: DEFAULT_CENTER_TILE_X,
|
||||
centerTileY: DEFAULT_CENTER_TILE_Y,
|
||||
zoom: this.defaultZoom,
|
||||
centerTileX: this.defaultCenterTileX,
|
||||
centerTileY: this.defaultCenterTileY,
|
||||
tileTranslateX: 0,
|
||||
tileTranslateY: 0,
|
||||
},
|
||||
@@ -1196,6 +1261,7 @@ export class MapEngine {
|
||||
zoom: this.state.zoom,
|
||||
centerTileX: this.state.centerTileX,
|
||||
centerTileY: this.state.centerTileY,
|
||||
tileBoundsByZoom: this.tileBoundsByZoom,
|
||||
viewportWidth: this.state.stageWidth,
|
||||
viewportHeight: this.state.stageHeight,
|
||||
visibleColumns: DESIRED_VISIBLE_COLUMNS,
|
||||
@@ -1482,7 +1548,7 @@ export class MapEngine {
|
||||
}
|
||||
|
||||
zoomAroundPoint(zoomDelta: number, stageX: number, stageY: number, residualScale: number): void {
|
||||
const nextZoom = clamp(this.state.zoom + zoomDelta, MIN_ZOOM, MAX_ZOOM)
|
||||
const nextZoom = clamp(this.state.zoom + zoomDelta, this.minZoom, this.maxZoom)
|
||||
const appliedDelta = nextZoom - this.state.zoom
|
||||
if (!appliedDelta) {
|
||||
this.animatePreviewToRest()
|
||||
@@ -1584,3 +1650,4 @@ export class MapEngine {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { type CameraState } from '../camera/camera'
|
||||
import { type TileStoreStats } from '../tile/tileStore'
|
||||
import { type LonLatPoint } from '../../utils/projection'
|
||||
import { type TileZoomBounds } from '../../utils/remoteMapConfig'
|
||||
|
||||
export interface MapScene {
|
||||
tileSource: string
|
||||
zoom: number
|
||||
centerTileX: number
|
||||
centerTileY: number
|
||||
tileBoundsByZoom: Record<number, TileZoomBounds> | null
|
||||
viewportWidth: number
|
||||
viewportHeight: number
|
||||
visibleColumns: number
|
||||
@@ -42,3 +44,5 @@ export function buildCamera(scene: MapScene): CameraState {
|
||||
rotationRad: scene.rotationRad,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { buildTileUrl, type TileItem } from '../../utils/tile'
|
||||
import { isTileWithinBounds, type TileZoomBounds } from '../../utils/remoteMapConfig'
|
||||
import { getTilePersistentCache, type TilePersistentCache } from '../renderer/tilePersistentCache'
|
||||
|
||||
const MAX_PARENT_FALLBACK_DEPTH = 2
|
||||
@@ -51,6 +52,7 @@ export interface TileStoreScene {
|
||||
viewportHeight: number
|
||||
translateX: number
|
||||
translateY: number
|
||||
tileBoundsByZoom: Record<number, TileZoomBounds> | null
|
||||
}
|
||||
|
||||
export interface TileStoreCallbacks {
|
||||
@@ -241,6 +243,9 @@ export class TileStore {
|
||||
const scale = Math.pow(2, depth)
|
||||
const fallbackX = Math.floor(tile.x / scale)
|
||||
const fallbackY = Math.floor(tile.y / scale)
|
||||
if (!isTileWithinBounds(scene.tileBoundsByZoom, fallbackZoom, fallbackX, fallbackY)) {
|
||||
continue
|
||||
}
|
||||
const fallbackUrl = buildTileUrl(scene.tileSource, fallbackZoom, fallbackX, fallbackY)
|
||||
const fallbackPriority = priority / (depth + 1)
|
||||
const existingPriority = parentPriorityMap.get(fallbackUrl)
|
||||
@@ -486,7 +491,10 @@ export class TileStore {
|
||||
const scale = Math.pow(2, depth)
|
||||
const fallbackX = Math.floor(tile.x / scale)
|
||||
const fallbackY = Math.floor(tile.y / scale)
|
||||
const fallbackUrl = buildTileUrl(scene.tileSource, fallbackZoom, fallbackX, fallbackY)
|
||||
if (!isTileWithinBounds(scene.tileBoundsByZoom, fallbackZoom, fallbackX, fallbackY)) {
|
||||
continue
|
||||
}
|
||||
const fallbackUrl = buildTileUrl(scene.tileSource, fallbackZoom, fallbackX, fallbackY)
|
||||
const fallbackEntry = this.tileCache.get(fallbackUrl)
|
||||
|
||||
if (fallbackEntry && fallbackEntry.status === 'ready' && fallbackEntry.image) {
|
||||
@@ -540,6 +548,9 @@ export class TileStore {
|
||||
for (let offsetX = 0; offsetX < division; offsetX += 1) {
|
||||
const childX = tile.x * division + offsetX
|
||||
const childY = tile.y * division + offsetY
|
||||
if (!isTileWithinBounds(scene.tileBoundsByZoom, childZoom, childX, childY)) {
|
||||
continue
|
||||
}
|
||||
const childUrl = buildTileUrl(scene.tileSource, childZoom, childX, childY)
|
||||
const childEntry = this.tileCache.get(childUrl)
|
||||
if (!childEntry || childEntry.status !== 'ready' || !childEntry.image) {
|
||||
@@ -565,3 +576,5 @@ export class TileStore {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user