[鸿蒙从零到一] HarmonyOS 地图、定位与传感器能力实战:从位置获取到运动感知

HarmonyOS 地图、定位与传感器能力实战:从位置获取到运动感知

在 HarmonyOS 应用开发中,地图、定位与传感器能力是构建位置服务、运动健康、AR 导航等场景的基础。本文将从定位权限申请、位置获取、地图集成、传感器订阅四个维度展开,涵盖单次定位、持续跟踪、地图标注、加速度计与陀螺仪的实战用法。


一、定位权限与隐私合规

HarmonyOS 的定位能力需要申请 ohos.permission.APPROXIMATELY_LOCATION(粗略位置)或 ohos.permission.LOCATION(精确位置)权限,前者精度约 5 公里,后者可达米级。

1. 配置权限

module.json5 中声明:

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION"
      }
    ]
  }
}

2. 动态申请

typescript 复制代码
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';

async function requestLocationPermission(): Promise<boolean> {
  const atManager = abilityAccessCtrl.createAtManager();
  const bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
  const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleFlags);
  const tokenID = bundleInfo.appInfo.accessTokenId;

  const permissions = ['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'];
  const grantStatus = await atManager.requestPermissionsFromUser(getContext(), permissions);
  return grantStatus.authResults.every(result => result === 0);
}

二、位置获取:单次定位与连续跟踪

1. 单次定位

适用于签到、地址选择等场景:

typescript 复制代码
import geoLocationManager from '@ohos.geoLocationManager';

async function getCurrentLocation() {
  const request: geoLocationManager.CurrentLocationRequest = {
    priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
    scenario: geoLocationManager.LocationRequestScenario.UNSET,
    maxAccuracy: 0
  };

  try {
    const location = await geoLocationManager.getCurrentLocation(request);
    console.info(`当前位置: ${location.latitude}, ${location.longitude}`);
    console.info(`精度: ${location.accuracy}m, 时间: ${location.time}`);
    return location;
  } catch (err) {
    console.error(`定位失败: ${err.message}`);
    return null;
  }
}

2. 连续定位

适用于导航、跑步轨迹记录:

typescript 复制代码
let locationRequest: geoLocationManager.LocationRequest = {
  priority: geoLocationManager.LocationRequestPriority.ACCURACY,
  scenario: geoLocationManager.LocationRequestScenario.NAVIGATION,
  timeInterval: 1, // 1 秒上报一次
  distanceInterval: 10, // 移动 10 米上报
  maxAccuracy: 0
};

function startTracking() {
  geoLocationManager.on('locationChange', locationRequest, (location) => {
    console.info(`实时位置: ${location.latitude}, ${location.longitude}`);
    // 更新 UI 或保存轨迹点
  });
}

function stopTracking() {
  geoLocationManager.off('locationChange');
}

3. 后台定位

需要申请 ohos.permission.LOCATION_IN_BACKGROUND 并配置后台任务:

typescript 复制代码
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';

async function startBackgroundLocation() {
  await backgroundTaskManager.startBackgroundRunning(getContext(), 
    backgroundTaskManager.BackgroundMode.LOCATION, 
    { notificationId: 1, notificationContent: '正在记录运动轨迹' }
  );
  startTracking();
}

function stopBackgroundLocation() {
  stopTracking();
  backgroundTaskManager.stopBackgroundRunning(getContext());
}

三、地图集成:华为地图服务(Map Kit)

HarmonyOS 推荐使用华为地图服务(Map Kit)显示地图、标注点位、绘制路线。

1. 配置依赖

oh-package.json5 中添加:

json 复制代码
{
  "dependencies": {
    "@hmscore/map-kit": "^1.0.0"
  }
}

2. 显示地图

typescript 复制代码
import mapCommon from '@hmscore/map-kit';

@Entry
@Component
struct MapPage {
  private mapController: mapCommon.MapComponentController = new mapCommon.MapComponentController();

  build() {
    Column() {
      MapComponent({
        mapOptions: {
          position: { latitude: 39.9042, longitude: 116.4074 },
          zoom: 12
        },
        mapCallback: (err, mapController) => {
          if (!err) {
            this.mapController = mapController;
          }
        }
      })
        .width('100%')
        .height('100%')
    }
  }
}

3. 添加标注点

typescript 复制代码
addMarker() {
  const markerOptions: mapCommon.MarkerOptions = {
    position: { latitude: 39.9042, longitude: 116.4074 },
    title: '天安门',
    snippet: '北京市中心'
  };
  this.mapController.addMarker(markerOptions);
}

