# [特殊字符] 指南针 — 鸿蒙ArkTS方向计算与方位跟踪系统

一、应用概述

1.1 应用简介

指南针(Compass)是一款模拟方向定位的实用工具,能够显示当前方向角度、方位文字、目标方位跟踪和方位保存功能。该应用以HarmonyOS NEXT的ArkTS框架为基础,深入展示了方向计算算法、角度映射、方位保存、旋转动画、传感器调用和视觉反馈等关键技术。

1.2 核心功能

功能模块 功能描述 技术实现 设计考量
方向显示 当前方向角度 角度计算+传感器 0-360°精确显示
方位文字 16方位文字 角度映射表 北/北东北/东北等
目标设定 设置目标方位 偏差计算 到达提醒
方位保存 保存方位记录 数组存储 带位置名称
校准功能 模拟校准 定时器动画 抖动动画效果
可视化 指针旋转动画 旋转动画+渐变 平滑过渡
磁场强度 显示磁场强度 传感器数据 检测干扰
水平仪 水平角度检测 加速度传感器 辅助功能

1.3 应用架构

指南针应用采用分层架构:

  1. UI表现层:指南针表盘、方向指针、角度/方位文字显示、目标方位设定、保存列表。
  2. 业务逻辑层:方向计算引擎、方位映射器、目标跟踪器、传感器管理器。
  3. 数据持久层:使用Preferences API保存方位记录和设置。

二、方向计算算法

2.1 方位映射

typescript 复制代码
class CompassEngine {
  // 16方位映射
  private static readonly DIRECTIONS_16 = [
    { name: '北', abbreviation: 'N', range: [0, 11.25] },
    { name: '北东北', abbreviation: 'NNE', range: [11.25, 33.75] },
    { name: '东北', abbreviation: 'NE', range: [33.75, 56.25] },
    { name: '东东北', abbreviation: 'ENE', range: [56.25, 78.75] },
    { name: '东', abbreviation: 'E', range: [78.75, 101.25] },
    { name: '东东南', abbreviation: 'ESE', range: [101.25, 123.75] },
    { name: '东南', abbreviation: 'SE', range: [123.75, 146.25] },
    { name: '南东南', abbreviation: 'SSE', range: [146.25, 168.75] },
    { name: '南', abbreviation: 'S', range: [168.75, 191.25] },
    { name: '南西南', abbreviation: 'SSW', range: [191.25, 213.75] },
    { name: '西南', abbreviation: 'SW', range: [213.75, 236.25] },
    { name: '西西南', abbreviation: 'WSW', range: [236.25, 258.75] },
    { name: '西', abbreviation: 'W', range: [258.75, 281.25] },
    { name: '西西北', abbreviation: 'WNW', range: [281.25, 303.75] },
    { name: '西北', abbreviation: 'NW', range: [303.75, 326.25] },
    { name: '北西北', abbreviation: 'NNW', range: [326.25, 348.75] },
    { name: '北', abbreviation: 'N', range: [348.75, 360] }
  ];
  
  // 获取方向文字
  static getDirectionText(degrees: number): { name: string; abbreviation: string } {
    const normalized = ((degrees % 360) + 360) % 360;
    for (const dir of this.DIRECTIONS_16) {
      if (normalized >= dir.range[0] && normalized < dir.range[1]) {
        return { name: dir.name, abbreviation: dir.abbreviation };
      }
    }
    return { name: '北', abbreviation: 'N' };
  }
  
  // 简化版:使用公式计算(更高效)
  static getDirectionSimple(degrees: number): string {
    const directions = ['北', '北东北', '东北', '东东北', '东', '东东南', '东南', 
      '南东南', '南', '南西南', '西南', '西西南', '西', '西西北', '西北', '北西北'];
    const index = Math.round(((degrees % 360) + 360) % 360 / 22.5) % 16;
    return directions[index];
  }
  
  // 计算两个方向的偏差角度
  static getBearingDifference(current: number, target: number): number {
    let diff = target - current;
    if (diff > 180) diff -= 360;
    if (diff < -180) diff += 360;
    return diff;
  }
  
  // 判断是否面向目标方向
  static isFacingTarget(current: number, target: number, threshold: number = 5): boolean {
    return Math.abs(this.getBearingDifference(current, target)) <= threshold;
  }
  
