クラウドインテグレーションサービス「雲斗」のブログ

芝公園にある創研情報株式会社がAWS を 中心にクラウドの基本から便利な使いかたまでをお伝えしていきます。

C# Visual Studio Windowsデスクトップ Windowsランタイム コンソールアプリケーション

WindowsでGPS(みちびき含む)から現在の座標を調べてみる

2019/02/21

はじめに

実際に使えそうなアプリネタ第2弾です。UW...と聞いてUWFと自動補完してしまうプログラマA(50歳)です。

私の自宅は携帯が普通に使えてますが、となり(ひと山越えた)の地区はいまだに携帯圏外(Wi-Fiもない)だったりします。iPhoneで撮った写真には座標が埋め込まれてますが、この携帯/Wi-Fi圏外で撮った写真の座標をGoogleマップで入力してみると全く別の地点にマップされました。

iPhoneで携帯/Wi-FiによらずGPSだけを利用させる方法もあるらしいですが、GPS(GNSS)※そのものの勉強もかねてWindowsタブレットで動作する位置情報取得アプリを作成してみました。

手元のWindowsタブレットはGPSレシーバを搭載していますが、デバイス情報からBroadcom BCM4752(かその派生?)と思われ、これであれば日本版GPS"みちびき"(QZSS)にも対応しているようです。

Geoloc_01

※ 厳密にはGNSS(Global Navigation Satellite System)の中の1システムがGPSという事になりますが、GPSの呼称が一般的なので以降、同義として使用します。

プログラム

C#、Windowsデスクトップ/コンソールアプリケーション、Windowsランタイム(WinRT)、Windows.Devices.Geolocation API利用

実行環境

  • Windows 10 64bit タブレット(Diginnos DG-D09IW2)
  • GPSレシーバ:Broadcom 47521

開発環境

  • Windows 10 64bit デスクトップPC
  • GPSレシーバ:VK172 G-MOUSE USB GPS/GLONASS(U-blox7)
  • Visual Studio 2017 Community

プログラム作成

1. プロジェクト作成

Visual Studioで新規作成->プロジェクト->Visual C#->Windowsデスクトップ->コンソールアプリ(.NET Framework)を選択し、プロジェクトを作成します。.NETフレームワークのバージョンは4.7.2としました。

Geoloc_02

2. プロジェクト設定変更

Windows.Devices.Geolocation APIのアセンブリを参照できるように、プロジェクトの設定を変更します。

いったんプロジェクトをアンロードします。

Geoloc_03

プロジェクトファイル(.csproj)を編集モードで開きます。

Geoloc_04

PropertyGroupにTargetPlatformVersionタグを追加します。今回は対象がWindows 10という事で10.0を指定します。

Geoloc_05

プロジェクトを再読み込みします。

Geoloc_06

3. アセンブリ参照設定

プロジェクト再読み込み後、参照マネージャーの"Universal Windows"から

  • Windows.Devices
  • Windows.Foundation

への参照を追加します。

Geoloc_07

参照マネージャーの"参照"から

  • Windows.WinMD
  • System.Runtime.WindowsRuntime.dll

への参照を追加します。これらはWinRT呼出しに必要となるdllファイルです。(ファイルパスは今回の環境では下のキャプチャのとおりでした。)

Geoloc_08

4. コーディング

コードを記述します。


static Geolocator procGeolocator = null;

