ISSが見える範囲に来たら通知する

こんにちは！

ISS（国際宇宙ステーション）が2030年まで運用されるようですね！

上空を通過するときにニュースになりますが、思い返すと、ISSを見たのは数十年前。
いつも気がついたら通り過ぎているので、Slackに通知するスクリプトを作ってみました。

ちょうど、2023年12月16日に「きぼう」が日本上空を通過するとのこと。

※きぼうは、日本が開発を担当した実験棟。ISSを構成する部位の一つ。

# やりたいこと
今回のスクリプトでやりたいのは、以下の2点。
- ISSの位置を取得する
- 見える範囲に来たらSlackに通知する

まずは簡易的なコードを書いてみます。

# 環境構築
Pythonの仮想環境を作ります。
```bash
python -m venv iss_notify
cd iss_notify
```
ディレクトリに移動した直後の状態
```bash
$ ls
bin/		include/	lib/		pyvenv.cfg
```
仮想環境の有効化
```bash
source bin/activate
```
仮想環境を有効化すると、以下のようになります。
```bash
(iss_notify) MacBook-Pro-2:iss_notify username$ 
```
次に、必要なライブラリのインストールをします。
```bash
pip install requests ephem
```

# ephemとは
ephemはPythonで利用できる天文計算ライブラリです。
惑星や小惑星、彗星、人工衛星の位置など、高精度の天文学計算を行うことができます。

