星轨算的方向传感器:DeviceSensor与方位罗盘

如果你是星轨摄影爱好者,推荐去鸿蒙应用市场搜一下**「星轨算」**,下载体验体验。方位罗盘帮你定位北极星,曝光计算器帮你规划参数,设备清单确保你不忘带东西。体验完再回来看这篇文章,你会更清楚方向传感器和方位罗盘背后是怎么实现的。


写在前面

大家好,我是一名写了十多年Web前端的老兵。从jQuery时代一路走到React/Vue,CSS3动画、requestAnimationFrame、Web Animation API这些都算是看家本领。去年开始转战鸿蒙生态,用ArkTS开发App,这一路踩了不少坑,也积累了不少心得。

很多人觉得"前端转鸿蒙"应该很容易——都是写UI嘛,组件化、状态管理、生命周期,概念都差不多。但真正上手之后你会发现,相似的地方让你觉得亲切,不同的地方让你抓狂

比如:

  • 传感器:Web里用DeviceOrientationEvent获取设备朝向,鸿蒙里用@ohos.sensor,API完全不同。
  • 权限管理:Web里传感器权限比较宽松,鸿蒙里需要声明ohos.permission.ACCELEROMETER等权限。
  • Canvas罗盘:绘制罗盘的逻辑在两个平台基本一致,但事件绑定方式不同。

接下来这篇文章,我会用"星轨算"的实际开发经历,带你看看鸿蒙的传感器API怎么实现方位罗盘——从方向传感器数据获取到Canvas罗盘绘制。


这篇文章聊什么

星轨算这个App,核心要解决两个问题:

  1. 方位罗盘:用设备传感器获取朝向,在Canvas上绘制罗盘
  2. 北极星定位:帮助用户找到北极星的位置

对应到HarmonyOS的API,主要涉及:

  • @ohos.sensor — 方向传感器
  • CanvasRenderingContext2D — 罗盘绘制

第一步:传感器数据获取

Web版本:

// React版本 - 获取设备方向
function useDeviceOrientation() {
  const [orientation, setOrientation] = useState({ alpha: 0, beta: 0, gamma: 0 });

  useEffect(() => {
    const handleOrientation = (event) => {
      setOrientation({
        alpha: event.alpha, // 0-360,设备绕Z轴旋转
        beta: event.beta,   // -180到180,设备绕X轴旋转
        gamma: event.gamma  // -90到90,设备绕Y轴旋转
      });
    };

    window.addEventListener('deviceorientation', handleOrientation);
    return () => window.removeEventListener('deviceorientation', handleOrientation);
  }, []);

  return orientation;
}

ArkTS版本:

import { sensor } from '@kit.SensorServiceKit';

@Entry
@Component
struct CompassPage {
  @State heading: number = 0       // 朝向角度(0-360)
  @State pitch: number = 0         // 俯仰角
  @State isSensorAvailable: boolean = false

  aboutToAppear() {
    this.startOrientationSensor()
  }

  aboutToDisappear() {
    this.stopOrientationSensor()
  }

  startOrientationSensor() {
    try {
      // 检查传感器是否可用
      this.isSensorAvailable = sensor.isSensorAvailable(sensor.SensorType.ORIENTATION)

      if (!this.isSensorAvailable) {
        console.error('方向传感器不可用')
        return
      }

      // 订阅方向传感器数据
      sensor.on(sensor.SensorType.ORIENTATION, (data: sensor.OrientationResponse) => {
        this.heading = data.x  // x是方位角,0-360
        this.pitch = data.y    // y是俯仰角
      }, { interval: 100000000 }) // 100ms更新一次
    } catch (err) {
      console.error(`传感器启动失败: ${err}`)
    }
  }

  stopOrientationSensor() {
    sensor.off(sensor.SensorType.ORIENTATION)
  }

