強震モニタの画像から揺れていることを検知する

この投稿は [防災アプリ Advent Calendar 2022](https://adventar.org/calendars/7488) の6日目の記事です。

強震モニタはずっと眺めているのも楽しいですが、地震が発生したタイミングでソフトウェアからの通知があるととても良いですね。
今やメジャーなアプリとなった [JQuake](https://jquake.net/) ではかなり精度の高い揺れの検出ができているように見えます。

そこで今回は JQuake のような精度を求めて拙作のアプリで揺れの検知機能を実装したときの考え方、実際のアルゴリズムを解説していこうと思います。

尚、ソースコードはすべて公開しているため、一連の処理はほぼすべてこのファイルにまとまっています。(フォルダ構成コロコロかわるので404になってたらごめんね)  
https://github.com/ingen084/KyoshinEewViewerIngen/blob/develop/src/KyoshinEewViewer/Series/KyoshinMonitor/Services/KyoshinMonitorWatchService.cs

## 今回のゴールについて

今回の場合は、

- 強震モニタ(リアルタイム震度)の画像から地震が発生していることを検知する
    - 強震モニタへの負荷を減らすためにも加速度は使用しないポリシーで開発しています
- **無感地震は検知できなくてもよく**、有感地震を確実に検知できるようにする
- 誤検知はなるべく減らしたい(希望)
- 揺れの強さの違いを判別できるようにする
- 検知した際、効果音と地図のズームを行えるようにする

あたりで開発を行いました。誤検知はまだまだなくなっていないのですが。
ちゃんと自分が欲しい物、必要ないものを決めておくことで実装中に無関係な問題で開発が頓挫することが少なくなるのでおすすめです。(n敗)

## アルゴリズムについて

自分(人間)が強震モニタの画像を見て、どのように地震が起きたことを判別しているか考えました。
例えば、

- 色が変化した(リアルタイム震度が上昇した)観測点が存在する
- その観測点に続けて付近の観測点でも震度が上昇している
    - 揺れが広がっている

などです。
この雑な考え方をもとにロジックとして組み立ててみます。

- リアルタイム震度の上昇幅を計算する
- 上昇していた場合、周囲の観測点を確認する
- 周囲の観測点も上昇していた場合、揺れている という判定を行う

少し見えてきましたが、これだけではまだ足りていない要素があります。

- 1秒前の上昇値だけでは大きな地震かつ急激に変化しないと検知することができない
- 周囲の観測点の探索に時間がかかる
- 観測点ごとに揺れの判定はできても、通知や音声を重複して鳴らさないようにする必要がある

パッと思いつくだけでもこれぐらいはありそうですね。
これを踏まえて修正してみます。

- 予め付近の観測点をリストアップしておき、観測点ごとに保持しておく
- 観測点に過去10秒間の差分を蓄積する機構を作成する
- 過去10秒間の間で一定以上上昇していた場合、周囲の観測点を確認する
- その付近の観測点も一定以上上昇している場合、揺れている という判定を行う

一気に処理できるようになってきましたね。しかしこれでは3つ目の足りない要素がカバーできないため、新しい概念を導入しました。

## イベントの概念

(念のためですが、これは気象庁の電文に含まれるEventIdのイベントなどとは一切関係ありません)

基本的に揺れというのは広がっていくため、リアルタイム震度の上昇は付近の観測点に伝播していくはずです。
これらの観測点を イベント というグループにまとめ、イベント単位で通知などを行うことにしました。

各観測点は1つのイベントにのみ属することができ、揺れの検知中に他のイベントを持つ観測点と遭遇した場合はより古い検知時刻のイベントにマージします。こうすることで一連の揺れをプログラム側からある程度追跡できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100304/9d795467-72ea-b763-f371-6190b949ba86.png)

## 検知の終了

いつまでもマークしているわけにはいかないので、検知を終了する条件も設定します。
現状の仕組みでは揺れを検知した場合観測点ごとに終了時間を設定しており、その時間が経過することで検知終了の扱いとしています。
終了時間については検知時のリアルタイム震度により変化し、検知している状態では毎秒更新されます。
つまり一定の基準で震度が高くなっていれば常にその震度に合わせて期間が延長され続けるわけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100304/4cfa3ec7-d9a5-b019-73ff-fafb46a44e3e.png)

イベントは検知終了に合わせて観測点の割当が解除され、観測点が0件になった時点でそのイベント自体も終了となります。雑に見えるかもしれませんが、なんだかんだで今年3月の地震において揺れに合わせて2度揺れを検知することに成功しています。

## 揺れの強さの区別

現状拙作のアプリでは以下の基準でイベントを取り扱っています。

1. `Weaker`(リアルタイム震度-1.5以上-1.0未満)
1. `Weak`(リアルタイム震度-1.0以上震度1未満)
1. `Medium`(震度1以上3未満)
1. `Strong`(震度3以上5弱未満)
1. `Stronger`(震度5弱以上)

(色々不便なので将来的には震度ごとに扱うつもりではあります)
イベント内の観測点の最大震度をベースに随時更新され、この強さが上昇したときに音声を再生する仕組みになっています。
**これらの強さはイベントが終了するまで下がりません**。これにより一時期リアルタイム震度の色が数秒前の値を表示してしまう不具合が発生した場合でも複数回音声が鳴ることがありませんでした。

## 異常値を弾く

強震モニタではまれに作業ミスなどで常に震度5弱以上として出力されてしまう観測点が発生してしまいます。これらの除外条件は揺れの検知も使用しており、

- 現状揺れを検知していない
- 最新の観測値が存在する
- 過去10秒間のリアルタイム震度の変化が1未満
- 現在のリアルタイム震度が震度3以上(周囲が存在しない離島などの場合は5弱以上)

のすべての条件に合致する場合除外するようになっています。
10秒は短いのでは？と思われるかもしれませんが、離島以外ではまず周りの観測点も同時に変化することがないこと、離島の場合はまず先に揺れを検知し検知が終了する数分が経過するまで除外されないことになるため支障がなさそうな感じはしています。

## 動作画面

これらのアルゴリズムの実際の動作映像を貼っておきます。
開発中バージョンでの動画のため挙動が違うのと、デバッグ表示がされており実際の検知時の画面と異なる点にはご注意ください。

<iframe width="560" height="315" src="https://www.youtube.com/embed/I7z9llECTUI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<iframe width="560" height="315" src="https://www.youtube.com/embed/LzwCxljPWzw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<iframe width="560" height="315" src="https://www.youtube.com/embed/mcrzXaAxiAc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## パラメータについて

このアルゴリズムにはいくつかパラメータが存在します。

- リアルタイム震度上昇のしきい値
- 揺れ検知として扱うための周囲の観測点の数･しきい値

検知のチューニングはこの閾値を変更することによって行います。
現状、東京と神奈川でのみ揺れ検知として扱うしきい値を少し高くしています。
将来的には観測点同士の距離も考慮し検知できるようにしたいと考えています。

## さいごに

いかがでしたでしょうか、非常に駆け足でしたがアルゴリズムの解説はできたと思います。
改良へのアイデアなどは自由にお待ちしております。
この文章は23時59分に書いています。つらみ。