2012年5月27日日曜日

CoreLocationサンプル2 - ヘディングイベントを利用した画面の回転

前回の「CoreLocationサンプル1 - 現在位置の取得と領域観測 」に続いて、今回はヘディングイベントです。前回と重複している部分もありますが、iOS Developer Libraryの「位置情報対応プログラミングガイド」(英語版はMaking Your Application Location-Aware)に沿って、サンプルを公開します。

方向関連のイベント

  • ヘディング(heading)【iOS 4.0以降】
    • 磁力センサーを利用して、「北」がどちらにあるかを計測します。単純に「北」と言っても、以下の二種類があり、どちらを示すかで方角が少し変わってきます。ヘディングでは両方を扱うことができます。詳細は「真北 - Wikipedia」「真北とは? | 用語集とGISの使い方 | 株式会社パスコ」などをご覧ください。
      • 磁北(じほく/magnetic north): 地球の磁場上の北です。(普通の)コンパスなどを使った場合に示されるものとなります。デバイスに内蔵されている磁力センサーを使って磁北を求めることができます。
      • 真北(しんぽく/true north): 普段の生活で良く使われる地図上の北です。北極点へと向かう線となります。磁北と真北はズレている為、現在地を元にしてそのズレを修正するものです。つまり、磁力センサーと現在地情報の両方が必要となります。
  • コース(course)【iOS 2.2以降】

標準アプリ「コンパス」でも「真北」と「磁北」を切り替えられます。


実装手順

  1. 「CoreLocation.framework」の追加
  2. 必要に応じて、<project-name>-Info.plistファイルにキー「location-services」「magnetometer」「gps」を追加する。
    • ただし、現在位置の情報や磁力センサー、GPSが無くともアプリケーションが正常に動作する場合には追加すべきではありません。(UIRequiredDeviceCapabilitiesについての詳細は「iOSデバイス間の互換性まとめ」をご覧下さい。)
  3. ヘッダのインポート
    #import <CoreLocation/CoreLocation.h>
    
  4. CLLocationManagerDelegateプロトコルの宣言
    @interface ViewController : UIViewController <CLLocationManagerDelegate>
    @end
  5. (真北の情報が必要であれば)位置情報サービスの開始
  6. ヘディングイベントの開始
  7. ヘディングイベント情報の受け取り
  8. (不必要になったら)ヘディングイベント(と位置情報サービス)の停止

磁北の取得

  1. ヘディングイベントの開始
    // プロパティではなくクラスメソッドです
    if ([CLLocationManager headingAvailable]) {
        // インスタンスを生成し、デリゲートの設定
        _manager = [[CLLocationManager alloc] init];
        _manager.delegate = self;
        _manager.headingFilter = kCLHeadingFilterNone;  // 更新頻度
        _manager.headingOrientation = CLDeviceOrientationPortrait;  // 基準とする向き
    
        [_locationManager startUpdatingHeading];
    }
    iOS 3.xにも対応させる必要がある場合はプロパティ【Deprecated in iOS 4.0】を使います。
    // インスタンスの生成
    _manager = [[CLLocationManager alloc] init];
    if (_manager.headingAvailable || [CLLocationManager headingAvailable]) {
        // 必要な設定をして、ヘディングイベントの開始
    }
  2. ヘディング情報の受け取り
    - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
    {
        // ここで任意の処理
        CLLocationDirection heading = newHeading.magneticHeading;
    }
    
  3. (不必要になったら)ヘディングイベントの停止
    if ([CLLocationManager headingAvailable]) {
        [_manager stopUpdatingHeading];
    }

真北の取得

  1. ヘディングイベントの開始
    // 位置情報サービスの開始
    if ([CLLocationManager locationServicesEnabled]) {
        _manager.desiredAccuracy = kCLLocationAccuracyBest;     
        _manager.distanceFilter = kCLDistanceFilterNone;
        [_manager startUpdatingLocation];
    }
    
    // ヘディングイベントの開始
    if ([CLLocationManager headingAvailable]) {  // プロパティではなくクラスメソッドです
        // インスタンスを生成し、デリゲートの設定
        _manager = [[CLLocationManager alloc] init];
        _manager.delegate = self;
        _manager.headingFilter = kCLHeadingFilterNone;
        _manager.headingOrientation = CLDeviceOrientationPortrait;
    
        [_locationManager startUpdatingHeading];
    }
    
  2. ヘディングイベント情報の受け取り
    - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
    {
        // ここで任意の処理
        CLLocationDirection heading = newHeading.trueHeading;
    }
    
  3. (不必要になったら)ヘディングイベントと位置情報サービスの停止
    if ([CLLocationManager headingAvailable]) {
        [_manager stopUpdatingHeading];
        [_manager stopUpdatingLocation];
    }


