💭

鴻蒙運動開発:屋外運動における歩頻と歩幅の計算、およびマップルートの描画

に公開

前言

屋外運動において、歩頻(1分間の歩数)と歩幅(1歩あたりの距離)は、運動の効率と強度を測定するための重要な指標です。ランニング愛好家もフィットネス愛好家も、これらのデータを理解することで、運動パフォーマンスを最適化するだけでなく、運動傷害を効果的に防止することができます。しかし、鴻蒙システムでどのように正確に歩頻と歩幅を計算し、運動の軌跡をリアルタイムでマップに表示することができるでしょうか。この記事では、実際の開発経験を基に、センサデータの収集からコアアルゴリズムの実装、マップルートの描画まで、屋外運動データの正確な計算と可視化の全プロセスを深く解析し、屋外運動データの正確な計算と可視化の方法をステップバイステップで学んでいただきます。

Image description
上記の画像のリンクは、ネットワークの問題のために解析に失敗しました。この問題は、リンク自体の合法性やネットワーク状況に関連する可能性があります。ウェブページのリンクが正しいことを確認し、適宜再度お試しください。

一、歩頻と歩幅:運動データの重要な指標

歩頻と歩幅は、運動データの2つの重要な指標であり、運動のリズムと効率を直感的に反映することができます。

  1. 歩頻:運動のリズム

歩頻とは、1分間の歩数を指し、ランニングやウォーキングのスピードとリズムを測定するのに一般的に使われます。高い歩頻は通常、より速い運動スピードを意味しますが、過度の疲労によって効率が低下する可能性もあります。理想的な歩頻は個人によって異なりますが、一般的にランニング時の歩頻は160-180歩/分の間が理想的です。初心者にとっては、高い歩頻を追求するよりも安定した歩頻を維持することが重要です。なぜなら、高い歩頻は身体の疲労と怪我を引き起こす可能性があるからです。

  1. 歩幅:運動の効率

歩幅とは、1歩あたりの長さ、つまり両足の間の距離を指します。大きな歩幅は運動スピードを上げる可能性がありますが、大きすぎる歩幅は身体の重心を不安定にし、怪我のリスクを増やす可能性があります。したがって、運動効率と安全性を高めるために、歩幅を適切にコントロールすることが非常に重要です。一般的に、歩幅の大きさは個人の身体条件や運動習慣に応じて調整する必要があります。例えば、背の高い人は大きな歩幅を持つかもしれませんが、大きすぎる歩幅は膝や足首に過度の圧力を与える可能性があります。

二、鴻蒙歩数センサー:データ収集のコアツール

鴻蒙システムでは、組み込みの歩数センサー(Pedometer)を利用して、運動中の歩数データを取得することができます。歩数センサーは、ユーザーの歩をリアルタイムで監視し、正確な歩数統計を提供することができます。以下は、歩数センサーの使用方法と重要なコードの解説です。

  1. 歩数センサーの初期化と権限申請

歩数センサーを使用する前に、運動権限を申請し、センサー サービスを初期化する必要があります。以下が関連コードです。

import { sensor } from '@kit.SensorServiceKit';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import { common } from '@kit.AbilityKit';

export class StepCounterService {
  private static instance: StepCounterService;
  private stepCount: number = 0; // 現在の累積歩数
  private initialStepCount: number = 0; // 初期歩数
  private isMonitoring: boolean = false; // 監視中かどうか
  private listeners: Array<(data: StepData) => void> = [];
  private context: common.UIAbilityContext;

  private constructor(context: common.UIAbilityContext) {
    this.context = context;
  }

  public static getInstance(context: common.UIAbilityContext): StepCounterService {
    if (!StepCounterService.instance) {
      StepCounterService.instance = new StepCounterService(context);
    }
    return StepCounterService.instance;
  }

