地図表示範囲内のポイントを近い順に絞り込む（BingMapsScaleコンポーネントの改善２）

この記事は「[PowerAppsでBingMapsを活用する時のお決まり計算をコンポーネントに](https://qiita.com/kenjik/items/d7ef4c5ba05cb8997ae7)」「[BingMapsScaleコンポーネントの改善](https://qiita.com/kenjik/items/3b6c25b295b41718de52)」の続きになります（大変遅くなり申し訳ありませんでした）。

#はじめに
　次はいよいよ地図をタップするコンポーネントを作成します、と言いながら、もう一度だけBingMapsScaleコンポーネントの改善記事を書くことにしました。
　というのも、前回の改善で、地図表示範囲内のデータ抽出はFilter文一発で可能となったものの、残念ながら[BingMapsのGetMethodではPushPinの最大数が18に制限](https://docs.microsoft.com/en-us/bingmaps/rest-services/imagery/get-a-static-map#pushpin-limits)されており、対象のデータ数が多くなると、何等かの基準で絞り込む必要があります。多くの場合は中心座標から近い順に選ぶのですが、この距離の計算が結構面倒でしたので、お決まり計算としてコンポーネントに入れてしまいました（ただし、コンポーネントとしては正式対応していないCollectionを使っています）。
また、前回記事の「気になる点３」で挙げた「タイミングによってはアウトプットが初期値で渡されてしまう」という点についても、OnReset を使うことで回避しています。

なお、サンプルプログラムのデータとして、[東京都オープンデータカタログサイト](http://opendata-portal.metro.tokyo.jp/www/index.html)に掲載されている、文京区の[AED設置箇所一覧(aedsettikasyoitiran.xlsx)](http://opendata-catalogue.metro.tokyo.jp/dataset/t131059d0000000002)を使わせていただきました。ありがとうございます。

#まずは出来上がりから
　コンポーネントの出来上がりですが、見た目の変化はありません。以下のカスタムプロパティを追加するだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278581/0e9c766b-79a6-6cf5-40fd-ca84847324c7.png)

| 表示名 | 名前 | プロパティの型 | データ型 | コメント |
|:-:|:-:|:-:|:-:|:-:|
| PointListInput | PointListInput | 入力 | テーブル | 対象とするポイントのリスト |
| PointListOutput | PointListOutput | 出力 | テーブル | 処理結果のポイントのリスト |
| MaxPoint | MaxPoint | 入力 | 数値 | 絞り込むポイントの数。初期値は18としておきます |

　PointListInputは地図に表示させたいポイントの集合体です。これを地図に表示可能でMaxPoint以内に絞り込んで、中心からの距離が近い順にPointListOutputにセットするのが処理内容となります。

##入力テーブル（PointListInput）の構造
　入力テーブルに必須な項目は以下の３項目です。これらさえあれば、引き渡すときに他の項目が付いていても問題ありません。

| 項目名 | データ型 | コメント |
|:-:|:-:|:-:|
| UniqueKey | テキスト | テーブル内で一意のキーです |
| PointLatitude | 数値 | 緯度 |
| PointLongitude | 数値 | 経度 |

参考までに、サンプルプログラムではaedsettikasyoitiran.xlsxの「NO」をUniqueKeyとして設定しています。

```php:cmpBingMapScale.PointListInput初期値
Table(
{UniqueKey:"0000000004", PointLatitude:35.708014, PointLongitude:139.756669},
{UniqueKey:"0000000032", PointLatitude:35.708093, PointLongitude:139.758136},
{UniqueKey:"0000000050", PointLatitude:35.708818, PointLongitude:139.754156},
{UniqueKey:"0000000091", PointLatitude:35.707769, PointLongitude:139.758134},
{UniqueKey:"0000000092", PointLatitude:35.708093, PointLongitude:139.758136},
{UniqueKey:"0000000121", PointLatitude:35.708578, PointLongitude:139.757479},
{UniqueKey:"0000000158", PointLatitude:35.708554, PointLongitude:139.756996}
)
```

##出力テーブル（PointListOutput）の構造
　出力テーブルは、入力テーブルに以下の４項目を追加します。

| 項目名 | データ型 | コメント |
|:-:|:-:|:-:|
| ID | 数値 | 中心からの距離が近い順に１から番号が振られます。 |
| GapX | 数値 | 中心からの経度方向のピクセル差 |
| GapY | 数値 | 中心からの緯度方向のピクセル差 |
| Distance | 数値 | 中心からの距離 |


##処理の設定
　地図の中心位置からの距離計算と絞り込みは、OnResetに記述します。
最初に地図の表示範囲にあるポイントに絞り込んだ上で、中心からの距離が小さい順に並べ替え、MaxPointで指定した行数に限定しています。
さらに付加情報として距離順のIDを振り、PointListOutputに引き渡します。

```php:cmpBingMapScale.OnReset
ClearCollect(
    colWork_cmpBMS,
    FirstN(                                             //レコード数をMaxPointに限定する
        SortByColumns(                                  //地図の中心からの距離が小さい順に並べ替える
            AddColumns(                                 //地図の中心からのピクセル差と距離の列を付加する
                Filter(                                 //地図の表示範囲のポイントだけに絞り込む
                    cmpBingMapsScale.PointListInput,
                    PointLatitude > cmpBingMapsScale.MapLatitudeFrom,
                    PointLatitude < cmpBingMapsScale.MapLatitudeTo,
                    PointLongitude > cmpBingMapsScale.MapLongitudeFrom,
                    PointLongitude < cmpBingMapsScale.MapLongitudeTo
                ),
                "GapX",
                ((PointLongitude - cmpBingMapsScale.Longitude) / cmpBingMapsScale.LongitudePer1000Pixels) * 1000,
                "GapY",
                ((PointLatitude - cmpBingMapsScale.Latitude) / cmpBingMapsScale.LatitudePer1000Pixels) * 1000,
                "Distance",
                Sqrt(
                    Power(
                        ((PointLatitude - cmpBingMapsScale.Latitude) / cmpBingMapsScale.LatitudePer1000Pixels) * 1000,
                        2
                    ) + Power(
                        ((PointLongitude - cmpBingMapsScale.Longitude) / cmpBingMapsScale.LongitudePer1000Pixels) * 1000,
                        2
                    )
                ) * cmpBingMapsScale.MeterPerPixel
            ),
            "Distance",
            Ascending
        ),
        cmpBingMapsScale.MaxPoint
    )
);
Clear(colWork2_cmpBMS);
ForAll(
    colWork_cmpBMS,
    Collect(
        colWork2_cmpBMS,
        {
            ID: Max(                //地図の中心からの距離が小さい順にIDを振る
                colWork2_cmpBMS,
                ID
            ) + 1,
            UK: UniqueKey
        }
    )
);
ClearCollect(
    colPointListOutput_cmpBMS,      //出力用のコレクションを作成する
    AddColumns(                     //絞り込んだレコードに先程計算したIDをLOOKUPする
        colWork_cmpBMS,
        "ID",
        LookUp(
            colWork2_cmpBMS,
            UK = UniqueKey,
            ID
        )
    )
)
```

最後に作成したコレクションを出力項目に設定します。
![outputsetting.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278581/2b321952-7856-6139-2044-ee32eb3ffe7d.jpeg)
以上でコンポーネントの改善は完了です。

#試してみよう
　では、コンポーネントが上手く動いているか確認しましょう。丁寧に書くと長くなってしまうため、ポイントだけ記述していきます。
##出来上がりイメージ
　サンプルデータとして接続した[AED設置箇所一覧(aedsettikasyoitiran.xlsx)](http://opendata-catalogue.metro.tokyo.jp/dataset/t131059d0000000002)に必須項目を追加し、中心にしたいポイントとズームレベルを指定すると、右下に絞られたリスト、右上に地図が表示されるというものです。
![BMSListsample1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278581/e593ee9d-1ddd-26de-4712-216693cf938d.png)
##事前準備
　事前準備として[AED設置箇所一覧(aedsettikasyoitiran.xlsx)](http://opendata-catalogue.metro.tokyo.jp/dataset/t131059d0000000002)のエクセルファイルをダウンロードし、「エクセルからインポート」を使ってデータソースとして接続しておきます（デフォルトでは「テーブル1」として接続）。また、上記コンポーネントをインポートし「cmpBingMapsScale_1」として挿入しておきます。
##コントロールと変数、コレクション

| コントロール名 | 種類 | 用途 |
|:-:|:-:|:-:|
| cmpBingMapsScale_1 | コンポーネント | 表示されていませんが、コンポーネントコントロールです。 |
| DataTable1 | データテーブル | 入力テーブルを表示します。（テーブル1に「UniqueKey」「PointLatitude」「PointLongitude」を付加したコレクション） |
| DataTable2 | データテーブル | 出力テーブルを表示します。（地図表示範囲とMaxPointに絞られ「ID」「Distance」「GapX」「GapY」を付加したコレクション） |
| drpLocation | ドロップダウン | 地図の中心にするポイントを選択します。 |
| drpZoomLevel | ドロップダウン | 地図のズームレベルを指定します。 |
| imgMapSpace | 画像 | 地図を表示します。 |

| 変数名 | 種類 | 用途 |
|:-:|:-:|:-:|
| gblLatitude | 数値 | drpLocationで選択したポイントの緯度を保持します。 |
| gblLongitude | 数値 | drpLocationで選択したポイントの経度を保持します。 |

| コレクション名 | 用途 |
|:-:|:-:|
| colPointListInput | 入力テーブルとして成形したリストを保持します。 |
| colPointListOutput_cmpBMS | コンポーネント内で使うコレクションで、出力テーブルを保持します。 |
| colWork_cmpBMS,colWork2_cmpBMS | コンポーネント内で使う作業用のコレクションです。 |

##OnStartの設定
　入力テーブルを成形するために、OnStartに以下を設定します。

```php:OnStart
ClearCollect(colPointListInput,AddColumns(テーブル1,"PointLatitude",Round(Value(緯度),8),"PointLongitude",Round(Value(経度),8),"UniqueKey",NO))
```

##コントロールの設定
###cmpBingMapScale_1

| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Latitude | gblLatitude |  |
| Longitude | gblLongitude |  |
| MaxXPixels | imgMapSpace.Width |  |
| MaxYPixels | imgMapSpace.Height |  |
| MaxPoint | 16 | 初期値を18としているので、少し小さめの16にしました。 |
| PointListInput | colPointListInput |  |
| Visible | false |  |
| ZoomLevel | drpZoomLevel.Selected.Value |  |

###DataTable1
| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Items | colPointListInput |  |
| フィールド | 名称、UniqueKey、PointLatitude、PointLongitude | 何でも良いです |

###DataTable2
| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Items | cmpBingMapsScale_1.PointListOutput |  |
| フィールド | ID、Distance、名称、UniqueKey | 何でも良いです |

###DrpLocation
| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Items | colPointListInput |  |
| OnChange | Set(gblLatitude,drpLocation.Selected.PointLatitude);Set(gblLongitude,drpLocation.Selected.PointLongitude);Reset(cmpBingMapsScale_1) | 選択されたポイントの緯度経度を変数に格納し、コンポーネントをリセットします。ここで再計算が行われます。 |

###DrpZoomLevel
| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Items | [15,16,17,18,19] | 使いそうなズームレベルを並べます。 |
| Default | 17 | とりあえず真ん中を指定しました。 |
| OnChange | Reset(cmpBingMapsScale_1) | 地図表示範囲が変わったらコンポーネントを再計算します。 |

###imgMapSpace
| プロパティ名 | 設定値 | コメント |
|:-:|:-:|:-:|
| Image | "https://dev.virtualearth.net/REST/v1/Imagery/Map/Road/" & gblLatitude & "," & gblLongitude & "/" & drpZoomLevel.Selected.Value & "?" & Concat(cmpBingMapsScale_1.PointListOutput,"&pp=" & PointLatitude & "," & PointLongitude & ";9;" & ID&名称) & "&ms=" & imgMapSpace.Width &  "," & imgMapSpace.Height & "&c=ja-JP&Key="&YourAPIKey | 複数ポイントのPushpinを立てるのにConcat関数を使っています。[";9;"はPushpinの形の指定です。詳しくはこちら](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/pushpin-syntax-and-icon-styles)を参照してください。YourAPIKeyの部分には、各自で取得したAPIキーを当てはめてください。 |

#最後に
　当初は一気に書くつもりだった一連の記事も（Oculus Questにハマってしまい、）半年がかりとなっています。その間にPowerAppsも進化し、地図も静止画像オンリーから、PowerApps Component Frameworkを使えば動的なものも組み込めるようになったようですが、ここまで来たらあと一回、地図のタップ部分を作るだけなので、なんとか頑張ってみようと思います。