4. 绘制路线

typescript 复制代码
drawPolyline(points: Array<{ latitude: number, longitude: number }>) {
  const polylineOptions: mapCommon.PolylineOptions = {
    points: points,
    color: 0xFF0000FF,
    width: 5
  };
  this.mapController.addPolyline(polylineOptions);
}

四、传感器能力:加速度计、陀螺仪与方向传感器

1. 订阅加速度计

适用于计步、摇一摇、碰撞检测:

typescript 复制代码
import sensor from '@ohos.sensor';

function startAccelerometer() {
  sensor.on(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
    console.info(`加速度: x=${data.x}, y=${data.y}, z=${data.z}`);
    // 检测摇一摇:若 |x| > 15 或 |y| > 15 或 |z| > 15 触发
    if (Math.abs(data.x) > 15 || Math.abs(data.y) > 15 || Math.abs(data.z) > 15) {
      console.info('检测到摇一摇');
    }
  }, { interval: 100000000 }); // 100ms
}

function stopAccelerometer() {
  sensor.off(sensor.SensorId.ACCELEROMETER);
}

2. 订阅陀螺仪

适用于 VR、AR、手势识别:

typescript 复制代码
function startGyroscope() {
  sensor.on(sensor.SensorId.GYROSCOPE, (data: sensor.GyroscopeResponse) => {
    console.info(`角速度: x=${data.x}, y=${data.y}, z=${data.z}`);
  }, { interval: 100000000 });
}

function stopGyroscope() {
  sensor.off(sensor.SensorId.GYROSCOPE);
}

3. 订阅方向传感器

适用于指南针、地图旋转:

typescript 复制代码
function startOrientation() {
  sensor.on(sensor.SensorId.ORIENTATION, (data: sensor.OrientationResponse) => {
    console.info(`方向角: alpha=${data.alpha}, beta=${data.beta}, gamma=${data.gamma}`);
    // alpha: 0-360° (方位角), beta: -180-180° (俯仰角), gamma: -90-90° (翻滚角)
  }, { interval: 200000000 });
}

function stopOrientation() {
  sensor.off(sensor.SensorId.ORIENTATION);
}

五、实战案例:跑步轨迹记录与地图展示

1. 架构设计

  • LocationService :封装定位逻辑,提供 start/stop/getTrack 接口
  • MapViewModel:管理地图状态、轨迹点、实时位置
  • RunPage:展示地图、控制按钮、统计信息(距离、配速、时长)

2. LocationService 封装

typescript 复制代码
import geoLocationManager from '@ohos.geoLocationManager';

export class LocationService {
  private trackPoints: Array<geoLocationManager.Location> = [];
  private isTracking = false;

  start() {
    if (this.isTracking) return;
    this.isTracking = true;
    this.trackPoints = [];

    const request: geoLocationManager.LocationRequest = {
      priority: geoLocationManager.LocationRequestPriority.ACCURACY,
      scenario: geoLocationManager.LocationRequestScenario.SPORT,
      timeInterval: 2,
      distanceInterval: 5
    };

    geoLocationManager.on('locationChange', request, (location) => {
      this.trackPoints.push(location);
      console.info(`记录点位: ${location.latitude}, ${location.longitude}`);
    });
  }

  stop() {
    if (!this.isTracking) return;
    geoLocationManager.off('locationChange');
    this.isTracking = false;
  }

  getTrack() {
    return this.trackPoints.map(p => ({ latitude: p.latitude, longitude: p.longitude }));
  }

  getTotalDistance(): number {
    let distance = 0;
    for (let i = 1; i < this.trackPoints.length; i++) {
      distance += this.calculateDistance(this.trackPoints[i - 1], this.trackPoints[i]);
    }
    return distance;
  }

  private calculateDistance(p1: geoLocationManager.Location, p2: geoLocationManager.Location): number {
    const R = 6371e3; // 地球半径(米)
    const φ1 = p1.latitude * Math.PI / 180;
    const φ2 = p2.latitude * Math.PI / 180;
    const Δφ = (p2.latitude - p1.latitude) * Math.PI / 180;
    const Δλ = (p2.longitude - p1.longitude) * Math.PI / 180;

    const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
      Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
  }
}

3. RunPage 界面

typescript 复制代码
import mapCommon from '@hmscore/map-kit';
import { LocationService } from './LocationService';

@Entry
@Component
struct RunPage {
  @State isRunning: boolean = false;
  @State distance: number = 0;
  @State duration: number = 0;
  private locationService = new LocationService();
  private mapController: mapCommon.MapComponentController | null = null;
  private timer: number = -1;