  // 運動権限を申請する
  private async requestMotionPermission(): Promise<boolean> {
    const atManager = abilityAccessCtrl.createAtManager();
    try {
      const result = await atManager.requestPermissionsFromUser(
        this.context,
        ['ohos.permission.ACTIVITY_MOTION']
      );
      return result.permissions[0] === 'ohos.permission.ACTIVITY_MOTION' &&
        result.authResults[0] === 0;
    } catch (err) {
      console.error('権限申請に失敗しました:', err);
      return false;
    }
  }
}

上記のコードでは、abilityAccessCtrlを使用して運動権限を申請し、StepCounterServiceクラスに歩数センサーの初期化ロジックを実装しました。権限申請は、センサーが正常に動作するための重要なステップです。ユーザーが関連する権限を付与していない場合、センサーは正常に動作しません。

  1. 歩数データのリアルタイム監視

センサーを初期化した後、歩数の変化を監視し、歩数データをリアルタイムで更新する必要があります。以下が歩数の変化を監視する重要なコードです。

public async startStepCounter(): Promise<void> {
  if (this.isMonitoring) return;

  const hasPermission = await this.requestMotionPermission();
  if (!hasPermission) {
    throw new Error('運動センサー権限が付与されていません');
  }

  try {
    sensor.on(sensor.SensorId.PEDOMETER, (data: sensor.PedometerResponse) => {
      this.deviceTotalSteps = data.steps;

      if (this.initialStepCount === 0) {
        this.initialStepCount = data.steps;
      }

      const deltaSteps = this.deviceTotalSteps - this.initialStepCount;

      if (this.isPaused) {
        // 一時停止中は、デバイスの総歩数のみを更新し、ビジネス歩数は変更しない
      } else {
        // すべての一時停止区間の総歩数を計算する
        const totalPausedSteps = this.pausedIntervals.reduce((sum, interval) => {
          return sum + (interval.end - interval.start);
        }, 0);

        this.stepCount = deltaSteps - totalPausedSteps;

        // 現在の歩数データを保存する
        this.lastStepData = {
          steps: this.stepCount
        };

        this.notifyListeners();
      }
    });

  } catch (error) {
    const e = error as BusinessError;
    console.error(`ペダルセンサーの購読に失敗しました: Code=${e.code}, Message=${e.message}`);
    throw error as Error;
  }
}


  public pauseStepCounter(): void {
    if (!this.isMonitoring || this.isPaused) return;
    this.pausedIntervals.push({
      start: this.deviceTotalSteps,
      end: 0
    });
    this.isPaused = true;
  }

  public resumeStepCounter(): void {
    if (!this.isMonitoring || !this.isPaused) return;

    const lastInterval = this.pausedIntervals[this.pausedIntervals.length - 1];
    lastInterval.end = this.deviceTotalSteps;

    this.isPaused = false;
  }

  // 監視を停止する
  public stopStepCounter(): void {
    if (!this.isMonitoring) return;

    this.stepCount = 0;
    this.initialStepCount = 0;
    this.isPaused = false;
    this.pausedIntervals = []; // すべての一時停止レコードをクリアする
    try {
      sensor.off(sensor.SensorId.PEDOMETER);
      this.isMonitoring = false;
    } catch (error) {
      const e = error as BusinessError;
      console.error(`ペダルセンサーの購読をキャンセルするに失敗しました: Code=${e.code}, Message=${e.message}`);
    }
  }

上記のコードでは、sensor.onメソッドを使用して歩数センサーのデータ変化を監視し、現在の歩数をリアルタイムで計算しています。また、一時停止とリスニングの再開のロジックも処理しており、運動プロセス中の一時停止(例えば信号待ち)が歩数統計に誤りを引き起こさないようにしています。

三、歩頻と歩幅の計算ロジック

歩数データが手に入ったら、歩頻と歩幅を計算することができます。以下が歩頻と歩幅を計算するコアロジックです。

  1. 歩頻の計算

歩頻の計算式は次のとおりです:

[
\text{歩頻}=\frac{\text{歩数差}}{\text{時間差}}\times 60
]

