Files
cmr-mini/miniprogram/engine/sensor/compassHeadingController.ts

205 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface CompassHeadingControllerCallbacks {
onHeading: (headingDeg: number) => void
onError: (message: string) => void
}
type SensorSource = 'compass' | 'motion' | null
const ABSOLUTE_HEADING_CORRECTION = 0.44
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
}
}