  build() {
    Stack() {
      MapComponent({
        mapOptions: { zoom: 15 },
        mapCallback: (err, controller) => {
          if (!err) this.mapController = controller;
        }
      })
        .width('100%')
        .height('100%')

      Column() {
        Text(`距离: ${(this.distance / 1000).toFixed(2)} km`)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        Text(`时长: ${Math.floor(this.duration / 60)}:${(this.duration % 60).toString().padStart(2, '0')}`)
          .fontSize(18)

        Button(this.isRunning ? '结束' : '开始')
          .onClick(() => this.toggleRun())
          .margin({ top: 20 })
      }
      .alignItems(HorizontalAlign.Center)
      .justifyContent(FlexAlign.End)
      .width('100%')
      .height('100%')
      .padding(20)
    }
  }

  toggleRun() {
    if (this.isRunning) {
      this.locationService.stop();
      clearInterval(this.timer);
      const track = this.locationService.getTrack();
      this.mapController?.addPolyline({ points: track, color: 0xFF0000FF, width: 5 });
      this.distance = this.locationService.getTotalDistance();
    } else {
      this.locationService.start();
      this.duration = 0;
      this.timer = setInterval(() => {
        this.duration++;
      }, 1000);
    }
    this.isRunning = !this.isRunning;
  }
}

六、性能与功耗优化

1. 定位策略

  • 单次定位 :使用 FIRST_FIX 优先级,获取后立即停止
  • 导航场景 :使用 ACCURACY 优先级 + NAVIGATION 场景
  • 省电场景 :使用 LOW_POWER 优先级 + 更大的 timeInterval

2. 传感器采样率

  • 加速度计:游戏场景 20ms,计步场景 200ms
  • 陀螺仪:VR 场景 10ms,普通场景 100ms
  • 及时调用 sensor.off() 停止订阅

3. 地图渲染

  • 限制同屏标注点数量(< 100 个)
  • 使用聚合标注(Cluster)处理密集点位
  • 避免频繁调用 addPolyline,批量更新路线

七、常见问题

1. 定位失败返回 201

原因 :未授予定位权限

解决 :检查 requestPermissionsFromUser 返回值,引导用户手动开启

2. 后台定位被系统杀死

原因 :未申请后台任务或通知未显示

解决 :确保调用 startBackgroundRunning 并显示前台通知

3. 地图标注点击无响应

原因 :未设置 MapComponent 的点击回调

解决 :在 mapCallback 中注册 onMarkerClick 监听器

4. 传感器数据抖动严重

原因 :硬件噪声或采样率过高

解决:应用低通滤波器或卡尔曼滤波平滑数据


总结

本文覆盖了 HarmonyOS 地图、定位与传感器能力的核心要点:

  • 定位 :单次定位用 getCurrentLocation,连续跟踪用 on('locationChange'),后台场景需配合后台任务
  • 地图:使用 Map Kit 显示地图、添加标注、绘制路线
  • 传感器:加速度计检测运动状态,陀螺仪获取角速度,方向传感器提供方位角
  • 优化:选择合适的定位策略、传感器采样率,及时停止订阅以降低功耗

掌握这些能力后,你可以构建位置签到、运动轨迹、AR 导航、体感游戏等丰富的应用场景,为用户提供更智能的空间感知与交互体验。

相关推荐
大锅盖12 小时前
Web 工单要调用相机,第一步不是打开取景框,而是建立能力门禁
前端·数码相机·harmonyos
fthux2 小时前
不必下载整个仓库:GitZip Pro 让 GitHub 文件与文件夹批量下载更简单
前端·chrome·ai·edge·开源·github·firefox
奥莱维2 小时前
【无标题】
java·前端·javascript
用户921080262862 小时前
0. 为什么我们的项目选择 Cesium:从三维地图、离线部署到工程代价
前端
阿懂在掘金2 小时前
同一份弹窗我重构了三次:从 v-model 地狱到路由式调用,终于治好了模板臃肿
前端·vue.js·前端框架
悟空瞎说2 小时前
从 CRA 到 Vite:含 Cesium 的真实项目迁移实战记录
前端
悟空瞎说2 小时前
Vite 中零配置接入 Cesium.js:vite-plugin-cesium-engine 深度解析
前端
To_OC2 小时前
后端接口还没交付,前端如何独立把整套业务跑通
前端·react.js·全栈
王琦03182 小时前
WEB服务
前端