アメリカ地質調査所（USGS）から世界中の地震情報を取得する

アメリカ地質調査所（United States Geological Survey, USGS）から世界中の地震情報を取得します。
フィードの種類は ATOM や GeoJSON など複数の形式が用意されていますが、今回は GeoJSON Summary 形式のフィードを C# で処理します。
サンプルコードはC# 9の機能を使用して記述しています。

過去1時間、過去24時間、過去1週間、過去30日間以内に発生した地震についてそれぞれ、顕著な地震、マグニチュード（以下M）4.5以上の地震、M2.5以上の地震、M1.0以上の地震、全ての地震情報のフィードが用意されています。取得したい内容に応じて適切なフィードを利用しましょう。
ここでは過去24時間以内のM4.5以上の地震について処理していきます。このフィードの URL は https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson です。



# フィードをC#で処理する

まずは、JSON を C# でデシリアライズするためのクラスを生成します。

Visual Studio を使っている場合は JSON を全て選択してクリップボードにコピーした後、Visual Studio のメニューから「編集」、「形式を選択して貼り付け」、「JSON をクラスとして貼り付ける」を実行してクラスを生成するとよいでしょう。自動生成されたクラスやプロパティ名、型は必要に応じて修正してください。ここでは `Rootobject` クラスの名前を `UsgsJson` に変え、全プロパティの名前をアッパーキャメルケースに、set アクセサーを init アクセサーに変更しました。

ちゃんと取得できたか確認するためにフィードのタイトルを取得してみました。ここまでのソースコードは次の通りです[^1]。

```csharp
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace UsgsSample
{
    class Program
    {
        const string Uri = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson";
        static readonly HttpClient HttpClient = new();

        static async Task Main()
        {
            await using var stream = await HttpClient.GetStreamAsync(Uri);
            var options = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };
            var json = await JsonSerializer.DeserializeAsync<UsgsJson>(stream, options);
            Console.WriteLine(json.Metadata.Title);
        }
    }

    public class UsgsJson
    {
        public string Type { get; init; }

        public Metadata Metadata { get; init; }

        public Feature[] Features { get; init; }

        public float[] Bbox { get; init; }
    }

    public class Metadata
    {
        public long Generated { get; init; }

        public string Url { get; init; }

        public string Title { get; init; }

        public int Status { get; init; }

        public string Api { get; init; }

        public int Count { get; init; }
    }

    public class Feature
    {
        public string Type { get; init; }

        public Properties Properties { get; init; }

        public Geometry Geometry { get; init; }

        public string Id { get; init; }
    }

    public class Properties
    {
        public float Mag { get; init; }

        public string Place { get; init; }

        public long Time { get; init; }

        public long Updated { get; init; }

        public object Tz { get; init; }

        public string Url { get; init; }

        public string Detail { get; init; }

        public int? Felt { get; init; }

        public float? Cdi { get; init; }

        public int? Mmi { get; init; }

        public string Alert { get; init; }

        public string Status { get; init; }

        public int Tsunami { get; init; }

        public int Sig { get; init; }

        public string Net { get; init; }

        public string Code { get; init; }

        public string Ids { get; init; }

        public string Sources { get; init; }

        public string Types { get; init; }

        public object Nst { get; init; }

        public float Dmin { get; init; }

        public float Rms { get; init; }

        public int Gap { get; init; }

        public string MagType { get; init; }

        public string Type { get; init; }

        public string Title { get; init; }
    }

    public class Geometry
    {
        public string Type { get; init; }

        public float[] Coordinates { get; init; }
    }
}
```

「USGS Magnitude 4.5+ Earthquakes, Past Day」という文字列が出力されれば成功です。

続いて、地震情報を取り出していきます。
JSONデータを見ると `features` キーの中にそれぞれの地震情報が配列で格納されています。
さらに、その中に `type`、`properties`、`geometry`、`id` というキーが存在します。今回使うキーは `properties` と `geometry` です。