static void Main(string[] args)
{
    Console.WriteLine("位置データへのアクセス許可を要求しています。");
    var accessStatus = Geolocator.RequestAccessAsync();
    accessStatus.Completed = GetAccessCompleted;
    Console.ReadKey();
}
private static void GetAccessCompleted(IAsyncOperation asyncInfo, AsyncStatus asyncStatus)
{
    if (asyncStatus == AsyncStatus.Completed)
    {
        var result = asyncInfo.GetResults();
        switch (result)
        {
            case GeolocationAccessStatus.Allowed:
                Console.WriteLine("現在位置の取得を開始しています。");
                // 精度レベル: High
                procGeolocator = new Geolocator() { DesiredAccuracy = PositionAccuracy.High };
                var geopositionAsyncResult = procGeolocator.GetGeopositionAsync();
                geopositionAsyncResult.Completed = GetGeopositionCompleted;
                break;
            case GeolocationAccessStatus.Denied:
                Console.WriteLine("位置データへのアクセスが拒否されました。");
                break;
            case GeolocationAccessStatus.Unspecified:
                Console.WriteLine("位置データへのアクセス許可が指定されていません。");
                break;
        }
    }
    else
    {
        Console.WriteLine("位置データへのアクセス許可要求が正常に終了しませんでした。");
    }
}
private static void GetGeopositionCompleted(IAsyncOperation asyncInfo, AsyncStatus asyncStatus)
{
    switch (asyncStatus)
    {
        case AsyncStatus.Completed:
            //
            var result = asyncInfo.GetResults();
            // 衛星のみを対象とする
            if (result.Coordinate.PositionSource == PositionSource.Satellite)
            {
                // 緯度・経度を出力
                var possition = result.Coordinate.Point.Position;
                Console.WriteLine("");
                Console.WriteLine(String.Format("緯度:{0}", possition.Latitude));
                Console.WriteLine(String.Format("経度:{0}", possition.Longitude));
                // 精度を出力
                var satedata = result.Coordinate.SatelliteData;
                Console.WriteLine(string.Format("位置精度(PDOP):{0}", satedata.PositionDilutionOfPrecision.ToString()));
                Console.WriteLine(string.Format("精度の水平方向の希釈(HDOP):{0}", satedata.HorizontalDilutionOfPrecision.ToString()));
                Console.WriteLine(string.Format("精度の垂直方向の希釈(VDOP):{0}", satedata.VerticalDilutionOfPrecision.ToString()));

                Console.WriteLine("\nいずれかのボタンを押して終了してください。");
            }
            else
            {
                var srcName = GetPositionSourceDescription(result.Coordinate.PositionSource);
                Console.WriteLine(string.Format("座標ソースが ({0}) です。", srcName));
            }
            break;
        default:
            Console.WriteLine(procGeolocator.LocationStatus.ToString());
            break;
    }
}
private static string GetPositionSourceDescription(PositionSource posSrc)
{
    string msg = "";
    switch (posSrc)
    {
        case PositionSource.Cellular:
            msg = "Cellular: セルラーネットワークデータ"; break;
        case PositionSource.Default:
            msg = "Default: ユーザが手動で設定した場所"; break;
        case PositionSource.IPAddress:
            msg = "IPAddress: IPアドレスから取得"; break;
        case PositionSource.Obfuscated:
            msg = "Obfuscated: 粗い位置特定機能から取得(意図的に不正確)"; break;
        case PositionSource.Satellite:
            msg = "Satellite: 衛星データ"; break;
        case PositionSource.Unknown:
            msg = "Unknown: 未知のソース"; break;
        case PositionSource.WiFi:
            msg = "WiFi: Wi-Fiネットワークデータ"; break;
    }
    return msg;
}

コード説明

  • 常時起動で位置データをモニタするのではなく、コマンド起動により1回だけ位置情報を取得します。
  • データソースが衛星データであれば現在の緯度・経度と位置精度を出力しますが、衛星データ以外であればデータソース名だけ出力します。
  • Geolocator.RequestAccessAsync()のコールバック->Geolocator.GetGeopositionAsync()のコールバックで二段階のコールバックになりました。(良いのか?)

5. ビルド/デバッグ

ソリューション構成Debugでビルド・デバッグ後、ソリューション構成Releaseでリビルドします。

検証

事前準備

開発環境のReleaseフォルダのファイルを1セット、実行環境のタブレットへコピーします。インストーラは作成せず、シンプルにファイルコピーです。