CLLocationManager

デバイスが機能を備えているかどうか

  • ヘディングイベントを扱えるか【iOS 4.0以降】
    + (BOOL)headingAvailable __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
    
    iOS 3.xでも使いたい場合【Deprecated in iOS 4.0
    @property(readonly, nonatomic) BOOL locationServicesEnabled __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA,__MAC_NA,__IPHONE_2_0,__IPHONE_4_0);
    デバイスに、磁力センサーの機能があるかを確認する。【iOS 4.0以降】

ヘディングイベントの挙動を設定

  • 次回の更新までの角度【iOS 3.0以降】
    @property(assign, nonatomic) CLLocationDegrees headingFilter __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
    // 定義
    typedef double CLLocationDegrees;
    
    指定の角度動いたらヘディングイベントを生成する。単位は度数。kCLHeadingFilterNoneを指定すると、角度ではなく、動く度にイベントが通知されるようになります。デフォルトは1度です。

  • デバイスで基準とする向き【iOS 4.0以降】
    @property(assign, nonatomic) CLDeviceOrientation headingOrientation __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
    // 定義
    typedef enum {
        CLDeviceOrientationUnknown = 0,
        CLDeviceOrientationPortrait,
        CLDeviceOrientationPortraitUpsideDown,
        CLDeviceOrientationLandscapeLeft,
        CLDeviceOrientationLandscapeRight,
        CLDeviceOrientationFaceUp,
        CLDeviceOrientationFaceDown
    } CLDeviceOrientation;
    
    デバイスのどの向きが基準となるかを決める。例えば、ホームボタンを右にして横向きに持った状態で北を設定した場合は「CLDeviceOrientationLandscapeLeft」を指定します。デフォルトはCLDeviceOrientationPortrait。
    イメージ定数説明
    無し
    CLDeviceOrientationUnknown無視される
    CLDeviceOrientationPortrait画面上部を北とする
    CLDeviceOrientationPortraitUpsideDown画面下部を北とする
    CLDeviceOrientationLandscapeLeft画面右部を北とする
    CLDeviceOrientationLandscapeRight画面左部を北とする
    画面が表(上)
    CLDeviceOrientationFaceUp無視される
    画面が裏(下)
    CLDeviceOrientationFaceDown無視される

利用開始

  • ヘディングイベント【iOS 3.0以降】
    - (void)startUpdatingHeading __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);

  • キャリブレーション表示を隠す【iOS 3.0以降】
    - (void)dismissHeadingCalibrationDisplay __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
    周りに磁力の強いものがあったりすると、正しく磁力センサーが働かなくなってしまう。その場合は下図のように、iOSが自動的に「8の字に振ってください」とのメッセージを表示するのだが、それを隠す。このメソッドを利用しなくても、キャリブレーションが成功すれば自動的に隠れます。(locationManagerShouldDisplayHeadingCalibrationを使わない場合に使用するのだろうが、使いどころが不明です)

利用停止

  • ヘディングイベント【iOS 3.0以降】
    - (void)stopUpdatingHeading __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);

情報の取得

  • ヘディング情報の取得【iOS 4.0以降】
    @property(readonly, nonatomic) CLHeading *heading __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
    
    最新のヘディング情報が格納されている。デリゲートを利用しない場合にも利用できる。

CLLocationManagerDelegate