実際のコードでは、時間と歩数の情報が含まれる2つの連続した軌跡点を使用して歩頻を計算します。以下が関連コードです:

if (this.previousPoint && this.previousPoint.steps > 0) {
  const timeDiff = (point.timestamp - this.previousPoint.timestamp) / 1000; // 秒に変換
  const stepDiff = point.steps - this.previousPoint.steps;

  if (timeDiff > 0 && stepDiff > 0) {
    // 歩頻(歩/分)を計算
    const instantCadence = (stepDiff / timeDiff) * 60;
    // 移動平均を使用して歩頻データをスムーズ化
    point.cadence = this.currentPoint.cadence * 0.7 + instantCadence * 0.3;
  } else {
    // 時間差または歩数差が0の場合、前の点のデータを保持
    point.cadence = this.currentPoint.cadence;
  }
} else {
  // 最初の点、0で初期化
  point.cadence = 0;
}

上記のコードでは、2つの軌跡点の時間差と歩数差を使用して瞬時歩頻を計算し、移動平均を使用して歩頻データをスムーズ化しています。移動平均の使用は、センサー誤差やユーザーの不規則な運動によって引き起こされる歩頻データの変動を効果的に減らすことができます。

  1. 歩幅の計算

歩幅の計算式は次のとおりです:

[
\text{歩幅}=\frac{\text{距離差}}{\text{歩数差}}
]

実際のコードでは、距離と歩数の情報が含まれる2つの連続した軌跡点を使用して歩幅を計算します。以下が関連コードです:

if (this.previousPoint && this.previousPoint.steps > 0) {
  const distance = this.calculateDistance(this.previousPoint, point) * 1000; // メートルに変換
  const stepDiff = point.steps - this.previousPoint.steps;

  if (stepDiff > 0) {
    // 歩幅(メートル/歩)を計算
    const instantStride = distance / stepDiff;
    // 移動平均を使用して歩幅データをスムーズ化
    point.stride = this.currentPoint.stride * 0.7 + instantStride * 0.3;
  } else {
    // 歩数差が0の場合、前の点のデータを保持
    point.stride = this.currentPoint.stride;
  }
} else {
  // 最初の点、0で初期化
  point.stride = 0;
}

上記のコードでは、2つの軌跡点の距離差と歩数差を使用して瞬時歩幅を計算し、移動平均を使用して歩幅データをスムーズ化しています。歩幅の計算は、軌跡点間の距離に依存するため、軌跡点の精度と密度を確保する必要があります。

四、データのリアルタイム更新と表示

屋外運動プロセス中では、歩頻と歩幅データをリアルタイムで更新し、ユーザーに表示する必要があります。これにより、ユーザーは自分の運動状態をリアルタイムで把握することができ、運動のリズムと強度を調整するための根拠を提供することができます。以下がデータの更新と表示の重要なコードです:

  1. データ更新ロジック

運動プロセス中では、歩数センサーと軌跡点データをリアルタイムでリスニングし、歩頻と歩幅を計算し、これらのデータを現在の軌跡点オブジェクトに保存します。以下がデータ更新のコアコードです:

         // 軌跡点を更新
         this.currentPoint = point;

         // リスナーに通知
         this.notifyListeners();

         // 現在の総距離を返す
         return this.totalDistance;

上記のコードでは、notifyListenersメソッドを使用して、すべての登録されたリスナーに最新の歩頻と歩幅データを通知し、それらにデータを渡します。これらのリスナーは、UIコンポーネントやリアルタイムデータが必要な他のサービスである可能性があります。

  1. データの表示

歩頻と歩幅データをユーザーに表示するには、アプリのインターフェイス上でこれらのデータをリアルタイムで更新する必要があります。以下は、UIに歩頻と歩幅を表示する方法を示す簡単な例です:

         // 歩頻と歩幅を表示するUIコンポーネントがあると仮定
         updateUI(point: RunPoint) {
           this.cadenceDisplayElement.textContent = `歩頻: ${point.cadence.toFixed(2)} 歩/分`;
           this.strideDisplayElement.textContent = `歩幅: ${point.stride.toFixed(2)} メートル/歩`;
         }