  // 角度平滑过渡(防止指针抖动)
  static smoothAngle(current: number, target: number, smoothingFactor: number = 0.3): number {
    let diff = target - current;
    if (diff > 180) diff -= 360;
    if (diff < -180) diff += 360;
    return current + diff * smoothingFactor;
  }
  
  // 磁偏角校正(根据地理位置)
  static applyDeclination(degrees: number, declination: number): number {
    return ((degrees + declination) % 360 + 360) % 360;
  }
}

2.2 目标方位跟踪

typescript 复制代码
class TargetTracker {
  private targetAngle: number | null = null;
  private targetName: string = '';
  private reached: boolean = false;
  private onReached: (() => void) | null = null;
  
  setTarget(angle: number, name: string): void {
    this.targetAngle = ((angle % 360) + 360) % 360;
    this.targetName = name;
    this.reached = false;
  }
  
  clearTarget(): void {
    this.targetAngle = null;
    this.targetName = '';
    this.reached = false;
  }
  
  update(currentAngle: number): TrackingInfo {
    if (this.targetAngle === null) {
      return { hasTarget: false, difference: 0, direction: '', isReached: false };
    }
    
    const diff = CompassEngine.getBearingDifference(currentAngle, this.targetAngle);
    const isReached = Math.abs(diff) <= 5;
    
    if (isReached && !this.reached) {
      this.reached = true;
      this.onReached?.();
    }
    
    let direction = '';
    if (diff > 0) direction = '← 向左转';
    else if (diff < 0) direction = '向右转 →';
    else direction = '✓ 正对目标';
    
    return {
      hasTarget: true,
      targetAngle: this.targetAngle,
      targetName: this.targetName,
      difference: diff,
      direction,
      isReached: isReached
    };
  }
  
  setOnReached(callback: () => void): void {
    this.onReached = callback;
  }
  
  getTarget(): { angle: number | null; name: string } {
    return { angle: this.targetAngle, name: this.targetName };
  }
}

interface TrackingInfo {
  hasTarget: boolean;
  targetAngle?: number;
  targetName?: string;
  difference: number;
  direction: string;
  isReached: boolean;
}

三、方位保存管理

3.1 方位记录

typescript 复制代码
interface CompassRecord {
  id: string;
  name: string;
  angle: number;
  direction: string;
  latitude?: number;
  longitude?: number;
  createdAt: number;
  note: string;
}

class CompassRecordManager {
  private records: CompassRecord[] = [];
  private prefs: preferences.Preferences | null = null;
  
  async init(context: Context): Promise<void> {
    this.prefs = await preferences.getPreferences(context, 'compass_prefs');
    await this.load();
  }
  
  addRecord(angle: number, name: string, note: string = ''): CompassRecord {
    const record: CompassRecord = {
      id: Date.now().toString(),
      name,
      angle: ((angle % 360) + 360) % 360,
      direction: CompassEngine.getDirectionSimple(angle),
      createdAt: Date.now(),
      note
    };
    this.records.push(record);
    this.save();
    return record;
  }
  
  deleteRecord(id: string): void {
    this.records = this.records.filter(r => r.id !== id);
    this.save();
  }
  
  getRecords(): CompassRecord[] {
    return [...this.records].sort((a, b) => b.createdAt - a.createdAt);
  }
  
  async load(): Promise<void> {
    if (!this.prefs) return;
    const json = await this.prefs.get('compass_records', '[]');
    try { this.records = JSON.parse(json); } catch { this.records = []; }
  }
  
  async save(): Promise<void> {
    if (!this.prefs) return;
    await this.prefs.put('compass_records', JSON.stringify(this.records));
    await this.prefs.flush();
  }
}

四、UI交互设计

4.1 指南针表盘

typescript 复制代码
@Component
struct CompassDial {
  @Link currentAngle: number;
  @Link targetAngle: number | null;
  @State displayAngle: number = 0;
  @State smoothAngle: number = 0;
  
  private readonly COMPASS_RING_COLORS = ['#E53935', '#FF9800', '#FFC107', '#4CAF50', '#2196F3', '#1565C0', '#9C27B0', '#E91E63'];
  
  aboutToAppear(): void {
    this.smoothAngle = this.currentAngle;
  }
  
