🚗

GitHub Actions + Google Maps Route API + iPhoneショートカットで朝の渋滞を検知する

に公開1

概要

毎朝の通勤で「どのルートが今日は空いてるか」を確認するのが面倒だったので、
ボタン一発でSlackに結果を通知するシステムを作った。

構成:

  • Google Maps Routes API → 複数ルートの渋滞考慮済み所要時間を取得
  • Python → ルート比較スクリプト
  • GitHub Actions → 実行エンジン
  • Slack Incoming Webhook → 結果通知
  • iOSショートカット → ワンタップで起動

必要なもの

  • Google Cloud アカウント(Routes API有効化 + APIキー)
  • GitHub アカウント(リポジトリ + GitHub Mobile アプリ)
  • Slack ワークスペース(個人用でOK)
  • iPhone(ショートカットアプリ)

Step 1: Google Maps Routes API のセットアップ

  1. Google Cloud Console でプロジェクト作成
  2. 「Routes API」を有効化
  3. 「認証情報」→ APIキーを発行

Step 2: Pythonスクリプトの作成

ディレクトリ構成

.
├── check_route.py
├── requirements.txt
├── .env
├── .env.example
├── .gitignore
└── .github/
    └── workflows/
        └── check_route.yml

requirements.txt

requests
python-dotenv

check_route.py

import json
import os
import sys
import requests
from datetime import datetime, timezone, timedelta
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv("GOOGLE_MAPS_API_KEY")
ROUTES_API_URL = "https://routes.googleapis.com/directions/v2:computeRoutes"

HOME = os.getenv("HOME_ADDRESS")
WORK = os.getenv("WORK_ADDRESS")

def load_routes():
    routes = []
    i = 1
    while True:
        raw = os.getenv(f"ROUTE{i}")
        if not raw:
            break
        data = json.loads(raw)
        routes.append({
            "name": data["name"],
            "waypoints": [HOME] + data["waypoints"] + [WORK],
        })
        i += 1
    return routes


def fetch_route(name, waypoints):
    body = {
        "origin": {"address": waypoints[0]},
        "destination": {"address": waypoints[-1]},
        "travelMode": "DRIVE",
        "routingPreference": "TRAFFIC_AWARE",
    }
    intermediates = waypoints[1:-1]
    if intermediates:
        body["intermediates"] = [{"address": wp} for wp in intermediates]

    headers = {
        "Content-Type": "application/json",
        "X-Goog-Api-Key": API_KEY,
        "X-Goog-FieldMask": "routes.duration,routes.distanceMeters",
    }

    res = requests.post(ROUTES_API_URL, json=body, headers=headers, timeout=10)
    if not res.ok:
        return {"name": name, "error": f"{res.status_code}: {res.text}"}

    route = res.json()["routes"][0]
    duration_sec = int(route["duration"].rstrip("s"))
    return {
        "name": name,
        "duration_min": duration_sec // 60,
        "duration_sec": duration_sec % 60,
        "distance_km": route["distanceMeters"] / 1000,
    }


def notify_slack(message):
    webhook_url = os.getenv("SLACK_WEBHOOK_URL")
    if not webhook_url:
        return
    requests.post(webhook_url, json={"text": message}, timeout=10)


def main():
    JST = timezone(timedelta(hours=9))
    now = datetime.now(JST).strftime("%Y-%m-%d %H:%M")
    results = [fetch_route(r["name"], r["waypoints"]) for r in load_routes()]
    valid = [r for r in results if "error" not in r]
    fastest = min(valid, key=lambda r: r["duration_min"] * 60 + r["duration_sec"])

    lines = [f"*朝の通勤ルートチェック ({now})*\n"]
    for r in valid:
        marker = " :star: 最速!" if r["name"] == fastest["name"] else ""
        lines.append(f"{r['name']}{marker}")
        lines.append(f" {r['duration_min']}{r['duration_sec']:02d}秒 {r['distance_km']:.1f} km")

    notify_slack("\n".join(lines))


if __name__ == "__main__":
    main()

経由地の住所をGoogleマップで調べる

ルートの経由地に指定する住所は、Googleマップで以下の手順で確認できる。

  1. Googleマップで、自宅 → 経由地1 → 経由地2 → 職場 のルートを検索(ルート検索で経由地を追加)
  2. 表示されたフォームの住所をコピー
  3. .envROUTE{n}waypoints に貼り付ける
  4. GithubのSecretsにも同様に登録する

ランドマーク(駅・公共施設など)は施設名でも認識されることが多いが、住所の方が確実。

Googleマップで経由地の住所を確認する様子

.env.example