  build() {
    Column() {
      Text('方位罗盘')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      // 罗盘Canvas
      Canvas(this.ctx)
        .width(250)
        .height(250)
        .onReady(() => {
          this.drawCompass()
        })

      // 朝向信息
      Text(`${Math.round(this.heading)}°`)
        .fontSize(48)
        .fontWeight(FontWeight.Bold)
        .fontColor('#10B981')
        .margin({ top: 16 })

      Text(this.getDirectionName(this.heading))
        .fontSize(16)
        .fontColor('#9CA3AF')
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#111827')
  }

  private getDirectionName(heading: number): string {
    const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
    const index = Math.round(heading / 45) % 8;
    return directions[index];
  }
}

第二步:Canvas罗盘绘制

罗盘的绘制逻辑:

private settings: RenderingContextSettings = new RenderingContextSettings(true)
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

private drawCompass() {
  const ctx = this.ctx
  const centerX = 125
  const centerY = 125
  const radius = 100

  ctx.clearRect(0, 0, 250, 250)

  // 画外圈
  ctx.strokeStyle = '#374151'
  ctx.lineWidth = 2
  ctx.beginPath()
  ctx.arc(centerX, centerY, radius, 0, Math.PI * 2)
  ctx.stroke()

  // 画刻度(每30度一个)
  for (let i = 0; i < 360; i += 30) {
    const rad = (i - 90) * Math.PI / 180
    const inner = i % 90 === 0 ? radius - 20 : radius - 10

    ctx.strokeStyle = i % 90 === 0 ? '#D1D5DB' : '#6B7280'
    ctx.lineWidth = i % 90 === 0 ? 2 : 1
    ctx.beginPath()
    ctx.moveTo(
      centerX + inner * Math.cos(rad),
      centerY + inner * Math.sin(rad)
    )
    ctx.lineTo(
      centerX + radius * Math.cos(rad),
      centerY + radius * Math.sin(rad)
    )
    ctx.stroke()
  }

  // 画方向标签
  const labels = [
    { text: 'N', angle: 0, color: '#EF4444' },
    { text: 'E', angle: 90, color: '#D1D5DB' },
    { text: 'S', angle: 180, color: '#D1D5DB' },
    { text: 'W', angle: 270, color: '#D1D5DB' }
  ]

  labels.forEach(label => {
    const rad = (label.angle - 90) * Math.PI / 180
    const x = centerX + (radius - 30) * Math.cos(rad)
    const y = centerY + (radius - 30) * Math.sin(rad)

    ctx.fillStyle = label.color
    ctx.font = 'bold 16px sans-serif'
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'
    ctx.fillText(label.text, x, y)
  })

  // 画指针(指向当前朝向)
  const needleAngle = (this.heading - 90) * Math.PI / 180
  ctx.strokeStyle = '#10B981'
  ctx.lineWidth = 3
  ctx.beginPath()
  ctx.moveTo(centerX, centerY)
  ctx.lineTo(
    centerX + 70 * Math.cos(needleAngle),
    centerY + 70 * Math.sin(needleAngle)
  )
  ctx.stroke()

  // 画中心点
  ctx.fillStyle = '#10B981'
  ctx.beginPath()
  ctx.arc(centerX, centerY, 5, 0, Math.PI * 2)
  ctx.fill()
}

第三步:北极星定位

北极星(Polaris)几乎正好在地球自转轴的延长线上,所以找到北极星就找到了正北方向。

// 北极星定位指南
const POLARIS_GUIDE = {
  name: '北极星(Polaris)',
  description: '位于小熊座,几乎正对地球自转轴',
  howToFind: [
    '找到北斗七星(大熊座的一部分)',
    '找到勺口两颗星(天璇和天枢)',
    '沿这两颗星连线向外延伸约5倍距离',
    '那颗亮星就是北极星'
  ],
  altitude: '等于你所在纬度的高度',
  importance: '星轨摄影的圆心就在北极星附近'
};

第四步:曝光计算

星轨摄影的曝光计算比普通摄影更复杂,因为要考虑"500法则":

// 500法则:最大曝光时间 = 500 / 焦距
function calculateMaxExposure(focalLength: number): number {
  return 500 / focalLength;
}

// 星轨叠加计算
interface StarTrailCalculation {
  singleExposure: number;   // 单张曝光时间(秒)
  numberOfShots: number;    // 拍摄张数
  totalDuration: number;    // 总时长(分钟)
  iso: number;
  aperture: string;
}

function calculateStarTrail(
  focalLength: number,
  trailLength: string, // 'short' | 'medium' | 'long'
  iso: number,
  aperture: string
): StarTrailCalculation {
  const maxExposure = calculateMaxExposure(focalLength);

  const durationMap = {
    'short': 30,   // 30分钟
    'medium': 60,  // 1小时
    'long': 120    // 2小时
  };

  const totalMinutes = durationMap[trailLength] || 60;
  const numberOfShots = Math.ceil(totalMinutes * 60 / maxExposure);

  return {
    singleExposure: Math.round(maxExposure),
    numberOfShots,
    totalDuration: totalMinutes,
    iso,
    aperture
  };
}

总结

这篇文章围绕"星轨算"的方向传感器功能,讲解了三个核心主题:

  1. 方向传感器:用@ohos.sensor获取设备朝向数据
  2. Canvas罗盘:绘制方位罗盘,显示当前朝向
  3. 北极星定位:帮助用户找到星轨摄影的圆心

方向传感器是鸿蒙里比较简单的传感器API,关键是理解x(方位角)、y(俯仰角)、z(翻滚角)三个值的含义。


如果你也是星轨摄影爱好者,希望这篇文章能帮你理解星轨算背后的传感器实现。去鸿蒙应用市场下载体验一下吧,有问题欢迎交流。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