[Rust/WinRT] GPS位置情報を取得

Rust/WinRT を試そうシリーズ第三回は位置情報の取得です. なお GPS と銘打っていますが別に情報源は GPS に限られている訳ではなく, Win10 が種々の情報を総合して位置情報を算出してくれるため, こちらがやることは適切な API を呼び出すことだけです ([Docs](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation?view=winrt-19041)). 本記事のコードは以下の環境で検証しました.

- Windows 10 v1909 build 18363.900
- Rust 1.44.0 stable-x86_64-pc-windows-msvc
- winrt 0.7.0

# 準備

## winmd 読み込み

例によってコード冒頭に winmd 読み込みマクロが必要です. 以下のコード例からは省略します.

```rust
winrt::import!(
    dependencies
        os
    types
        windows::devices::geolocation::*
);
```

## アクセス権限

位置情報 API を使用する場合, 「設定」→「プライバシー」→「位置情報」から「このデバイスでの位置情報へのアクセスを許可する」、「アプリが位置情報にアクセスできるようにする」を両方許可しておく必要があります. とはいえストアアプリとしてパッケージングしないでバイナリ実行ファイル (.exe) として使う分にはマニフェストファイルを用いた明示的な権限要求は不要です.

位置情報 API へのアクセス権限があることの確認も WinRT から可能です. 権限がなければエラーが発生するだけなので, わざわざ明示的にアクセス権限があることを確認する必要はあまりないのではないかと思いますが, 一応コード例を載せておきます. ストアアプリとしてパッケージングすることが視野に入っているのなら必要かもしれません.

```rust
use windows::devices::geolocation::{Geolocator, GeolocationAccessStatus};

fn main() -> winrt::Result<()> {
    if Geolocator::request_access_async()?.get()? != GeolocationAccessStatus::Allowed { 
        eprintln!("位置情報へのアクセスが許可されていません.");
    }
    Ok(())
}
```

参考文献: [Geolocator Class](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geolocator?view=winrt-19041), [GeolocationAccessStatus Enum](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geolocationaccessstatus?view=winrt-19041)


# 現在地を取得

現在位置の緯度 (latitude), 経度 (longitude), 高度 (altitude) を表示する, 最もシンプルなコード例です. 現在位置の取得に若干時間がかかるかもしれません.

```rust
use windows::devices::geolocation::Geolocator;

fn main() -> winrt::Result<()> {
    let geolocator = Geolocator::new()?;
    let geocoordinate = geolocator.get_geoposition_async()?.get()?.coordinate()?;
    let p = geocoordinate.point()?.position()?;
    println!("緯度 {}, 経度 {}, 高度 {}", p.latitude, p.longitude, p.altitude);
}
```

実行すると次のような形で結果が表示されます (値はダミーです).

```shell-session
$ cargo run
緯度 35.025278, 経度 135.762222, 高度 0
```

参考文献: [Geolocator Class](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geolocator?view=winrt-19041), [Geocoordinate Class](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geocoordinate?view=winrt-19041), [BasicGeoposition Struct](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.basicgeoposition?view=winrt-19041)


# 情報源と精度

GNSS (GPS やみちびき) モジュールが搭載されていなくても, モバイルネットワークや Wi-Fi ネットワークから位置情報を取得することができます ([Docs](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.positionsource?view=winrt-19041)). いま取得した位置情報が何をもとに算出されたものなのかを表示するコードがこちら.

```rust
use windows::devices::geolocation::{
    Geolocator, PositionSource,
};

fn main() -> winrt::Result<()> {
    let geolocator = Geolocator::new()?;
    let geocoordinate = geolocator.get_geoposition_async()?.get()?.coordinate()?;
    let p = geocoordinate.point()?.position()?;
    println!("緯度 {}, 経度 {}, 高度 {}", p.latitude, p.longitude, p.altitude);

    // 位置情報の取得に用いた情報源
    let source = match geocoordinate.position_source()? {
        PositionSource::Cellular   => "モバイルネットワーク",
        PositionSource::Satellite  => "衛星測位システム",
        PositionSource::WiFi       => "Wi-Fiネットワーク",
        PositionSource::IPAddress  => "IPアドレス",
        PositionSource::Unknown    => "不明",
        PositionSource::Default    => "手動設定",
        PositionSource::Obfuscated => "粗い位置情報",
        _ => unreachable!(),
    };
    println!("情報源: {:?}", source);

    Ok(())
}
```

衛星測位システムの場合, 得られた位置情報の精度を知ることができます. DOP という値が小さいほど精度が良いです ([enwp](https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation))).

```rust
use windows::devices::geolocation::{
    Geolocator, PositionSource,
};

fn main() -> winrt::Result<()> {
    let geolocator = Geolocator::new()?;
    let geocoordinate = geolocator.get_geoposition_async()?.get()?.coordinate()?;
    let p = geocoordinate.point()?.position()?;
    println!("緯度 {}, 経度 {}, 高度 {}", p.latitude, p.longitude, p.altitude);

    if geocoordinate.position_source()? == PositionSource::Satellite {
        let satellite = geocoordinate.satellite_data()?;
        println!("PDOP {}", satellite.position_dilution_of_precision()?.value()?);
        println!("HDOP {}", satellite.horizontal_dilution_of_precision()?.value()?);
        println!("VDOP {}", satellite.vertical_dilution_of_precision()?.value()?);
    }

    Ok(())
}
```

参考文献: [PositionSource Enum](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.positionsource?view=winrt-19041), [GeocoordinateSatelliteData Class](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geocoordinatesatellitedata?view=winrt-19041)


# 一般の精度

なお, (情報源が何であろうと) 位置の誤差をメートル単位で取得する API も提供されているため, 実用上はこれが一番便利かもしれません. ただし, 誤差の大きさを見積もることができない場合, この `Geocoordinate::accuracy()` メソッドの値は取得できません.

```rust
use windows::devices::geolocation::Geolocator;

fn main() -> winrt::Result<()> {
    let geolocator = Geolocator::new()?;
    let geocoordinate = geolocator.get_geoposition_async()?.get()?.coordinate()?;
    let p = geocoordinate.point()?.position()?;
    println!("緯度 {}, 経度 {}, 高度 {}", p.latitude, p.longitude, p.altitude);

    println!("精度 {}m", geocoordinate.accuracy()?);
}
```

参考文献: [Geocoordinate.Accuracy Property](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geocoordinate.accuracy?view=winrt-19041)

# 参考文献

- [WindowsでGPS(みちびき含む)から現在の座標を調べてみる](http://blog.kumoto.jp/2019/02/20/win_geolocation_api/)
- Windows Dev Center ([Windows.Devices.Geolocation Namespace](https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation?view=winrt-19041))
    - [ユーザーの位置情報の取得](https://docs.microsoft.com/ja-jp/windows/uwp/maps-and-location/get-location)