GOOGLE_MAPS_API_KEY=your_api_key_here
HOME_ADDRESS=your_home_address_here
WORK_ADDRESS=your_work_address_here
ROUTE1={"name": "ルートA", "waypoints": ["経由地1", "経由地2"]}
ROUTE2={"name": "ルートB", "waypoints": ["経由地1"]}
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ

.gitignore

.env
__pycache__/
*.pyc
.venv/

Step 3: Slack Incoming Webhook の設定

  1. api.slack.com/apps → 「Create New App」→「From scratch」
  2. 左メニュー「Incoming Webhooks」→ トグルを ON
  3. 「Add New Webhook to Workspace」→ チャンネルを選択
  4. 表示されたWebhook URLをコピー

動作確認:

curl -X POST -H 'Content-type: application/json' \
  --data '{"text":"テスト通知"}' \
  https://hooks.slack.com/services/XXX/YYY/ZZZ

Step 4: GitHub Actions の設定

Secrets の登録

リポジトリの SettingsSecrets and variablesActionsNew repository secret で追加:

Name Value
GOOGLE_MAPS_API_KEY Google CloudのAPIキー
SLACK_WEBHOOK_URL SlackのWebhook URL
HOME_ADDRESS 自宅の住所
WORK_ADDRESS 職場の住所
ROUTE1 ルート1の名前と経由地(JSONオブジェクト)
ROUTE2 ルート2の名前と経由地(JSONオブジェクト)
ROUTE3 ルート3の名前と経由地(JSONオブジェクト)

ルート名と経由地を1つのJSONオブジェクトにまとめて登録する:

{"name": "ルートA", "waypoints": ["経由地の住所1", "経由地の住所2"]}

ROUTE1 から連番で定義し、存在しない番号になった時点でループを終了する仕組みのため、
ルートを追加したい場合は ROUTE4ROUTE5... とSecretsを増やすだけでよい。

.github/workflows/check_route.yml

name: 朝の通勤ルートチェック

on:
  workflow_dispatch:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: python check_route.py
        env:
          GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          HOME_ADDRESS: ${{ secrets.HOME_ADDRESS }}
          WORK_ADDRESS: ${{ secrets.WORK_ADDRESS }}
          ROUTE1: ${{ secrets.ROUTE1 }}
          ROUTE2: ${{ secrets.ROUTE2 }}
          ROUTE3: ${{ secrets.ROUTE3 }}

Actionsタブの「Run workflow」から手動実行して動作確認する。

Step 5: iOSショートカットの設定

GitHub Mobile アプリを使うのが最も簡単。

  1. App StoreでGitHub Mobileをインストール
  2. ショートカットアプリ → 「+」→「アクションを追加」
  3. 「ワークフローをディスパッチ」を検索して追加
  4. 各フィールドを入力:
フィールド
Owner GitHubユーザー名
Workflow ID check_route.yml
Repository リポジトリ名
Branch / ref main
  1. ショートカットをホーム画面に追加

ハマりどころ

1. departureTime に現在時刻を渡すと400エラー

Routes APIは departureTime に過去・現在時刻を渡すと弾かれる。

"Timestamp must be set to a future time."

routingPreference: TRAFFIC_AWARE を指定するだけで現在の渋滞を考慮してくれるので、
departureTime は省略でOK。

2. iOSショートカットの「URLの内容を取得」で204エラー

GitHub workflow_dispatch APIは成功時に 204 No Content を返す。
iOSショートカットはレスポンスボディが空だとエラーと誤認することがある。

解決策1: GitHub Mobileの「ワークフローをディスパッチ」アクションを使う(推奨)

解決策2: リクエストボディに return_run_details: true を追加すると
200+ボディで返ってくるようになる(2026年2月〜対応)

{
  "ref": "main",
  "return_run_details": true
}

3. APIエラーの詳細が見えない

requests.raise_for_status() だとエラーメッセージが隠れる。
デバッグ中は res.text を出力するようにすると原因がわかりやすい。

if not res.ok:
    return {"name": name, "error": f"{res.status_code}: {res.text}"}

まとめ

項目 採用技術
ルート取得 Google Maps Routes API(TRAFFIC_AWARE)
スクリプト Python + requests
実行基盤 GitHub Actions(workflow_dispatch)
通知 Slack Incoming Webhook
スマホトリガー iOSショートカット + GitHub Mobile

朝の出発前にボタン一発で最速ルートがSlackに届くようになった。
GitHub Actionsは無料枠(月2000分)で十分運用できる。

ソースコード

https://github.com/yuki2021/morning_check_road_open

Discussion

Ryo TakahashiRyo Takahashi

ショートカットのオートメーションで、ナビのBluetooth接続時をトリガーにすれば完全自動化もできそうですね