情報の更新時

  • ヘディング情報の更新時【iOS 3.0以降】
    - (void)locationManager:(CLLocationManager *)manager
           didUpdateHeading:(CLHeading *)newHeading __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
    ヘディング情報が更新された際に呼ばれる。
  • キャリブレーション表示をするかどうか【iOS 3.0以降】
    - (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager  __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
    周りに磁力の強いものがあったりすると、正しく磁力センサーが働かなくなってしまう。その場合に、「8の字に振ってください」とのメッセージを表示するかどうかを返す。表示しないと磁力センサーの値がおかしいまま修正されません。
    デザイン的に表示してほしくない場合などにNOを返すと良いのか?(使いどころが不明です。)


CLHeading

ヘディング情報

  • 磁北の角度【iOS 3.0以降】
    @property(readonly, nonatomic) CLLocationDirection magneticHeading;
    // 定義
    typedef double CLLocationDirection;
    
    磁北の角度を取得する。0.0から359.9の値で、0(北),90(東),180(南),270(西)となる。
  • 真北の角度【iOS 3.0以降】
    @property(readonly, nonatomic) CLLocationDirection trueHeading;
    // 定義
    typedef double CLLocationDirection;
    
    真北の角度を取得する。0.0から359.9の値で、0(北),90(東),180(南),270(西)となる。

取得した情報の精度

  • 精度【iOS 3.0以降】
    @property(readonly, nonatomic) CLLocationDirection headingAccuracy;
    // 定義
    typedef double CLLocationDirection;
    
    磁北の方角と、計測された磁力から導き出した方角とのズレを角度で示す。値が小さいほど正確な磁北を示す。しかし、キャリブレーションが正しく行われていない場合など、負の値になる。
  • 計測された磁力の数値【iOS 3.0以降】
    @property(readonly, nonatomic) CLHeadingComponentValue x;
    @property(readonly, nonatomic) CLHeadingComponentValue y;
    @property(readonly, nonatomic) CLHeadingComponentValue z;
    
    それぞれの方向の磁力センサーから得られた磁力をマイクロテスラで示す。-128から+128までの正規化された値になる。


サンプル

ソースコードはGitHubにて公開しています。
iOS-SampleCodes/CoreLocationSample-HeadingRelated - GitHub

「設定 > 位置情報サービス >システムサービス > コンパスの調整」について


電池の持ちを良くするためとして、位置情報サービスのシステムサービスの全てをオフにしていました。ただ、「コンパスの調整」だけは意味が分からないままだったので、今回はこれが原因でハマりました。このスイッチの意味は、iPhoneのユーザーズガイドにも詳細な説明は書かれておらず、色々と調べても分からずに疑問だったのですが、磁力センサーを利用して方角の計測をするときに「磁北」だけではなく「真北」も自動的に計測するというもののようです。
  • コンパスの調整「オン」
    • 磁北だけではなく、真北も自動で計測する。その際「ステータスバーアイコン」も「オン」になっていれば、ステータスバーの右上に矢印を表示する。
  • コンパスの調整「オフ」
    • 磁北のみで、真北は"利用できない"

つまり、「コンパスの調整」がオフのままだと、「真北」の利用が出来なくなります。その時の、標準アプリ「コンパス」の例です。真北の選択ができません。

今回のサンプルの場合は以下の通りです。コンパスの針に見立てた「^」が北を示します。ボタンを押すことで、磁北と真北とを切り替えることもできます。ですが、位置情報サービス(startUpdatingLocation)をオンにしても、真北の情報(trueHeading)を得られませんでした。

そこで、「コンパスの調整」を「オン」にすると真北が利用できるようになりました。

下図のように、アプリの標準位置情報サービスはまだオンになっていないのに、バックグラウンドで位置情報を取得し、真北を計算しています。この時表示されている矢印は「コンパスの調整」が自動的に位置情報を取得して真北を計算しているようです。それを表示するかしないかの判定が「ステータスバーアイコン」ということになります。
上の方で、真北を得るためには「startUpdatingLocation」と「startUpdatingHeading」の両方が必要だと書いたのですが、位置情報サービスを開始しても真北を得られないとなると、それは困ります。ご存知の方、ご教示いただけると幸いです。

「磁北」と「真北」の違い。若干、示す角度が異なっています。

iPod touch(第4世代)では、磁力センサーがないため、電子コンパスとして利用できません。


次回

次回は、「CoreLocationサンプル3 - ジオコーディング」です。


0 コメント:

コメントを投稿