C:\kumoto\GeoLocWin\GeoLocWin\bin\Release
├──GeoLocWin.exe
├──GeoLocWin.exe.config
├──GeoLocWin.pdb
├──System.Runtime.WindowsRuntime.dll
├──System.Runtime.WindowsRuntime.xml
├──Windows.WinMD

タブレットで忘れずにWindowsの設定->プライバシー->位置情報->このデバイスの位置情報を"オン"にします。

コマンド実行

.exeを起動します。以下は実行結果です。

Geoloc_09

GeocoordinateSatelliteData Classの記述によれば、信頼できる精度で緯度・経度を取得できているようです。

これらの値は提供された位置が正確であるという信頼のレベルを示す。精度の希釈(DOP)の値が低いほど、得られた位置の精度に対する信頼度が高いことを示す。
DOP 内容
<=5 信頼できる
>10 信頼性が低く、概算の位置推定を除いて通常は破棄する必要がある
>20 常に破棄する必要がある

ここでは画面キャプチャは示しませんが、u-center for Windows v18.11で衛星の位置情報を表示させると、アメリカ・ロシア・日本の国旗が表示されてます!無事、"みちびき"の電波も受信できているようです。

GPSデバイス無効時の動作

GPSデバイスを無効にして実行してみます。

Geoloc_10

衛星データが利用できない場合、Wi-Fi(オレオレ基地局)からではなくIPアドレスから位置を取得するようです。(関連するデバイスが利用できない場合、どのような順番・優先度でデータソースを利用しているのかはちゃんと調べてません。)

Geoloc_11

課題・疑問

  • 座標データをタブレットのローカルストレージにファイル出力したい。

    • 緯度・経度の数値は長いのでGoogleマップへコピペできるようにしないと不便
    • ネットワーク圏外での利用を想定しているためクラウドへのダイレクトアップロードは対象外(ネットワークアダプタ有効化イベントでアップロード?バックグラウンド実行?)
  • PositionSource Enumによれば、SDK 14393からObfuscated(粗い位置特定)が追加されたようだが、何をどう設定すればこのデータソースになる?
  • 上のBroadcomの説明には以下の記載(原文は英語)があるので、そもそも"みちびき"からだけ電波を受信する事はできない?
    マルチコンステレーション機能により4つの衛星コンステレーション(GPS、GLONASS、QZSS、SBAS)から同時にデータを収集し、最良の受信信号を使用する

まとめ

  • いまは一年で一番寒い時期なので、暖かくなったらタブレット(アプリ)を持って携帯/Wi-Fi圏外で座標を調べてきたいと思います。実は地元自治会で毎年の作業記録を残すのに、「だいたいこの辺」で写真を撮って残してたりしますが、それなら緯度・経度で地図にマーカを設定して残した方が後々楽になるかなと目論んでいたりします。
  • タブレットのGPSも窓際に置いて衛星としばらく通信させないと位置の精度があがらないようです。勝手に想像するに、高精度のGPS座標を得ようとすると、衛星と頻繁に通信->バッテリの減りが早くなるという事でiPhoneはなるべくGPSを利用しないのではないでしょうか。(普通は携帯基地局/Wi-Fiと常時つながっているので)
  • Windowsだし、Location APIも公開されているようだし、当初はエラー系なし数ステップコーディングの世界かなぁと、いう事でスタートしたのですが、思ったより手間取りました。
    私の場合、C#は.NET1.0時代に触っていたのが一番長かった位でして、UWP、WinRT、.Net Coreって何?の世界で、あっちにフラフラ、こっちをフラフラとネットの情報をさまよう羽目になりました。でも、そのおかげで、最近のWindowsアプリ開発の傾向がなんとなく理解できたのが最大の成果でしょうか。


参考

  1. Read from “Broadcom GNSS 4752 Geolocation Sensor” with C# [closed]
  2. How to use Windows.Devices.Geolocation API in a C# WinForm Win32 Desktop application in Windows 10?

-C#, Visual Studio, Windowsデスクトップ, Windowsランタイム, コンソールアプリケーション

Bitnami