最寄りの強震モニタ観測点を割り出す

この記事は [防災アプリ Advent Calendar 2022](https://adventar.org/calendars/7488) 3日目の記事です。
前日（2日目）は、ingen084 さんの「[**何もわからない人が震度の算出について解説する**](https://blog.ingen084.net/posts/20221202-calc-shindo)」でした。

# はじめに
初めましての人も多いと思うので自己紹介させていただきます。
あめうま と申します。
昨年から [EarthQuickly](https://ameuma773.github.io/EarthQuickly/) という地震ソフトを制作している人です。

# 本題
最寄りの強震モニタ観測点を割り出すうえで、実装の際に躓いた点を中心に書いていきます。

## なぜこの処理を書く必要があったのか
当方制作のソフトでは従来、最寄観測点を手動でリストから探して設定するという手法をとっていました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2359589/b4247a64-bd29-dee2-6986-b926eb703658.png" width="700px">


しかし、ある時とあるご意見をいただきました。
**「自分から一番近い地点が分かりづらい」**
改めて考えてみれば、特に強震モニタ系のソフトを初めて使う人には、自分の最寄り地点は分からないと思います。
詳細は後述しますが、この観測点リストには、**強震モニタ上に表示されていない地点**も含まれているため、
**「自分の住んでいる町の観測点登録したけど、震度表示されないんだけど...」** という状態になることもありえます。
これを解消するため、最寄りの強震モニタを割り出すという処理を書く必要が出てきました。8月ごろのお話です。

## 強震モニタの特徴
多くの方がご存じかと思いますが、強震モニタ上にはK-NETとKiK-netの観測点が表示されます。
本来なら、およそ1700箇所の観測点が表示されるはずですが、休止しているなどの理由により、現在のところ実際に表示されている観測点は1050箇所程度です。
最寄りの強震モニタ観測点を求めたいとき、**表示されていない観測点を除外するような処理**を書く必要があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2359589/4129f45f-20bf-2717-f1b1-d81324b6cb56.png" width="400px">


## 処理の概要
    強震モニタ観測点の位置をリスト化
    ↓
    ユーザー地点の緯度経度からの距離を近い順にソート
    ↓
    一番近い観測点から順に、その地点は表示されているかどうか判定
    ↓
    表示されていたら、その地点を最寄り観測点として登録

:::note alert
ここから先、サンプルコードが出てきますが、
恐らく**非効率なコード**だと思うので、参考程度に見てください。
:::

## 強震モニタ観測点の位置をリスト化
[KyoshinMonitorLib](https://github.com/ingen084/KyoshinMonitorLib)を使うので、[KyoshinShindoPlaceEditor](https://github.com/ingen084/KyoshinShindoPlaceEditor)を使ってサクッとプロットします。やり方については他を参照してください。

## ユーザー地点の緯度経度からの距離を近い順にソート
.NETにはKeyとValueを登録してデータを保持した上、自動でソートしてくれる [SortedDictionary](https://learn.microsoft.com/ja-jp/dotnet/api/system.collections.generic.sorteddictionary-2?view=net-7.0) が用意されています。

緯度経度間の距離計算についても、[GetDistanceToメソッド](https://learn.microsoft.com/ja-jp/dotnet/api/system.device.location.geocoordinate.getdistanceto?view=netframework-4.8)が用意されているので、簡単に計算できます。

```diff_c_sharp
//using KyoshinMonitorLib;

//ユーザー地点（緯度・経度）
double UserLatitude;
double UserLongitude;

//距離を代入する配列（観測点の数だけ用意しておく）
int[] Kyori = new int[1741];

//強震モニタ観測点のリスト（KyoshinMonitorLib）
ObservationPoint[] points = ObservationPoint.LoadFromMpk("KyoshinMonitorPoints.mpk.lz4", true);

SortedDictionary<double, int> keys = new SortedDictionary<double, int>();

int num = 0;

foreach (var point in points)
{
    var distance = new GeoCoordinate(UserLongitude, UserLatitude).GetDistanceTo(new GeoCoordinate(point.Location.Latitude, point.Location.Longitude));
    keys[distance] = num;
    num++;
}

int iii = 0;
foreach (var a in keys)
{
    Kyori[iii] = a.Value;
    iii++;
}
```
最終的に、`int配列 Kyori`には、距離が近い順に観測点の番号（観測点リストの要素の番号）が代入されます。

## 一番近い観測点から順に、その地点は表示されているかどうか判定
```diff_c_sharp
//最新の観測点の色を入れておく配列
System.Drawing.Color[] KMoniPointColors = new System.Drawing.Color[1741];

string NearestPoint_Name;
int NearestPoint_Num;

for (int i = 0; i < Kyori.Length; i++)
{
    var color = KMoniPointColors[Kyori[i]];

    //観測点の色が黒ならば（≒色が取得できていない）
    if ((color.R == 0) && (color.G == 0) && (color.B == 0))
    {
        continue;
    }
    else
    {
        NearestPoint_Name = $"{points[Kyori[i]].Region} {points[Kyori[i]].Name}";
        NearestPoint_Num = Kyori[i];
        break;
    }
}
```
表示されていない観測点の色として、 **RGB(0,0,0)** が返ってきます。
表示されている観測点において観測点の色が真っ黒になることはあり得ないので、観測点の色で表示されている観測点か表示されていない観測点か区別することができます。

距離が一番近い観測点から順に、現在その観測点は表示されているかを判定していきます。
表示されている観測点が見つかったら、その地点を最寄り地点として登録し、処理終了となります。

::: note warn
強震モニタの観測点は、**一瞬だけ**強震モニタ上から表示が消えたりすることがあり、
前述の処理によって割り出した最寄り観測点は、取得のタイミングによって異なる場合もあります。
1度割り出した地点は、本当の最寄り地点でない可能性があるため、時間をあけてもう一度割り出してみるなど、一瞬の観測点表示の消滅に影響を受けないような処理を書く必要があります。
:::

# まとめ
最寄りの強震モニタ観測点を割り出す場合は、
**未表示の地点を除外する**という点に注意しましょう。
それ以外は、複数の地点から最寄りのものを見つけるという単純な処理なので、特に意識することはないかと思います。

.NETをお使いの方は、[SortedDictionary](https://learn.microsoft.com/ja-jp/dotnet/api/system.collections.generic.sorteddictionary-2?view=net-7.0)と[GetDistanceToメソッド](https://learn.microsoft.com/ja-jp/dotnet/api/system.device.location.geocoordinate.getdistanceto?view=netframework-4.8)を使うことをお勧めしておきます。めっちゃ便利。

# さいごに
分かりづらいコードですいません。
ここだけの話、この記事を書く時も、過去の自分はどのような仕組みのコードを書いているのかを理解するのに結構時間を使いました...
より美しくわかりやすいコードが書けるように精進したいと思います...

[防災アプリ Advent Calendar 2022](https://adventar.org/calendars/7488)、
翌4日目は、はち さんの「[**長周期地震動の緊急地震速報をどう表現するか**](https://note.com/hachineko_2054/n/nd62990a48228)」です。
<!--他の方の記事も大変楽しみにしています！-->

お読みいただきありがとうございました。