まずは、`properties` のサブキーについてみていきます。
次の表は主な項目をピックアップしたものです。詳しいフォーマットについては [USGS のページ](https://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php)に記載されています。

|キー名|型|値の説明|
|---|---|---|
|mag|float|マグニチュード|
|place|string|震央位置|
|time|long|地震発生時刻のエポックミリ秒（UNIX時間）|
|updated|long|最終更新時刻のエポックミリ秒（UNIX時間）|
|url|string|USGSページへのリンク|
|mmi|float（null許容）|推定最大計測震度|
|magtype|string|マグニチュードの種類|
|type|string|地震の種類|

この中でも特筆すべき項目について解説します。


#### place（震央位置）

>
日本の気象庁が発表する地震情報のように領域ごとに震央地名が設定されているのではなく、たいていは「○○の北 ○○km」のような表記です。最初の○○には代表的な都市名が入りますが、もう少し大雑把な地域でよい場合が多いです。そこで、海外の地震情報については **Flinn-Engdahl Regions**（F-E Regions） という境界の定義が使われることがあります。経緯度からF-E Regionsを取得し、日本語化する方法を[後述](#震央地名を日本語化する)します。

#### time（地震発生時刻） と updated（最終更新時刻）

>
ミリ秒の UNIX 時間から `DateTimeOffset` 型に変換するサンプルコードです。

>
```csharp
var dateTime = DateTimeOffset.FromUnixTimeMilliseconds(feature.Properties.Time).ToLocalTime();
```

>
`ToLocalTime()` メソッドを使用して、プログラムが実行されているロケールのローカルタイムに変換しています。


#### mmi（推定最大計測震度）

>
最大加速度や最大速度をもとに推定された震度で、震度階級には**改正メルカリ震度階級**（ローマ数字表記）が使われます。この値は、日本国内の地震情報で用いられる気象庁震度階級とは異なります。この API では自動処理を行いやすいように実数で値が格納されていますが、ローマ数字のほうが人間は見やすいです。震度階級に変換するメソッドのサンプルです。

>
```csharp
static string ToIntensityString(float? mmi) => mmi switch
{
    null => "",
    < 1.5f => "I",
    < 2.5f => "II",
    < 3.5f => "III",
    < 4.5f => "IV",
    < 5.5f => "V",
    < 6.5f => "VI",
    < 7.5f => "VII",
    < 8.5f => "VIII",
    < 9.5f => "IX",
    < 10.5f => "X",
    < 11.5f => "XI",
    _ => "XII"
};
```

>
ほとんどの地震は `null` が格納されており、`null` の場合の処理が必要です。


#### magtype（マグニチュードの種類）

>
日本国内の地震情報で地震の規模を表現する際には気象庁マグニチュード（Mj）が用いられます。しかし、USGS の地震情報で用いられるマグニチュードは1種類に統一されておらず、マグニチュードの値を参照する際はマグニチュードの種類も参照する必要があります。代表的な値には 「mww （モーメントマグニチュード）」、「mb（実体波マグニチュード）」があり、取り得る値については [USGS のページ](https://www.usgs.gov/natural-hazards/earthquake-hazards/science/magnitude-types)にまとめられています。


#### type（地震の種類）
 
>
自然地震だけなく、発破や核実験による地震もあります。それらを識別するにはこの値を使用します。取り得る値のリストをUSGSのサイト内で見つけられませんでしたが、これまでに確認できた値をまとめておきます。
>
|type|
|---|
|earthquake|
|nuclear explosion|
|quarry blast|
|volcanic eruption|


続いて `geometry` のサブキーをみていきます。`coordinates` キーに震源要素が格納されています。`coordinates` キーは配列になっており、「震源の経度」、「震源の緯度」、「震源の深さ」の順で格納されています。


それでは実際に値を表示してみます。

```csharp
foreach (var feature in json.Features)
{
    var originTime = DateTimeOffset.FromUnixTimeMilliseconds(feature.Properties.Time).ToLocalTime();
    Console.WriteLine($"地震発生時刻：{originTime}");
    Console.WriteLine($"震源地：{feature.Properties.Place}");
    Console.WriteLine($"震源の緯度：{feature.Geometry.Coordinates[1]}");
    Console.WriteLine($"震源の経度：{feature.Geometry.Coordinates[0]}");
    Console.WriteLine($"震源の深さ：{feature.Geometry.Coordinates[2]:F1}km");
    Console.WriteLine($"マグニチュード：{feature.Properties.Mag:F1}");
    Console.WriteLine($"マグニチュードの種類：{feature.Properties.MagType}");
    var intensity = ToIntensityString(feature.Properties.Mmi);
    if (intensity.Length != 0)
        Console.WriteLine($"改正メルカリ震度階級：{intensity}");
    Console.WriteLine(feature.Properties.Url);
    Console.WriteLine("---");
}
```

![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195741/68d76c31-ed7b-531e-097e-5913a929c799.png)
このように表示されていれば成功です。



# 震央地名を日本語化する

先ほど震央位置を出力した際に「（都市名）の（方角） ○○km」のように表示されていました。今度は [Flinn-Engdahl Regions](https://earthquake.usgs.gov/data/flinn_engdahl.php)（F-E Regions）という境界の定義に基づく地名を表示します。気象庁から発表される地震情報の一つに「遠地地震に関する情報」というものがありますが、実はこの情報の詳細震央地名は F-E Regions に基づいています。[気象庁防災情報XMLのコード表](http://xml.kishou.go.jp/tec_material.html)に日本語の F-E Regions のリストがあるので、これを利用します。

まず、気象庁のサイトから個別コード表をダウンロードし展開します。その中の「地震火山関連コード表.xls」ファイルを開きます。シートが複数ありますが、「43（AreaEpicenterDetailコード表）」が今回使用するリストです。データ部だけをコンマ区切りの CSV 形式で保存します。
![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195741/e43e5ccd-2e98-3696-3042-c962d7313fef.png)
保存したファイルをエディタで開いて、このように保存されていればOKです。4桁のコードが付番されていますが、千の位は不要なので削除して上書き保存します。ボックス選択に対応したエディタであれば `Shift キー` と `Alt キー` を押しながら範囲を選択すると楽です。
![image3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195741/8ed14433-5b47-c9be-05df-19edb3ffc541.png)

続いて、C# でコードを書いていきます。
実は USGS には経緯度を渡すと F-E Regions を返してくれる API があります。詳しくは [USGS のページ](https://earthquake.usgs.gov/ws/geoserve/regions.php)に記載されています。JSON 形式でデシリアライズをするためのクラスを定義します。

```csharp
public class FeJson
{
    public Fe Fe { get; init; }
}

public class Fe
{
    public FeatureFe[] Features { get; init; }
}

public class FeatureFe
{
    public PropertiesFe Properties { get; init; }
}

public class PropertiesFe
{
    public int? Number { get; init; }
}
```

続いて、日本語の震央地名を取得するメソッドを定義します[^1]。

```csharp
const string RegionsUri = "https://earthquake.usgs.gov/ws/geoserve/regions.json";
const string FeRegionsFile = "FeRegions.csv";

static async Task<string> GetJapaneseEpicenterNameAsync(float latitude, float longitude)
{
    var uri = $"{RegionsUri}?latitude={latitude}&longitude={longitude}&type=fe";
    await using var stream = await HttpClient.GetStreamAsync(uri);
    var options = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };
    var json = await JsonSerializer.DeserializeAsync<FeJson>(stream, options);

    var lines = await File.ReadAllLinesAsync(FeRegionsFile);
    return lines.Select(line => line.Split(','))
                .Where(line => int.Parse(line[0]) == json.Fe.Features.First(feature => feature.Properties.Number is not null).Properties.Number)
                .Select(line => line[1]).FirstOrDefault();
}
```

JSON と CSV ファイルのコード番号同士を比較して、一致していれば CSV ファイルの日本語名を返します。

先ほど作成した地震情報を取得するコードに組み込んで実行してみます。

```csharp
var originTime = DateTimeOffset.FromUnixTimeMilliseconds(feature.Properties.Time).ToLocalTime();
Console.WriteLine($"地震発生時刻：{originTime}");
var latitude = feature.Geometry.Coordinates[1];
var longitude = feature.Geometry.Coordinates[0];
var epicenter = await GetJapaneseEpicenterNameAsync(latitude, longitude);
if (epicenter.Length != 0)
    Console.WriteLine($"震源地：{epicenter}");
Console.WriteLine($"震源の緯度：{latitude}");
Console.WriteLine($"震源の経度：{longitude}");
Console.WriteLine($"震源の深さ：{feature.Geometry.Coordinates[2]:F1}km");
Console.WriteLine($"マグニチュード：{feature.Properties.Mag:F1}");
Console.WriteLine($"マグニチュードの種類：{feature.Properties.MagType}");
var intensity = ToIntensityString(feature.Properties.Mmi);
if (intensity.Length != 0)
    Console.WriteLine($"改正メルカリ震度階級：{intensity}");
Console.WriteLine(feature.Properties.Url);
Console.WriteLine("---");
```

![image4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195741/6f5485b6-f92a-7fb6-4aeb-f2e5821792a2.png)
このように表示されていれば成功です。

[^1]: 例外処理は省略しています。