上記のコードでは、cadenceDisplayElementstrideDisplayElementは、歩頻と歩幅を表示するHTML要素です。toFixedメソッドを使用して、歩頻と歩幅の値を2桁の小数にフォーマットし、データがより直感的で読みやすいようにします。

五、マップルートの描画:リアルタイムトラックの表示

歩頻と歩幅の計算に加えて、リアルタイムで運動の軌跡を描画することも、屋外運動アプリの重要な機能の1つです。マップ上でユーザーの運動パスを表示することで、ユーザーは自分の運動軌跡をよりよく理解することができ、運動の楽しさを増すことができます。以下がマップルートの描画のコアコードです:

  1. マップの初期化

トラックを描画する前に、マップを初期化する必要があります。ここでは、百度マップを使用します:

         import { MapController, Polyline, SysEnum } from '@ohos.maps';

         // マップコントローラーを初期化
         this.mapController = new MapController(this.mapView);

         // マップの中心点を設定
         this.mapController.setMapCenter({
           latitude: this.startLatitude,
           longitude: this.startLongitude,
         });

上記のコードでは、MapControllerはマップを制御するためのクラスで、setMapCenterメソッドはマップの中心点を設定するために使用されます。ユーザーの開始位置を通常マップの中心点として使用します。

  1. トラックラインの描画

運動プロセス中では、ユーザーのリアルタイム位置に応じてトラックラインを描画する必要があります。以下がトラックラインを描画するコアコードです:

         // トラックポイントの配列を作成
         this.trackPoints = [];

         // 新しいトラックポイントを取得するたびに、それをトラックポイントの配列に追加する
         this.trackPoints.push({
           latitude: point.latitude,
           longitude: point.longitude,
         });

         // トラックラインを作成
         this.polyline = new Polyline({
           points: this.trackPoints,
           strokeColor: '#f0f', // トラックラインの色
           strokeWidth: 20, // トラックラインの幅
           lineJoin: SysEnum.LineJoinType.ROUND, // トラックラインの接続方法
           lineCap: SysEnum.LineCapType.ROUND, // トラックラインの端点スタイル
           isThined: true, // トラックラインの簡略化を行うかどうか
         });

         // トラックラインをマップに追加
         this.mapController.addOverlay(this.polyline);

上記のコードでは、Polylineはトラックラインを描画するためのクラスで、pointsプロパティはトラックポイントの座標を含む配列です。新しいトラックポイントを取得するたびに、そのポイントをtrackPoints配列に追加し、トラックラインオブジェクトを再作成します。addOverlayメソッドを使用してトラックラインをマップに追加することができます。

  1. トラックの動的更新

トラックを動的に更新するには、新しいトラックポイントを取得するたびに、トラックラインを再描画する必要があります。以下がトラックを動的に更新するコアコードです:

         // トラックポイントの配列を更新
         this.trackPoints.push({
           latitude: point.latitude,
           longitude: point.longitude,
         });

         this.mapController!.removeOverlay(this.polyline)

         this.polyline.setPoints(this.trackPoints)

         this.mapController!.addOverlay(this.polyline);

上記のコードでは、removeOverlayメソッドを使用してトラックラインのポイントの配列をクリアし、setPointsメソッドを使用してトラックラインのポイントの配列を更新し、addOverlayメソッドを使用してトラックラインを再描画します。この方法を使用してトラックをリアルタイムで更新し、ユーザーが運動プロセス中自分の運動トラックを⾒ることができるようになります。

六、まとめ

鴻蒙の歩数センサーとマップサービスを使用して、屋外運動における歩頻と歩幅の正確な計算と運動トラックのリアルタイム描画を実現しました。

Discussion