# 必要なデータ
ISSに関するデータが必要ですね。
ここでは、[Celestrak](https://www.celestrak.com/)から取得しました。

Celestrakは、信頼性が高く広く利用されているソースであり、多くの専門家や宇宙関連の組織によって使用されています。

# データの構造
以下のようなデータが取得できます。
```text
ISS (ZARYA)             
1 25544U 98067A   23349.58332819  .00011687  00000+0  20935-3 0  9994
2 25544  51.6408 150.0331 0002426  25.3019  91.6622 15.50419692429927
ISS DEB                 
1 47853U 98067RZ  23349.42063777  .00058140  00000+0  22231-3 0  9994
2 47853  51.6280  86.0384 0002193 284.9273  75.1488 15.86886339157384
```
「ISS (ZARYA)」と「ISS DEB」がありますね。
最初のデータがISS本体、次のデータがISSから出たデブリ（宇宙ゴミ）です。
他にもたくさんのデータが含まれますが、今回は、「ISS (ZARYA)」だけを利用します。

ZARYAというのは、最初に打ち上げられた国際宇宙ステーション (ISS) のモジュールです。

※Celestrakのファイルに繰り返しアクセスすると一定時間ブロックされるようなので、ローカルに保存するようにしましょう！


# コード
今回は、通知先をGoogleチャットにしました。
ISSが見える範囲に来たら通知して、見えない場合はログ出力してます。
```python
import requests
import ephem
import os
from datetime import datetime, timedelta

# 東京の緯度と経度を定義
MY_LAT = 35.6895  # 東京の緯度
MY_LONG = 139.6917  # 東京の経度

# GoogleチャットのWebhook URL
GOOGLE_CHAT_WEBHOOK_URL = 'https://chat.googleapis.com/v1/spaces/XXXXX/messages?key=XXXXX&token=XXXXX'

# TLEデータのパス（ローカル）
TLE_FILE_PATH = 'iss_tle.txt'
# データの更新間隔（24時間）
UPDATE_INTERVAL = timedelta(hours=24)

# ISSの現在の軌道要素を取得する関数
def get_iss_tle():
    # TLEデータを更新する必要があるかチェック
    update_required = not os.path.exists(TLE_FILE_PATH) or (datetime.now() - datetime.fromtimestamp(os.path.getmtime(TLE_FILE_PATH))) > UPDATE_INTERVAL

    if update_required:
        # TLEデータを取得
        response = requests.get("https://www.celestrak.com/NORAD/elements/stations.txt")
        # エラーがあれば例外を投げる
        response.raise_for_status()
        # 取得したテキストを書き込み
        with open(TLE_FILE_PATH, 'w') as file:
            file.write(response.text)

        # 取得したテキストを行ごとに分割
    with open(TLE_FILE_PATH, 'r') as file:
        lines = file.readlines()
        # 軌道要素を返す
        return lines[0], lines[1], lines[2]

# ISSが頭上を通過しているかどうかを判断する関数
def iss_overhead():
    # ISSの軌道要素を取得
    iss_tle = get_iss_tle()
    # 軌道要素からISSのオブジェクトを作成
    iss = ephem.readtle(iss_tle[0], iss_tle[1], iss_tle[2])
    # ISSの現在位置を計算
    iss.compute()

    # 観測者の情報を設定
    observer = ephem.Observer()
    # 観測者の緯度経度を設定
    observer.lat, observer.lon = str(MY_LAT), str(MY_LONG)

    # ISSの方角と高度を計算
    # 現在日時を設定
    observer.date = ephem.now()
    # 観測者の位置から見たISSの位置を計算
    iss.compute(observer)
    # 方位角
    azimuth = iss.az
    # 高度
    elevation = iss.alt

    # ISSの現在位置
    # 緯度
    iss_lat = iss.sublat / ephem.degree
    # 経度
    iss_long = iss.sublong / ephem.degree

    # ISSの仰角が30度から90度の範囲内にある場合に見えると判断
    visible = ephem.degrees("30") < elevation < ephem.degrees("90")

    return iss_lat, iss_long, azimuth, elevation, visible

# ISSがいる地域を判定する関数
def get_iss_area(lat, long):
    if lat > 0:
        if long < -90:
            return "北アメリカ西部"
        elif long < 0:
            return "北アメリカ東部"
        elif long < 90:
            return "ヨーロッパ or アフリカ"
        else:
            return "アジア"
    else:
        if long < -90:
            return "南アメリカ西部"
        elif long < 0:
            return "南アメリカ東部"
        elif long < 90:
            return "アフリカ南部 or インド洋"
        else:
            return "オーストラリア or 太平洋"

# 方位角を日本語の方向に変換する関数
def azimuth_to_japanese(azimuth):
    # ラジアンから度に変換
    deg = azimuth * 180 / ephem.pi
    directions = ["北", "北北東", "北東", "東北東", "東", "東南東", "南東", "南南東",
                  "南", "南南西", "南西", "西南西", "西", "西北西", "北西", "北北西"]
    # 方位角を16方向のいずれかに変換
    ix = round(deg / 22.5) % 16
    return directions[ix]

# Googleチャットにメッセージを送信する関数
def send_google_chat_message(message):
    data = {"text": message} 
    response = requests.post(GOOGLE_CHAT_WEBHOOK_URL, json=data)
    response.raise_for_status()

# ISSの位置と方角を取得
iss_lat, iss_long, azimuth, elevation, visible = iss_overhead()
# 方位角を日本語に変換
azimuth_jp = azimuth_to_japanese(azimuth)
# ISSが上空にある地域を取得
iss_area = get_iss_area(iss_lat, iss_long)

# メッセージを作成
message = f"ISSの位置: 緯度 {iss_lat:.2f}, 経度 {iss_long:.2f} ({iss_area})\n観測者の位置: 緯度 {MY_LAT}, 経度 {MY_LONG}\nISSの方角: {azimuth_jp}\n見えるかどうか: {'見える' if visible else '見えない'}"

# ISSが見える場合はGoogleチャットにメッセージを送信、見えない場合はコンソールに表示
if visible:
    send_google_chat_message(message)
else:
    print(message)
```

# 実行結果
それでは、実行してみましょう！
```bash
$ python main.py 
ISSの位置: 緯度 -15.63, 経度 -74.51 (南アメリカ東部)
観測者の位置: 緯度 35.6895, 経度 139.6917
ISSの方角: 南南西
見えるかどうか: 見えない
```
まだ見えないようですね。
ISSの位置がちゃんと計算されているか心配なので、時間をおいて、再度実行してみます。
```bash
$ python main.py 
ISSの位置: 緯度 -50.88, 経度 10.69 (アフリカ南部またはインド洋)
観測者の位置: 緯度 35.6895, 経度 139.6917
ISSの方角: 南西
見えるかどうか: 見えない
```
位置が変わったので、ちゃんと計算されているようです。

[きぼう予報](https://lookup.kibo.space/)で確認すると、本日はもう通り過ぎてしまったようです。
明日も日本上空を通過するので、定期実行してみます。
![スクリーンショット 2023-12-16 7.06.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/909d0c8a-b944-41b1-6583-c5dbc0716d45.png)

# 追記（2023/12/17実行結果）
当日、ついに通知が来ました！
![スクリーンショット 2023-12-17 9.58.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/fe82ceaf-3912-4bf1-743b-e68402a4bd40.png)

1分おきに実行しているので、5時37分、5時38分、5時39分に通知がきました。

曇り空で観測できなかったので、きぼう予報と比較してみましょう。
![スクリーンショット 2023-12-17 9.55.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/9e759c54-acc1-aa39-653d-dcdacec8d167.png)
観測者が東京にいると仮定して、通知は以下の通り。
5時37分　南南西
5時38分　東南東
5時39分　東北東
合っているようですね！

# 今後の課題
ISSの移動スピードが速く、通知が来てからだと観測できない可能性があります。
1分後、2分後の移動経路も含めた方が、観測者が探しやすいですね。

観測可能時間：5時37分〜5時39分
方角：南南西〜東北東

もう少し分かりやすい書き方が必要ですが、このような情報を通知すると良さそうです。

# まとめ
今回はPythonのライブラリを使って、ISSの位置を算出し、見える範囲に来たら通知するスクリプトを作ってみました。

ライブラリのおかげで高度な天文学の計算ができ、非常に助かってます。
他にも様々な機能があるようなので、また改めて使ってみようと思います。

一緒にがんばりましょう:smiley: