feat: add background gps tracking support
This commit is contained in:
@@ -9,6 +9,16 @@
|
|||||||
"navigationBarTitleText": "CMR Mini",
|
"navigationBarTitleText": "CMR Mini",
|
||||||
"navigationBarBackgroundColor": "#ffffff"
|
"navigationBarBackgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
|
"permission": {
|
||||||
|
"scope.userLocation": {
|
||||||
|
"desc": "用于获取当前位置并为后台持续定位授权"
|
||||||
|
},
|
||||||
|
"scope.userLocationBackground": {
|
||||||
|
"desc": "用于后台持续获取当前位置并在地图上显示GPS点"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requiredBackgroundModes": ["location"],
|
||||||
|
"requiredPrivateInfos": ["startLocationUpdateBackground", "onLocationChange"],
|
||||||
"style": "v2",
|
"style": "v2",
|
||||||
"componentFramework": "glass-easel",
|
"componentFramework": "glass-easel",
|
||||||
"lazyCodeLoading": "requiredComponents"
|
"lazyCodeLoading": "requiredComponents"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { getTileSizePx, screenToWorld, worldToScreen, type CameraState } from '../camera/camera'
|
import { getTileSizePx, screenToWorld, worldToScreen, type CameraState } from '../camera/camera'
|
||||||
import { CompassHeadingController } from '../sensor/compassHeadingController'
|
import { CompassHeadingController } from '../sensor/compassHeadingController'
|
||||||
|
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 { worldTileToLonLat, type LonLatPoint } from '../../utils/projection'
|
import { gcj02ToWgs84, lonLatToWorldTile, worldTileToLonLat, type LonLatPoint } from '../../utils/projection'
|
||||||
import { 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'
|
||||||
const PROJECTION_MODE = 'WGS84 -> WorldTile -> Camera -> Screen'
|
const PROJECTION_MODE = 'WGS84 -> WorldTile -> Camera -> Screen'
|
||||||
@@ -35,6 +36,8 @@ const AUTO_ROTATE_DEADZONE_DEG = 4
|
|||||||
const AUTO_ROTATE_MAX_STEP_DEG = 0.75
|
const AUTO_ROTATE_MAX_STEP_DEG = 0.75
|
||||||
const AUTO_ROTATE_HEADING_SMOOTHING = 0.32
|
const AUTO_ROTATE_HEADING_SMOOTHING = 0.32
|
||||||
const COMPASS_NEEDLE_SMOOTHING = 0.12
|
const COMPASS_NEEDLE_SMOOTHING = 0.12
|
||||||
|
const GPS_TRACK_MAX_POINTS = 200
|
||||||
|
const GPS_TRACK_MIN_STEP_METERS = 3
|
||||||
|
|
||||||
const SAMPLE_TRACK_WGS84: LonLatPoint[] = [
|
const SAMPLE_TRACK_WGS84: LonLatPoint[] = [
|
||||||
worldTileToLonLat({ x: DEFAULT_CENTER_TILE_X - 0.72, y: DEFAULT_CENTER_TILE_Y + 0.44 }, DEFAULT_ZOOM),
|
worldTileToLonLat({ x: DEFAULT_CENTER_TILE_X - 0.72, y: DEFAULT_CENTER_TILE_Y + 0.44 }, DEFAULT_ZOOM),
|
||||||
@@ -108,6 +111,9 @@ export interface MapEngineViewState {
|
|||||||
stageLeft: number
|
stageLeft: number
|
||||||
stageTop: number
|
stageTop: number
|
||||||
statusText: string
|
statusText: string
|
||||||
|
gpsTracking: boolean
|
||||||
|
gpsTrackingText: string
|
||||||
|
gpsCoordText: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MapEngineCallbacks {
|
export interface MapEngineCallbacks {
|
||||||
@@ -149,6 +155,9 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
|
|||||||
'cacheHitRateText',
|
'cacheHitRateText',
|
||||||
'tileSizePx',
|
'tileSizePx',
|
||||||
'statusText',
|
'statusText',
|
||||||
|
'gpsTracking',
|
||||||
|
'gpsTrackingText',
|
||||||
|
'gpsCoordText',
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildCenterText(zoom: number, x: number, y: number): string {
|
function buildCenterText(zoom: number, x: number, y: number): string {
|
||||||
@@ -349,10 +358,32 @@ function formatCacheHitRate(memoryHitCount: number, diskHitCount: number, networ
|
|||||||
const hitRate = ((memoryHitCount + diskHitCount) / total) * 100
|
const hitRate = ((memoryHitCount + diskHitCount) / total) * 100
|
||||||
return `${Math.round(hitRate)}%`
|
return `${Math.round(hitRate)}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatGpsCoordText(point: LonLatPoint | null, accuracyMeters: number | null): string {
|
||||||
|
if (!point) {
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = `${point.lat.toFixed(6)}, ${point.lon.toFixed(6)}`
|
||||||
|
if (accuracyMeters === null || !Number.isFinite(accuracyMeters)) {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${base} / ±${Math.round(accuracyMeters)}m`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApproxDistanceMeters(a: LonLatPoint, b: LonLatPoint): number {
|
||||||
|
const avgLatRad = ((a.lat + b.lat) / 2) * Math.PI / 180
|
||||||
|
const dx = (b.lon - a.lon) * 111320 * Math.cos(avgLatRad)
|
||||||
|
const dy = (b.lat - a.lat) * 110540
|
||||||
|
return Math.sqrt(dx * dx + dy * dy)
|
||||||
|
}
|
||||||
|
|
||||||
export class MapEngine {
|
export class MapEngine {
|
||||||
buildVersion: string
|
buildVersion: string
|
||||||
renderer: WebGLMapRenderer
|
renderer: WebGLMapRenderer
|
||||||
compassController: CompassHeadingController
|
compassController: CompassHeadingController
|
||||||
|
locationController: LocationController
|
||||||
onData: (patch: Partial<MapEngineViewState>) => void
|
onData: (patch: Partial<MapEngineViewState>) => void
|
||||||
state: MapEngineViewState
|
state: MapEngineViewState
|
||||||
previewScale: number
|
previewScale: number
|
||||||
@@ -392,6 +423,10 @@ export class MapEngine {
|
|||||||
defaultCenterTileX: number
|
defaultCenterTileX: number
|
||||||
defaultCenterTileY: number
|
defaultCenterTileY: number
|
||||||
tileBoundsByZoom: Record<number, TileZoomBounds> | null
|
tileBoundsByZoom: Record<number, TileZoomBounds> | null
|
||||||
|
currentGpsPoint: LonLatPoint
|
||||||
|
currentGpsTrack: LonLatPoint[]
|
||||||
|
currentGpsAccuracyMeters: number | null
|
||||||
|
hasGpsCenteredOnce: boolean
|
||||||
|
|
||||||
constructor(buildVersion: string, callbacks: MapEngineCallbacks) {
|
constructor(buildVersion: string, callbacks: MapEngineCallbacks) {
|
||||||
this.buildVersion = buildVersion
|
this.buildVersion = buildVersion
|
||||||
@@ -414,12 +449,34 @@ export class MapEngine {
|
|||||||
this.handleCompassError(message)
|
this.handleCompassError(message)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
this.locationController = new LocationController({
|
||||||
|
onLocation: (update) => {
|
||||||
|
this.handleLocationUpdate(update.longitude, update.latitude, typeof update.accuracy === 'number' ? update.accuracy : null)
|
||||||
|
},
|
||||||
|
onStatus: (message) => {
|
||||||
|
this.setState({
|
||||||
|
gpsTracking: this.locationController.listening,
|
||||||
|
gpsTrackingText: message,
|
||||||
|
}, true)
|
||||||
|
},
|
||||||
|
onError: (message) => {
|
||||||
|
this.setState({
|
||||||
|
gpsTracking: false,
|
||||||
|
gpsTrackingText: message,
|
||||||
|
statusText: `${message} (${this.buildVersion})`,
|
||||||
|
}, true)
|
||||||
|
},
|
||||||
|
})
|
||||||
this.minZoom = MIN_ZOOM
|
this.minZoom = MIN_ZOOM
|
||||||
this.maxZoom = MAX_ZOOM
|
this.maxZoom = MAX_ZOOM
|
||||||
this.defaultZoom = DEFAULT_ZOOM
|
this.defaultZoom = DEFAULT_ZOOM
|
||||||
this.defaultCenterTileX = DEFAULT_CENTER_TILE_X
|
this.defaultCenterTileX = DEFAULT_CENTER_TILE_X
|
||||||
this.defaultCenterTileY = DEFAULT_CENTER_TILE_Y
|
this.defaultCenterTileY = DEFAULT_CENTER_TILE_Y
|
||||||
this.tileBoundsByZoom = null
|
this.tileBoundsByZoom = null
|
||||||
|
this.currentGpsPoint = SAMPLE_GPS_WGS84
|
||||||
|
this.currentGpsTrack = []
|
||||||
|
this.currentGpsAccuracyMeters = null
|
||||||
|
this.hasGpsCenteredOnce = false
|
||||||
this.state = {
|
this.state = {
|
||||||
buildVersion: this.buildVersion,
|
buildVersion: this.buildVersion,
|
||||||
renderMode: RENDER_MODE,
|
renderMode: RENDER_MODE,
|
||||||
@@ -464,6 +521,9 @@ export class MapEngine {
|
|||||||
stageLeft: 0,
|
stageLeft: 0,
|
||||||
stageTop: 0,
|
stageTop: 0,
|
||||||
statusText: `单 WebGL 管线已准备接入方向传感器 (${this.buildVersion})`,
|
statusText: `单 WebGL 管线已准备接入方向传感器 (${this.buildVersion})`,
|
||||||
|
gpsTracking: false,
|
||||||
|
gpsTrackingText: '持续定位待启动',
|
||||||
|
gpsCoordText: '--',
|
||||||
}
|
}
|
||||||
this.previewScale = 1
|
this.previewScale = 1
|
||||||
this.previewOriginX = 0
|
this.previewOriginX = 0
|
||||||
@@ -508,10 +568,58 @@ export class MapEngine {
|
|||||||
this.clearViewSyncTimer()
|
this.clearViewSyncTimer()
|
||||||
this.clearAutoRotateTimer()
|
this.clearAutoRotateTimer()
|
||||||
this.compassController.destroy()
|
this.compassController.destroy()
|
||||||
|
this.locationController.destroy()
|
||||||
this.renderer.destroy()
|
this.renderer.destroy()
|
||||||
this.mounted = false
|
this.mounted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleLocationUpdate(longitude: number, latitude: number, accuracyMeters: number | null): void {
|
||||||
|
const nextPoint: LonLatPoint = gcj02ToWgs84({ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentGpsPoint = nextPoint
|
||||||
|
this.currentGpsAccuracyMeters = accuracyMeters
|
||||||
|
|
||||||
|
const gpsWorldPoint = lonLatToWorldTile(nextPoint, this.state.zoom)
|
||||||
|
const gpsTileX = Math.floor(gpsWorldPoint.x)
|
||||||
|
const gpsTileY = Math.floor(gpsWorldPoint.y)
|
||||||
|
const gpsInsideMap = isTileWithinBounds(this.tileBoundsByZoom, this.state.zoom, gpsTileX, gpsTileY)
|
||||||
|
|
||||||
|
if (gpsInsideMap && !this.hasGpsCenteredOnce) {
|
||||||
|
this.hasGpsCenteredOnce = true
|
||||||
|
this.commitViewport({
|
||||||
|
centerTileX: gpsWorldPoint.x,
|
||||||
|
centerTileY: gpsWorldPoint.y,
|
||||||
|
tileTranslateX: 0,
|
||||||
|
tileTranslateY: 0,
|
||||||
|
gpsTracking: true,
|
||||||
|
gpsTrackingText: '持续定位进行中',
|
||||||
|
gpsCoordText: formatGpsCoordText(nextPoint, accuracyMeters),
|
||||||
|
}, `GPS定位成功,已定位到当前位置 (${this.buildVersion})`, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
gpsTracking: true,
|
||||||
|
gpsTrackingText: gpsInsideMap ? '持续定位进行中' : 'GPS不在当前地图范围内',
|
||||||
|
gpsCoordText: formatGpsCoordText(nextPoint, accuracyMeters),
|
||||||
|
statusText: gpsInsideMap ? `GPS位置已更新 (${this.buildVersion})` : `GPS位置超出当前地图范围 (${this.buildVersion})`,
|
||||||
|
}, true)
|
||||||
|
this.syncRenderer()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleGpsTracking(): void {
|
||||||
|
if (this.locationController.listening) {
|
||||||
|
this.locationController.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.locationController.start()
|
||||||
|
}
|
||||||
setStage(rect: MapEngineStageRect): void {
|
setStage(rect: MapEngineStageRect): void {
|
||||||
this.previewScale = 1
|
this.previewScale = 1
|
||||||
this.previewOriginX = rect.width / 2
|
this.previewOriginX = rect.width / 2
|
||||||
@@ -1272,8 +1380,8 @@ export class MapEngine {
|
|||||||
previewScale: this.previewScale || 1,
|
previewScale: this.previewScale || 1,
|
||||||
previewOriginX: this.previewOriginX || this.state.stageWidth / 2,
|
previewOriginX: this.previewOriginX || this.state.stageWidth / 2,
|
||||||
previewOriginY: this.previewOriginY || this.state.stageHeight / 2,
|
previewOriginY: this.previewOriginY || this.state.stageHeight / 2,
|
||||||
track: SAMPLE_TRACK_WGS84,
|
track: this.currentGpsTrack.length ? this.currentGpsTrack : SAMPLE_TRACK_WGS84,
|
||||||
gpsPoint: SAMPLE_GPS_WGS84,
|
gpsPoint: this.currentGpsPoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1643,6 +1751,18 @@ export class MapEngine {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
151
miniprogram/engine/sensor/locationController.ts
Normal file
151
miniprogram/engine/sensor/locationController.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
export interface LocationUpdate {
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
accuracy?: number
|
||||||
|
speed?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationControllerCallbacks {
|
||||||
|
onLocation: (update: LocationUpdate) => void
|
||||||
|
onStatus: (message: string) => void
|
||||||
|
onError: (message: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasLocationPermission(settings: WechatMiniprogram.AuthSetting): boolean {
|
||||||
|
const authSettings = settings as Record<string, boolean | undefined>
|
||||||
|
return !!authSettings['scope.userLocation']
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasBackgroundLocationPermission(settings: WechatMiniprogram.AuthSetting): boolean {
|
||||||
|
const authSettings = settings as Record<string, boolean | undefined>
|
||||||
|
return !!authSettings['scope.userLocationBackground']
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LocationController {
|
||||||
|
callbacks: LocationControllerCallbacks
|
||||||
|
listening: boolean
|
||||||
|
boundLocationHandler: ((result: WechatMiniprogram.OnLocationChangeCallbackResult) => void) | null
|
||||||
|
|
||||||
|
constructor(callbacks: LocationControllerCallbacks) {
|
||||||
|
this.callbacks = callbacks
|
||||||
|
this.listening = false
|
||||||
|
this.boundLocationHandler = null
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): void {
|
||||||
|
if (this.listening) {
|
||||||
|
this.callbacks.onStatus('后台持续定位进行中')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wx.getSetting({
|
||||||
|
success: (result) => {
|
||||||
|
const settings = result.authSetting || {}
|
||||||
|
if (hasBackgroundLocationPermission(settings)) {
|
||||||
|
this.startBackgroundLocation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasLocationPermission(settings)) {
|
||||||
|
this.requestBackgroundPermissionInSettings()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wx.authorize({
|
||||||
|
scope: 'scope.userLocation',
|
||||||
|
success: () => {
|
||||||
|
this.requestBackgroundPermissionInSettings()
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
this.requestBackgroundPermissionInSettings()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
const message = error && error.errMsg ? error.errMsg : 'getSetting 失败'
|
||||||
|
this.callbacks.onError(`GPS授权检查失败: ${message}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBackgroundPermissionInSettings(): void {
|
||||||
|
this.callbacks.onStatus('请在授权面板开启后台定位')
|
||||||
|
wx.openSetting({
|
||||||
|
success: (result) => {
|
||||||
|
const settings = result.authSetting || {}
|
||||||
|
if (hasBackgroundLocationPermission(settings)) {
|
||||||
|
this.startBackgroundLocation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.callbacks.onError('GPS启动失败: 未授予后台定位权限')
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
const message = error && error.errMsg ? error.errMsg : 'openSetting 失败'
|
||||||
|
this.callbacks.onError(`GPS启动失败: ${message}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
startBackgroundLocation(): void {
|
||||||
|
wx.startLocationUpdateBackground({
|
||||||
|
success: () => {
|
||||||
|
this.bindLocationListener()
|
||||||
|
this.listening = true
|
||||||
|
this.callbacks.onStatus('后台持续定位已启动')
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
const message = error && error.errMsg ? error.errMsg : 'startLocationUpdateBackground 失败'
|
||||||
|
this.callbacks.onError(`GPS启动失败: ${message}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
if (!this.listening) {
|
||||||
|
this.callbacks.onStatus('后台持续定位未启动')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof wx.offLocationChange === 'function' && this.boundLocationHandler) {
|
||||||
|
wx.offLocationChange(this.boundLocationHandler)
|
||||||
|
}
|
||||||
|
this.boundLocationHandler = null
|
||||||
|
|
||||||
|
wx.stopLocationUpdate({
|
||||||
|
complete: () => {
|
||||||
|
this.listening = false
|
||||||
|
this.callbacks.onStatus('后台持续定位已停止')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (typeof wx.offLocationChange === 'function' && this.boundLocationHandler) {
|
||||||
|
wx.offLocationChange(this.boundLocationHandler)
|
||||||
|
}
|
||||||
|
this.boundLocationHandler = null
|
||||||
|
|
||||||
|
if (this.listening) {
|
||||||
|
wx.stopLocationUpdate({ complete: () => {} })
|
||||||
|
this.listening = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindLocationListener(): void {
|
||||||
|
if (this.boundLocationHandler) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.boundLocationHandler = (result) => {
|
||||||
|
this.callbacks.onLocation({
|
||||||
|
latitude: result.latitude,
|
||||||
|
longitude: result.longitude,
|
||||||
|
accuracy: result.accuracy,
|
||||||
|
speed: result.speed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
wx.onLocationChange(this.boundLocationHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ type MapPageData = MapEngineViewState & {
|
|||||||
showDebugPanel: boolean
|
showDebugPanel: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const INTERNAL_BUILD_VERSION = 'map-build-82'
|
const INTERNAL_BUILD_VERSION = 'map-build-88'
|
||||||
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/qyds-001/game.json'
|
||||||
|
|
||||||
let mapEngine: MapEngine | null = null
|
let mapEngine: MapEngine | null = null
|
||||||
@@ -195,6 +195,12 @@ Page({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleToggleGpsTracking() {
|
||||||
|
if (mapEngine) {
|
||||||
|
mapEngine.handleToggleGpsTracking()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
handleToggleDebugPanel() {
|
handleToggleDebugPanel() {
|
||||||
this.setData({
|
this.setData({
|
||||||
showDebugPanel: !this.data.showDebugPanel,
|
showDebugPanel: !this.data.showDebugPanel,
|
||||||
@@ -221,6 +227,13 @@ Page({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,14 @@
|
|||||||
<text class="info-panel__label">Status</text>
|
<text class="info-panel__label">Status</text>
|
||||||
<text class="info-panel__value">{{statusText}}</text>
|
<text class="info-panel__value">{{statusText}}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="info-panel__row">
|
||||||
|
<text class="info-panel__label">GPS</text>
|
||||||
|
<text class="info-panel__value">{{gpsTrackingText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-panel__row info-panel__row--stack">
|
||||||
|
<text class="info-panel__label">GPS Coord</text>
|
||||||
|
<text class="info-panel__value">{{gpsCoordText}}</text>
|
||||||
|
</view>
|
||||||
<view class="control-row">
|
<view class="control-row">
|
||||||
<view class="control-chip control-chip--secondary" bindtap="handleToggleDebugPanel">{{showDebugPanel ? '隐藏调试' : '查看调试'}}</view>
|
<view class="control-chip control-chip--secondary" bindtap="handleToggleDebugPanel">{{showDebugPanel ? '隐藏调试' : '查看调试'}}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -148,6 +156,9 @@
|
|||||||
<view class="control-chip control-chip--primary" bindtap="handleRecenter">回到首屏</view>
|
<view class="control-chip control-chip--primary" bindtap="handleRecenter">回到首屏</view>
|
||||||
<view class="control-chip control-chip--secondary" bindtap="handleRotationReset">旋转归零</view>
|
<view class="control-chip control-chip--secondary" bindtap="handleRotationReset">旋转归零</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="control-row">
|
||||||
|
<view class="control-chip {{gpsTracking ? 'control-chip--active' : 'control-chip--secondary'}}" bindtap="handleToggleGpsTracking">{{gpsTracking ? '停止定位' : '开启定位'}}</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 class="control-chip {{orientationMode === 'north-up' ? 'control-chip--active' : ''}}" bindtap="handleSetNorthUpMode">北朝上</view>
|
<view class="control-chip {{orientationMode === 'north-up' ? 'control-chip--active' : ''}}" bindtap="handleSetNorthUpMode">北朝上</view>
|
||||||
@@ -166,3 +177,4 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,3 +59,45 @@ export function worldTileToLonLat(point: WorldTilePoint, zoom: number): LonLatPo
|
|||||||
lat: latRad * 180 / Math.PI,
|
lat: latRad * 180 / Math.PI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CHINA_AXIS = 6378245
|
||||||
|
const CHINA_EE = 0.00669342162296594323
|
||||||
|
|
||||||
|
function isOutsideChina(point: LonLatPoint): boolean {
|
||||||
|
return point.lon < 72.004 || point.lon > 137.8347 || point.lat < 0.8293 || point.lat > 55.8271
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformLat(x: number, y: number): number {
|
||||||
|
let result = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x))
|
||||||
|
result += (20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2 / 3
|
||||||
|
result += (20 * Math.sin(y * Math.PI) + 40 * Math.sin(y / 3 * Math.PI)) * 2 / 3
|
||||||
|
result += (160 * Math.sin(y / 12 * Math.PI) + 320 * Math.sin(y * Math.PI / 30)) * 2 / 3
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformLon(x: number, y: number): number {
|
||||||
|
let result = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x))
|
||||||
|
result += (20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2 / 3
|
||||||
|
result += (20 * Math.sin(x * Math.PI) + 40 * Math.sin(x / 3 * Math.PI)) * 2 / 3
|
||||||
|
result += (150 * Math.sin(x / 12 * Math.PI) + 300 * Math.sin(x / 30 * Math.PI)) * 2 / 3
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gcj02ToWgs84(point: LonLatPoint): LonLatPoint {
|
||||||
|
if (isOutsideChina(point)) {
|
||||||
|
return point
|
||||||
|
}
|
||||||
|
|
||||||
|
const dLat = transformLat(point.lon - 105, point.lat - 35)
|
||||||
|
const dLon = transformLon(point.lon - 105, point.lat - 35)
|
||||||
|
const radLat = point.lat / 180 * Math.PI
|
||||||
|
const magic = Math.sin(radLat)
|
||||||
|
const sqrtMagic = Math.sqrt(1 - CHINA_EE * magic * magic)
|
||||||
|
const latOffset = (dLat * 180) / ((CHINA_AXIS * (1 - CHINA_EE)) / (sqrtMagic * sqrtMagic * sqrtMagic) * Math.PI)
|
||||||
|
const lonOffset = (dLon * 180) / (CHINA_AXIS / sqrtMagic * Math.cos(radLat) * Math.PI)
|
||||||
|
|
||||||
|
return {
|
||||||
|
lon: point.lon - lonOffset,
|
||||||
|
lat: point.lat - latOffset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user