  build() {
    Stack() {
      // 外圈刻度
      Circle()
        .width(280)
        .height(280)
        .fill('none')
        .stroke('#333333')
        .strokeWidth(2)
      
      // 刻度线
      ForEach(Array.from({ length: 72 }, (_, i) => i), (i: number) => {
        const angle = i * 5;
        const isMajor = i % 9 === 0;
        const length = isMajor ? 20 : 10;
        const width = isMajor ? 3 : 1;
        
        // 使用旋转定位
        Path()
          .width(isMajor ? 4 : 2)
          .height(length)
          .fill(isMajor ? '#333' : '#999')
          .rotate({ angle })
      })
      
      // 基本方位文字
      ForEach([
        { text: 'N', angle: 0, color: '#E53935' },
        { text: 'E', angle: 90, color: '#333333' },
        { text: 'S', angle: 180, color: '#333333' },
        { text: 'W', angle: 270, color: '#333333' }
      ], (dir: { text: string; angle: number; color: string }) => {
        Text(dir.text)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor(dir.color)
          .position({
            x: 140 - 8 + Math.sin(dir.angle * Math.PI / 180) * 120,
            y: 140 - 12 - Math.cos(dir.angle * Math.PI / 180) * 120
          })
      })
      
      // 指针
      Path()
        .width(4)
        .height(120)
        .fill('#E53935')
        .rotate({ angle: this.smoothAngle })
        .position({ x: 138, y: 20 })
      
      // 中心圆
      Circle()
        .width(20)
        .height(20)
        .fill('#333333')
      
      // 中心点
      Circle()
        .width(8)
        .height(8)
        .fill('#E53935')
    }
    .width(280)
    .height(280)
    .rotate({ angle: -this.smoothAngle })
    .animation({ duration: 200, curve: Curve.EaseOut })
    .padding(20)
  }
}

五、总结

5.1 核心技术要点

  1. 16方位映射算法:将360°角度精确映射到16个方位,支持简称和全称。
  2. 角度平滑过渡:使用平滑因子算法防止指针抖动,提升视觉体验。
  3. 目标方位跟踪:计算当前方向与目标方向的偏差,提供转向指引。
  4. 磁偏角校正:支持根据地理位置进行磁偏角校正。
  5. 方位记录管理:保存和管理已记录的方位数据。
  6. 旋转动画:使用ArkTS动画API实现指针平滑旋转。

5.2 扩展方向

  1. 真实传感器调用:接入设备磁力计和加速度计获取真实方向数据。
  2. 地图集成:与地图应用联动,显示目标方位在地图上的位置。
  3. 导航路线:记录行进路线,支持返航导航。
  4. 磁场检测:检测周围磁场强度,识别电磁干扰。
  5. 水平仪:集成水平角度检测功能。

5.3 核心代码量统计

模块 核心代码行数 接口数 组件数
方向计算引擎 120 6 -
目标跟踪器 70 5 -
方位记录管理器 80 5 -
UI组件 250 3 4
总计 520 19 4

相关推荐
donk13141 小时前
React Native for OpenHarmony 实战:三方库 react-native-toast-message 的鸿蒙化适配指南
react native·react.js·音视频·harmonyos
Flutter_OH2 小时前
Flutter OHOS Dart 堆栈符号化
harmonyos
Flutter_OH2 小时前
Flutter OHOS Flutter 多设备自适应 DPI 指导文档
harmonyos
SuperHeroWu72 小时前
【HarmonyOS 7】 应用 Agent 接入云 A2A 与端 A2A 的实践详解
ai·harmonyos·小艺
用户29469405448163 小时前
个人开发者上架鸿蒙 AI 应用,最后倒在安全评估实名制这一格
harmonyos·deepseek
Keya3 小时前
HarmonyOS 开发实战:用 util.Aspect 玩转 AOP 切面编程
harmonyos
威哥爱编程4 小时前
HarmonyOS 7 Agent 开发实战 03:Tool Calling 让 AI 查实时订单与库存
agent·harmonyos
威哥爱编程4 小时前
HarmonyOS 7 Agent 开发实战 02:Agent 工程脚手架与对话 UI 骨架
agent·harmonyos
Georgewu12 小时前
【HarmonyOS 7】 HarmonyOS 应用 Skill创建和编排详解
harmonyos
Georgewu15 小时前
【HarmonyOS 7】 应用 Agent 接入云 A2A 与端 A2A 的实践详解
harmonyos