| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- export interface CompassHeadingControllerCallbacks {
- onHeading: (headingDeg: number) => void
- onError: (message: string) => void
- }
- type SensorSource = 'compass' | 'motion' | null
- export type CompassTuningProfile = 'smooth' | 'balanced' | 'responsive'
- const HEADING_CORRECTION_BY_PROFILE: Record<CompassTuningProfile, number> = {
- smooth: 0.3,
- balanced: 0.4,
- responsive: 0.54,
- }
- 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
- tuningProfile: CompassTuningProfile
- 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
- this.tuningProfile = 'balanced'
- }
- 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()
- }
- setTuningProfile(profile: CompassTuningProfile): void {
- this.tuningProfile = profile
- }
- 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
- : null
- this.rollDeg = typeof result.gamma === 'number' && !Number.isNaN(result.gamma)
- ? result.gamma
- : null
- this.applyAbsoluteHeading(normalizeHeadingDeg(360 - result.alpha), '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 {
- const headingCorrection = HEADING_CORRECTION_BY_PROFILE[this.tuningProfile]
- if (this.absoluteHeadingDeg === null) {
- this.absoluteHeadingDeg = headingDeg
- } else {
- this.absoluteHeadingDeg = interpolateHeadingDeg(this.absoluteHeadingDeg, headingDeg, headingCorrection)
- }
- 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
- }
- }
|