| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- import { DEFAULT_MOCK_LOCATION_BRIDGE_URL, MockLocationBridge } from './mockLocationBridge'
- import { MockLocationSource } from './mockLocationSource'
- import { RealLocationSource } from './realLocationSource'
- import { type LocationSample, type LocationSourceCallbacks, type LocationSourceMode } from './locationSource'
- export interface LocationUpdate extends LocationSample {}
- export interface LocationControllerDebugState {
- sourceMode: LocationSourceMode
- sourceModeText: string
- listening: boolean
- mockBridgeConnected: boolean
- mockBridgeStatusText: string
- mockBridgeUrlText: string
- mockCoordText: string
- mockSpeedText: string
- }
- export interface LocationControllerCallbacks {
- onLocation: (update: LocationUpdate) => void
- onStatus: (message: string) => void
- onError: (message: string) => void
- onDebugStateChange?: (state: LocationControllerDebugState) => void
- }
- function formatSourceModeText(mode: LocationSourceMode): string {
- return mode === 'mock' ? '模拟定位' : '真实定位'
- }
- function formatMockCoordText(sample: LocationSample | null): string {
- if (!sample) {
- return '--'
- }
- return `${sample.latitude.toFixed(6)}, ${sample.longitude.toFixed(6)}`
- }
- function formatMockSpeedText(sample: LocationSample | null): string {
- if (!sample || !Number.isFinite(sample.speed)) {
- return '--'
- }
- return `${(Number(sample.speed) * 3.6).toFixed(1)} km/h`
- }
- function normalizeMockBridgeUrl(rawUrl: string): string {
- const trimmed = rawUrl.trim()
- if (!trimmed) {
- return DEFAULT_MOCK_LOCATION_BRIDGE_URL
- }
- let normalized = trimmed
- if (!/^wss?:\/\//i.test(normalized)) {
- normalized = `ws://${normalized.replace(/^\/+/, '')}`
- }
- if (!/\/mock-gps(?:\?.*)?$/i.test(normalized)) {
- normalized = normalized.replace(/\/+$/, '')
- normalized = `${normalized}/mock-gps`
- }
- return normalized
- }
- export class LocationController {
- callbacks: LocationControllerCallbacks
- realSource: RealLocationSource
- mockSource: MockLocationSource
- mockBridge: MockLocationBridge
- sourceMode: LocationSourceMode
- mockBridgeStatusText: string
- mockBridgeUrl: string
- constructor(callbacks: LocationControllerCallbacks) {
- this.callbacks = callbacks
- this.sourceMode = 'real'
- this.mockBridgeUrl = DEFAULT_MOCK_LOCATION_BRIDGE_URL
- this.mockBridgeStatusText = `未连接 (${this.mockBridgeUrl})`
- const sourceCallbacks: LocationSourceCallbacks = {
- onLocation: (sample) => {
- this.callbacks.onLocation(sample)
- this.emitDebugState()
- },
- onStatus: (message) => {
- this.callbacks.onStatus(message)
- this.emitDebugState()
- },
- onError: (message) => {
- this.callbacks.onError(message)
- this.emitDebugState()
- },
- }
- this.realSource = new RealLocationSource(sourceCallbacks)
- this.mockSource = new MockLocationSource(sourceCallbacks)
- this.mockBridge = new MockLocationBridge({
- onOpen: () => {
- this.mockBridgeStatusText = `已连接 (${this.mockBridge.url})`
- this.callbacks.onStatus('模拟定位源已连接')
- this.emitDebugState()
- },
- onClose: () => {
- this.mockBridgeStatusText = `未连接 (${this.mockBridge.url})`
- this.callbacks.onStatus('模拟定位源已断开')
- this.emitDebugState()
- },
- onError: (message) => {
- this.mockBridgeStatusText = `连接失败 (${this.mockBridge.url})`
- this.callbacks.onError(`模拟定位源错误: ${message}`)
- this.emitDebugState()
- },
- onSample: (sample) => {
- this.mockSource.pushSample(sample)
- this.emitDebugState()
- },
- })
- }
- get listening(): boolean {
- return this.sourceMode === 'mock' ? this.mockSource.active : this.realSource.active
- }
- getDebugState(): LocationControllerDebugState {
- return {
- sourceMode: this.sourceMode,
- sourceModeText: formatSourceModeText(this.sourceMode),
- listening: this.listening,
- mockBridgeConnected: this.mockBridge.connected,
- mockBridgeStatusText: this.mockBridgeStatusText,
- mockBridgeUrlText: this.mockBridgeUrl,
- mockCoordText: formatMockCoordText(this.mockSource.lastSample),
- mockSpeedText: formatMockSpeedText(this.mockSource.lastSample),
- }
- }
- start(): void {
- this.getActiveSource().start()
- this.emitDebugState()
- }
- stop(): void {
- this.getActiveSource().stop()
- this.emitDebugState()
- }
- destroy(): void {
- this.realSource.destroy()
- this.mockSource.destroy()
- this.mockBridge.destroy()
- this.emitDebugState()
- }
- setSourceMode(mode: LocationSourceMode): void {
- if (this.sourceMode === mode) {
- this.callbacks.onStatus(`${formatSourceModeText(mode)}已启用`)
- this.emitDebugState()
- return
- }
- const wasListening = this.listening
- if (wasListening) {
- this.getActiveSource().stop()
- }
- this.sourceMode = mode
- if (wasListening) {
- this.getActiveSource().start()
- } else {
- this.callbacks.onStatus(`已切换到${formatSourceModeText(mode)}`)
- }
- this.emitDebugState()
- }
- setMockBridgeUrl(url: string): void {
- this.mockBridgeUrl = normalizeMockBridgeUrl(url)
- if (this.mockBridge.connected || this.mockBridge.connecting) {
- this.mockBridgeStatusText = `已设置新地址,重连生效 (${this.mockBridgeUrl})`
- this.callbacks.onStatus('模拟定位源地址已更新,重连后生效')
- } else {
- this.mockBridgeStatusText = `未连接 (${this.mockBridgeUrl})`
- this.callbacks.onStatus('模拟定位源地址已更新')
- }
- this.emitDebugState()
- }
- connectMockBridge(url = DEFAULT_MOCK_LOCATION_BRIDGE_URL): void {
- if (this.mockBridge.connected || this.mockBridge.connecting) {
- this.callbacks.onStatus('模拟定位源已连接')
- this.emitDebugState()
- return
- }
- const targetUrl = normalizeMockBridgeUrl(url === DEFAULT_MOCK_LOCATION_BRIDGE_URL ? this.mockBridgeUrl : url)
- this.mockBridgeUrl = targetUrl
- this.mockBridgeStatusText = `连接中 (${targetUrl})`
- this.emitDebugState()
- this.callbacks.onStatus('模拟定位源连接中')
- this.mockBridge.connect(targetUrl)
- }
- disconnectMockBridge(): void {
- if (!this.mockBridge.connected && !this.mockBridge.connecting) {
- this.callbacks.onStatus('模拟定位源未连接')
- this.emitDebugState()
- return
- }
- this.mockBridge.disconnect()
- this.mockBridgeStatusText = `未连接 (${this.mockBridge.url})`
- this.callbacks.onStatus('模拟定位源已断开')
- this.emitDebugState()
- }
- getActiveSource(): RealLocationSource | MockLocationSource {
- return this.sourceMode === 'mock' ? this.mockSource : this.realSource
- }
- emitDebugState(): void {
- if (this.callbacks.onDebugStateChange) {
- this.callbacks.onDebugStateChange(this.getDebugState())
- }
- }
- }
|