地図上でポイントの緯度経度を補正する(PowerApps ＆ BingMaps)

この記事は、先に投稿した「[PowerAppsでBingMapsを活用する時のお決まり計算をコンポーネントに](https://qiita.com/kenjik/items/d7ef4c5ba05cb8997ae7)」で作ったコンポーネントの活用サンプルです。

#まずは出来上がりから
実業務では、外回りで新規活動先を見つけた時に現在位置をベースに登録するのと、登録済みの位置情報が間違っていた時に修正する機能として作っていますが、ここでは現在位置をベースに地図上の＋マークをスライダーで動かして、指定された位置の緯度経度を計算で取得し、新たにその緯度経度を中心とした地図を表示する形にしました。取得した緯度経度が間違っているとズレるので、正しく動いているかどうかわかる、という仕組みです（ちょっと無理があるか）。
長い記述になりましたが、ポイントは地図上ではピクセル数で計算して、距離や度数を出したい時にコンポーネントの出力を掛けて使う、というところです。

![editlocation.jpg](https://qiita-image-store.s3.amazonaws.com/0/278581/e1e7adb2-0e3a-6e27-4c87-8989c72ae5d6.jpeg)
なお、このアプリではBingMapsの画像を取得するのにAPIキーが必要になります。お持ちでない方は、以下のページから登録・取得を行っておいてください。
[Bing Maps Dev Center(https://www.bingmapsportal.com/)](https://www.bingmapsportal.com/)

#作ってみよう
##コンポーネントの読み込み
コンポーネントを読み込むには、新規アプリを開いた後で「ファイル」-「アプリの設定」-「詳細設定」-「コンポーネント」をオンにする必要があります。
設定したら、「挿入」メニューで「コンポーネント」というタブが現れるので、そこから「コンポーネントのインポート」を選び、前回の記事で保存したコンポーネントを選択します。
コンポーネントが読み込まれたら、「挿入」-「コンポーネント」から読み込んだコンポーネントを選び画面に挿入します。
![component_import.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278581/8ea6feab-20b0-233f-ae25-064446ba0a84.jpeg)
ここではコンポーネントの名前が「Component1」だったので挿入されたコンポーネントは「Component1_1」になりました。

##コントロールと変数
###コントロール一覧
![editlocation2.jpg](https://qiita-image-store.s3.amazonaws.com/0/278581/86ca2278-a9d9-8f8e-385a-754fea6dbc35.jpeg)


| 番号 | コントロール名 | 種類 | 用途 |
|:-:|:-:|:-:|:-:|
| 0 | Component1_1 | コンポーネント | 画面上表示されていませんが、コンポーネントコントロールです。 |
| 1 | icoSetLocation | アイコン | 現在位置を使って表示をリセットします。 |
| 2 | lblBaseLatitude | ラベル | 地図中央の緯度が表示されます |
| 3 | lblBaseLongitude | ラベル | 地図中央の経度が表示されます |
| 4 | MapSizeX | テキスト入力 | 地図の幅（ピクセル）。あちこちから参照します。 |
| 5 | MapSizeY | テキスト入力 | 地図の高さ（ピクセル）。あちこちから参照します。 |
| 6 | drpZoom | ドロップダウン | 地図のズームレベル。地図表示とコンポーネントに使います。 |
| 7 | Image1 | 画像 | 地図を表示します。 |
| 8 | SliderX | スライダー | 地図の座標と連動し、＋マーカーを動かします。 |
| 9 | SliderY | スライダー | 地図の座標と連動し、＋マーカーを動かします。 |
| 10 | icoMarker | アイコン | 移動先を示します。 |
| 11 | btnChangeBaseLocation | ボタン | ＋マーカーの位置を新たな中央座標として表示をリセットします。 |
| 12 | lblMovedPixelsLat | ラベル | ＋マーカーが中央から緯度方向に何ピクセル動いたか表示します。 |
| 13 | lblMovedPixelsLon | ラベル | ＋マーカーが中央から経度方向に何ピクセル動いたか表示します。 |
| 14 | lblMovedMeterLat | ラベル | ＋マーカーが中央から緯度方向に何ｍ動いたか表示します。 |
| 15 | lblMovedMeterLon | ラベル | ＋マーカーが中央から経度方向に何ｍ動いたか表示します。 |
| 16 | lblMovedDegreeLat | ラベル | ＋マーカーが中央から緯度方向に何度動いたか表示します。 |
| 17 | lblMovedDegreeLon | ラベル | ＋マーカーが中央から経度方向に何度動いたか表示します。 |
###変数一覧

| 変数名 | 変数の種類 | 用途 |
|:-:|:-:|:-:|
| baseLatitude | ローカル変数 | 地図中央の緯度を保持します。 |
| baseLongitude | ローカル変数 | 地図中央の経度を保持します。 |
##コントロールの設定
###0. Component1_1

| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Latitude | baseLatitude | 変数に設定した緯度を基準にします。 |
| ZoomLevel | drpZoom.Selected.Value | 変数に設定した緯度を基準にします。 |
| Visible | false | 計算結果だけを使うので非表示にします。 |

###1. icoSetLocation
```php:icoSetLocation.OnSelect
//現在位置を変数にセット
UpdateContext({baseLatitude: Location.Latitude, baseLongitude: Location.Longitude});
//スライダーーをリセット
Reset(SliderX);Reset(SliderY);
//移動を反映するボタンを選択
Select(btnChangeBaseLocation)
```
###2. lblBaseLatitude - 6. DrpZoom

| 番号 | コントロール名 | プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|:-:|:-:|
| 2 | lblBaseLatitude | Text | baseLatitude | 表示のみ |
| 3 | lblBaseLongitude | Text | baseLongitude | 表示のみ |
| 4 | MapSizeX | Default | 560 | 任意。地図表示、マーカー表示で使います。右側にスライダーの幅を残す必要があります。 |
| 5 | MapSizeY | Default | 350 | 任意。地図表示、マーカー表示で使います。 |
| 6 | DrpZoom | Items | [15,16,17,18,19] | ある程度拡大したズームレベルに限定します。 |
|  |  | Default | 17 | 初期値は任意ですが、ここでは17にします。 |

###7. 画像（Image1）
ImagesにBing Mapsの地図画像を取得します。
画像取得で使うURLの作成方法は、以下のページを参照してください。
Key=の後ろの部分には、別途取得したAPIキーを設定してください。
[Microsoft Docs: Get a Static Map](https://docs.microsoft.com/en-us/bingmaps/rest-services/imagery/get-a-static-map)

```php:Image1.Image
"https://dev.virtualearth.net/REST/v1/Imagery/Map/Road/" & baseLatitude & "," & baseLongitude & "/" & drpZoom.Selected.Value & "?mapSize=" & MapSizeX & "," & MapSizeY & "&Key=YourAPIKey"
```

| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| ImagePosition | Center | 画像を拡大縮小しないで表示します。 |
| Height | MapSizeY | テキスト入力コントロールに設定した値を参照します。 |
| Width | MapSizeX |  |
###8. SliderX
地図で表示されている位置情報を経度方向に移動するためのスライダーです。地図の中央を０と置きます。

| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Default | 0 | 現在の経度の位置を０にします。 |
| Max | RoundDown(MapSizeX/2,0) | 地図の横幅の半分を、プラスの目盛りに振ります。 |
| Min | (SliderX.Max+1)-MapSizeX | 地図の横幅からプラスの目盛りと０目盛りを除いた分をマイナスに振ります。 |
| Width | MapSizeX | スライダーの横幅を地図に合わせます。 |
| X | Image1.X | 地図に合わせます。 |
| Y | Image1.Y+Image1.Height | 地図の下に合わせます。 |
＊注意点としては、Image1の位置を変更するときは、Image1のみ動かしてください。スライダーコントロールを動かしてしまうと、せっかく設定したX,Yの数式が無くなってしまいます。

###9. SliderY
地図で表示されている位置情報を緯度方向に移動するためのスライダーです。地図の中央を０と置きます。

| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Layout | Vertical | スライダーを縦にします。 |
| Default | 0 | 現在の経度の位置を０にします。 |
| Max | RoundDown(MapSizeY/2,0) | 地図の高さの半分を、プラスの目盛りに振ります。 |
| Min | (SliderY.Max+1)-MapSizeY | 地図の高さからプラスの目盛りと０目盛りを除いた分をマイナスに振ります。 |
| Height | MapSizeY | スライダーの縦の長さを地図に合わせます。 |
| Width | SliderX.Height | スライダーの幅をSliderXの高さに合わせます。 |
| X | Image1.X+Image1.Width | 地図の右に合わせます。 |
| Y | Image1.Y | 地図に合わせます。 |
###10. icoMarker
移動先の位置を表示します。

| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Height | 61 | 上下各30ピクセルの大きさ。 |
| Width | 61 | 左右も各30ピクセルの大きさ。 |
| X | Image1.X+(SliderX.Value-SliderX.Min+1)-RoundDown(icoMarker.Width/2,0) | スライダーの位置にアイコンの中央を合わせます。 |
| Y | Image1.Y+(SliderY.Max-SliderY.Value+1)-RoundDown(icoMarker.Height/2,0) | スライダーの位置にアイコンの中央を合わせます。 |
###11. btnChangeBaseLocation
現在の地図中央の位置情報を移動先の位置情報に置き換えます。地図は再描画されます。

``` php:btnChangeBaseLocation.OnSelect
//位置情報を保持する変数を移動先にアップデートします。ここではラベルで計算した移動量（度数）を参照して加減しています。
UpdateContext({baseLatitude: baseLatitude+lblMovedDegreeLat, baseLongitude: baseLongitude+lblMovedDegreeLon});
//スライダーをリセットします。
Reset(SliderX);Reset(SliderY)
```
###12. lblMovedPixelsLat - 17. lblMovedDegreeLon

| 番号 | コントロール名 | プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|:-:|:-:|
| 12 | lblMovedPixelsLat | Text | SliderY.Value | 表示のみ |
| 13 | lblMovedPixelsLon | Text | SliderX.Value | 表示のみ |
| 14 | lblMovedMeterLat | Text | SliderY*Component1_1.MeterPerPixel | コンポーネントの出力を使用して、緯度方向の移動距離を表示します。 |
| 15 | lblMovedMeterLon | Text | SliderX.Value*Component1_1.MeterPerPixel | コンポーネントの出力を使用して、経度方向の移動距離を表示します。 |
| 16 | lblMovedDegreeLat | Text | SliderY.Value*Component1_1.LatitudePer1000Pixels/1000 | コンポーネントの出力を使用して、緯度の移動分を計算します。1000ピクセルあたりの度数なので、1000で割っています。 |
| 17 | lblMovedDegreeLon | Text | SliderX.Value*Component1_1.LongitudePer1000Pixels/1000 | コンポーネントの出力を使用して、経度の移動分を計算します。1000ピクセルあたりの度数なので、1000で割っています。 |
以上でアプリの設定は終わりです。

#動かしてみよう
全て設定できたら動かしてみましょう。スライダーの移動に従って、緯度方向か経度方向の変化が、メートルや度数で表されましたでしょうか。
「移動を反映」ボタンを押すと、＋マークで示されていた部分が新たに地図中央になるはずです。
#最後に
以上が簡単なコンポーネントの使用例です。
スライダーを２つ動かすのは面倒ですね。
@tomikiya さんの「[PowerAppsでドラッグ&ドロップを実装する(https://qiita.com/tomikiya/items/591a7b3d69c70d56c70f)](https://qiita.com/tomikiya/items/591a7b3d69c70d56c70f)」という素晴らしいアイディアをうまく使いたかったのですが、PCでは問題なかったもののiPhoneでは上手く指を認識させられず、見送りました。

次はコンポーネント使用例の第二弾として、地図に複数ポイントを表示してタッチで選択する方法とコンポーネント化をご紹介したいと思うのですが、今回以上に複雑になるので書き切れるか自信がありません（すでに今回も破綻寸前ですし）。こちらも結局は各ポイントをピクセル位置に置き換えて、そこに透明なコントロールを重ねる、というのが基本コンセプトですので、待ちきれないから自分で作るよ、という方はお試しください。