# [特殊字符] 指南针 — 鸿蒙ArkTS方向计算与方位跟踪系统
·
一、应用概述
1.1 应用简介
指南针(Compass)是一款模拟方向定位的实用工具,能够显示当前方向角度、方位文字、目标方位跟踪和方位保存功能。该应用以HarmonyOS NEXT的ArkTS框架为基础,深入展示了方向计算算法、角度映射、方位保存、旋转动画、传感器调用和视觉反馈等关键技术。
1.2 核心功能
| 功能模块 | 功能描述 | 技术实现 | 设计考量 |
|---|---|---|---|
| 方向显示 | 当前方向角度 | 角度计算+传感器 | 0-360°精确显示 |
| 方位文字 | 16方位文字 | 角度映射表 | 北/北东北/东北等 |
| 目标设定 | 设置目标方位 | 偏差计算 | 到达提醒 |
| 方位保存 | 保存方位记录 | 数组存储 | 带位置名称 |
| 校准功能 | 模拟校准 | 定时器动画 | 抖动动画效果 |
| 可视化 | 指针旋转动画 | 旋转动画+渐变 | 平滑过渡 |
| 磁场强度 | 显示磁场强度 | 传感器数据 | 检测干扰 |
| 水平仪 | 水平角度检测 | 加速度传感器 | 辅助功能 |
1.3 应用架构
指南针应用采用分层架构:
- UI表现层:指南针表盘、方向指针、角度/方位文字显示、目标方位设定、保存列表。
- 业务逻辑层:方向计算引擎、方位映射器、目标跟踪器、传感器管理器。
- 数据持久层:使用Preferences API保存方位记录和设置。
二、方向计算算法
2.1 方位映射
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 目标方位跟踪
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 方位记录
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 指南针表盘
@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 核心技术要点
- 16方位映射算法:将360°角度精确映射到16个方位,支持简称和全称。
- 角度平滑过渡:使用平滑因子算法防止指针抖动,提升视觉体验。
- 目标方位跟踪:计算当前方向与目标方向的偏差,提供转向指引。
- 磁偏角校正:支持根据地理位置进行磁偏角校正。
- 方位记录管理:保存和管理已记录的方位数据。
- 旋转动画:使用ArkTS动画API实现指针平滑旋转。
5.2 扩展方向
- 真实传感器调用:接入设备磁力计和加速度计获取真实方向数据。
- 地图集成:与地图应用联动,显示目标方位在地图上的位置。
- 导航路线:记录行进路线,支持返航导航。
- 磁场检测:检测周围磁场强度,识别电磁干扰。
- 水平仪:集成水平角度检测功能。
5.3 核心代码量统计
| 模块 | 核心代码行数 | 接口数 | 组件数 |
|---|---|---|---|
| 方向计算引擎 | 120 | 6 | - |
| 目标跟踪器 | 70 | 5 | - |
| 方位记录管理器 | 80 | 5 | - |
| UI组件 | 250 | 3 | 4 |
| 总计 | 520 | 19 | 4 |
更多推荐

所有评论(0)