feat: initialize mini program map engine

This commit is contained in:
2026-03-19 15:58:48 +08:00
commit 03abe28d8c
49 changed files with 28584 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
export interface CompassHeadingControllerCallbacks {
onHeading: (headingDeg: number) => void
onError: (message: string) => void
}
type SensorSource = 'compass' | 'motion' | null
const ABSOLUTE_HEADING_CORRECTION = 0.24
function normalizeHeadingDeg(headingDeg: number): number {
const normalized = headingDeg % 360
return normalized < 0 ? normalized + 360 : normalized
}
function normalizeHeadingDeltaDeg(deltaDeg: number): number {
let normalized = deltaDeg
while (normalized > 180) {
normalized -= 360
}
while (normalized < -180) {
normalized += 360
}
return normalized
}
function interpolateHeadingDeg(currentDeg: number, targetDeg: number, factor: number): number {
return normalizeHeadingDeg(currentDeg + normalizeHeadingDeltaDeg(targetDeg - currentDeg) * factor)
}
export class CompassHeadingController {
callbacks: CompassHeadingControllerCallbacks
listening: boolean
source: SensorSource
compassCallback: ((result: WechatMiniprogram.OnCompassChangeCallbackResult) => void) | null
motionCallback: ((result: WechatMiniprogram.OnDeviceMotionChangeCallbackResult) => void) | null
absoluteHeadingDeg: number | null
pitchDeg: number | null
rollDeg: number | null
motionReady: boolean
compassReady: boolean
constructor(callbacks: CompassHeadingControllerCallbacks) {
this.callbacks = callbacks
this.listening = false
this.source = null
this.compassCallback = null
this.motionCallback = null
this.absoluteHeadingDeg = null
this.pitchDeg = null
this.rollDeg = null
this.motionReady = false
this.compassReady = false
}
start(): void {
if (this.listening) {
return
}
this.absoluteHeadingDeg = null
this.pitchDeg = null
this.rollDeg = null
this.motionReady = false
this.compassReady = false
this.source = null
if (typeof wx.startCompass === 'function' && typeof wx.onCompassChange === 'function') {
this.startCompassSource()
return
}
this.callbacks.onError('当前环境不支持罗盘方向监听')
}
stop(): void {
this.detachCallbacks()
if (this.motionReady) {
wx.stopDeviceMotionListening({ complete: () => {} })
}
if (this.compassReady) {
wx.stopCompass({ complete: () => {} })
}
this.listening = false
this.source = null
this.absoluteHeadingDeg = null
this.pitchDeg = null
this.rollDeg = null
this.motionReady = false
this.compassReady = false
}
destroy(): void {
this.stop()
}
startMotionSource(previousMessage: string): void {
if (typeof wx.startDeviceMotionListening !== 'function' || typeof wx.onDeviceMotionChange !== 'function') {
this.callbacks.onError(previousMessage)
return
}
const callback = (result: WechatMiniprogram.OnDeviceMotionChangeCallbackResult) => {
if (typeof result.alpha !== 'number' || Number.isNaN(result.alpha)) {
return
}
this.pitchDeg = typeof result.beta === 'number' && !Number.isNaN(result.beta)
? result.beta * 180 / Math.PI
: null
this.rollDeg = typeof result.gamma === 'number' && !Number.isNaN(result.gamma)
? result.gamma * 180 / Math.PI
: null
const alphaDeg = result.alpha * 180 / Math.PI
this.applyAbsoluteHeading(normalizeHeadingDeg(360 - alphaDeg), 'motion')
}
this.motionCallback = callback
wx.onDeviceMotionChange(callback)
wx.startDeviceMotionListening({
interval: 'ui',
success: () => {
this.motionReady = true
this.listening = true
this.source = 'motion'
},
fail: (res) => {
this.detachMotionCallback()
const motionMessage = res && res.errMsg ? res.errMsg : 'startDeviceMotionListening failed'
this.callbacks.onError(`${previousMessage}${motionMessage}`)
},
})
}
startCompassSource(): void {
const callback = (result: WechatMiniprogram.OnCompassChangeCallbackResult) => {
if (typeof result.direction !== 'number' || Number.isNaN(result.direction)) {
return
}
this.applyAbsoluteHeading(normalizeHeadingDeg(result.direction), 'compass')
}
this.compassCallback = callback
wx.onCompassChange(callback)
wx.startCompass({
success: () => {
this.compassReady = true
this.listening = true
this.source = 'compass'
},
fail: (res) => {
this.detachCompassCallback()
this.callbacks.onError(res && res.errMsg ? res.errMsg : 'startCompass failed')
},
})
}
applyAbsoluteHeading(headingDeg: number, source: 'compass' | 'motion'): void {
if (this.absoluteHeadingDeg === null) {
this.absoluteHeadingDeg = headingDeg
} else {
this.absoluteHeadingDeg = interpolateHeadingDeg(this.absoluteHeadingDeg, headingDeg, ABSOLUTE_HEADING_CORRECTION)
}
this.source = source
this.callbacks.onHeading(this.absoluteHeadingDeg)
}
detachCallbacks(): void {
this.detachMotionCallback()
this.detachCompassCallback()
}
detachMotionCallback(): void {
if (!this.motionCallback) {
return
}
if (typeof wx.offDeviceMotionChange === 'function') {
wx.offDeviceMotionChange(this.motionCallback)
}
this.motionCallback = null
}
detachCompassCallback(): void {
if (!this.compassCallback) {
return
}
if (typeof wx.offCompassChange === 'function') {
wx.offCompassChange(this.compassCallback)
}
this.compassCallback = null
}
}