PR

【Python×旅・ハイキング 第4回】ルートの険しさを丸裸に!matplotlibで高尾山の「標高断面図」を作ろう【シリーズ最終回】⛰️

Python

「地図上でルートは分かったけど、このコースってどれくらい登りがキツいの?」
「よく登山雑誌で見かける、横軸が距離で縦軸が標高になっている『山の断面図』を自分で作りたい!」
「シリーズの締めくくりとして、これまでの知識をすべて繋ぎ合わせたい!」

こんにちは! Pythonプログラミング探検隊、隊長のPythonistaです! 前回の第3回では、foliumを使って高尾山のルートをグリグリ動かせるWeb地図の上にプロットしました。自分のコードが本物の地図になった瞬間は、大きな感動がありましたね。

平面のルートが可視化できたら、次に知りたくなるのが**「高さ(垂直方向)」**のドラマです。 「スタートからどれくらい歩いた場所で、どれくらい一気に登るのか?」 これが一目で分かれば、ペース配分や休憩ポイントの計画が劇的に立てやすくなります。

新シリーズの最終回となる今回は、Pythonのグラフ描画ライブラリの王様であるmatplotlib(マットプロットリブ)を召喚します!これまでに学んだXMLパース技術と、pyprojによる距離計算を組み合わせ、スタート地点からの累積距離(横軸)と標高(縦軸)をプロットした「標高断面図(高低差プロファイルグラフ)」を自動生成しましょう。これが完成すれば、あなたのPython登山計画システムはついに完全体へと進化します!


1. 標高断面図を作るためのロジック

断面図のグラフを描くためには、グラフの「横軸(X軸)」と「縦軸(Y軸)」に設定する2つのデータリストをPythonで作る必要があります。

  • 縦軸(Y軸)= 標高: これは簡単ですね。GPXファイルから抜き出した各地点のele(標高)をそのまま並べればOKです。
  • 横軸(X軸)= スタートからの累積距離: ここが少し頭を使うポイントです!GPXには「スタートからの距離」は直接書かれていません。そのため、第2回で学んだpyprojを使い、「地点1〜地点2の距離」「地点2〜地点3の距離」を計算し、それを**スタート地点から順番に足し合わせていく(累積していく)**ことで、横軸のデータを自前で作り出します。

この「データを加工して新しい指標を作る」というプロセスは、データサイエンスの現場でも非常によく使われる重要なアプローチです。


2. 実践!標高断面図自動生成スクリプト

それでは、シリーズの集大成となる完成版スクリプトです。グラフを描画するためのmatplotlibをまだインストールしていない場合は、事前にターミナルで pip install matplotlib を実行しておいてくださいね。

import xml.etree.ElementTree as ET
from pyproj import Geod
import matplotlib.pyplot as plt
import os

def plot_elevation_profile(file_path, output_image):
    """
    GPXファイルを読み込み、累積距離と標高を計算して
    matplotlibで標高断面図(高低差プロファイル)を描画・保存する関数。
    """
    # 1. ファイルが存在するかチェック
    if not os.path.exists(file_path):
        print(f"エラー: '{file_path}' が見つかりません。")
        print("先に第1回のGPX自動生成スクリプトを実行して、ファイルを準備してください。")
        return

    # 2. GPXファイルをパースして、全地点のデータを一度リストに格納する
    tree = ET.parse(file_path)
    root = tree.getroot()
    ns = {'gpx': 'http://www.topografix.com/GPX/1/1'}
    track_points = root.findall('.//gpx:trkpt', ns)

    points_data = []
    for pt in track_points:
        lat = float(pt.attrib['lat'])
        lon = float(pt.attrib['lon'])
        ele = float(pt.find('gpx:ele', ns).text)
        name_tag = pt.find('gpx:name', ns)
        name = name_tag.text if name_tag is not None else ""
        
        points_data.append({'name': name, 'lat': lat, 'lon': lon, 'ele': ele})

    # 3. 高精度な地球モデル(WGS84回転楕円体)を設定
    geod = Geod(ellps='WGS84')

    # グラフ用のデータリストを初期化
    distances = [0.0]  # スタート地点の累積距離は 0 メートル
    elevations = [points_data[0]['ele']] # スタート地点の標高
    labels = {0.0: points_data[0]['name']} # 特徴的な場所にラベル(縦線)をつけるための辞書

    total_distance = 0.0

    # 4. 各区間の距離を計算し、累積距離のリストを作る
    for i in range(len(points_data) - 1):
        current_pt = points_data[i]
        next_pt = points_data[i + 1]

        # pyprojで2点間の距離を計算
        _, _, distance = geod.inv(
            current_pt['lon'], current_pt['lat'], 
            next_pt['lon'], next_pt['lat']
        )
        
        # 距離を足し合わせて累積距離を更新
        total_distance += distance
        distances.append(total_distance)
        elevations.append(next_pt['ele'])
        
        # 経由地名が設定されていれば、その位置の累積距離と名前を記録
        if next_pt['name']:
            labels[total_distance] = next_pt['name']

    # 5. matplotlibによるグラフ描画の設定
    plt.figure(figsize=(10, 5))
    
    # 【日本語フォント崩れ対策】
    # 環境に合わせてフォントを変更してください(Windows: 'MS Gothic', Mac: 'Hiragino Sans' など)
    plt.rcParams['font.family'] = 'sans-serif' 
    plt.rcParams['font.sans-serif'] = ['MS Gothic', 'Hiragino Sans', 'TakaoPGothic', 'IPAexGothic']

    # 断面図の線を描く(美しい緑色を採用)
    plt.plot(distances, elevations, color='#27ae60', linewidth=3, label='標高')
    
    # グラフの下側を薄い緑色で塗りつぶして「山らしさ」を演出
    plt.fill_between(distances, elevations, color='#27ae60', alpha=0.2)

    # 6. 各経由地の位置に縦の点線とテキストラベルを追加
    for dist, name in labels.items():
        # 経由地の位置に薄いグレーの点線を引く
        plt.axvline(x=dist, color='gray', linestyle='--', alpha=0.5)
        # 地名テキストを90度回転させて垂直に配置(グラフの最下部から少し上にずらす)
        plt.text(dist, min(elevations) - 20, f' {name}', rotation=90, verticalalignment='bottom', fontsize=10)

    # 7. グラフの装飾(タイトル・軸ラベル・グリッド線)
    plt.title('高尾山 1号路 標高断面図 (高低差プロファイル)', fontsize=14, fontweight='bold')
    plt.xlabel('スタートからの距離 (メートル)', fontsize=12)
    plt.ylabel('標高 (メートル)', fontsize=12)
    plt.grid(True, linestyle=':', alpha=0.6)
    
    # 表示範囲の調整(見やすさのために上下左右に少し余白を持たせる)
    plt.xlim(0, total_distance)
    plt.ylim(min(elevations) - 50, max(elevations) + 50)

    # 画像ファイルとして高解像度(300dpi)で保存
    plt.tight_layout()
    plt.savefig(output_image, dpi=300)
    print(f"🎉 成功: 標高断面図グラフ '{output_image}' を保存しました!")
    print(f"保存先: {os.path.abspath(output_image)}")
    
    # 画面にグラフウィンドウを表示
    plt.show()

if __name__ == "__main__":
    # 解析・描画対象のGPXファイル名
    target_gpx = "takao_templated.gpx"
    # 出力するグラフ画像ファイル名
    output_png = "takao_elevation_profile.png"
    
    # 標高断面図作成関数の実行
    plot_elevation_profile(target_gpx, output_png)

3. 生成された「標高断面図」を読み解く

スクリプトを実行すると、新しくtakao_elevation_profile.pngという高解像度な美しいグラフ画像が保存されます。

【スクリーンショット推奨箇所: 生成された山型の美しい標高断面図グラフ。緑色で塗りつぶされ、各経由地に点線と縦書きの地名が入っている画像】

グラフを見ると、ルートの立体的な特徴が一目瞭然ですね! 最初の「清滝駅 〜 かすみ台展望台」までの区間は、距離約840メートルに対して標高が一気に260メートル以上も上がっており、グラフの傾きが非常に急なことが視覚的に分かります。まさにここが1号路の前半の踏ん張りどころです。

その後、展望台から薬王院まではなだらかな水平移動に近く、最後の山頂に向けて再びキュッと登る、というコース全体の「ストーリー」が1枚の絵として完璧に浮かび上がりました。

※注:今回はブログ用に4点のみの簡易データで描いたためグラフが直線的になっていますが、実際のスマホアプリ等で記録したリアルなGPSログ(何千点もの座標が詰まったデータ)をこのスクリプトに読み込ませると、山道の細かなアップダウンまで完全に再現された、極めて滑らかな美しい断面図が描けますよ!


シリーズの総まとめ:Pythonが広げるアウトドアの新しい扉

全4回にわたってお届けした「プログラミング×旅・ハイキング」シリーズ、いかがでしたでしょうか? 私たちは、ただ山に登るだけでなく、デジタルなルートデータ(GPX)をハックすることで、これほど多彩なシステムを自作してきました。

  1. 第1回(データ解析編): ルートの標準規格であるGPXファイルの中身(XML構造)を理解し、データとして座標を抽出しました。
  2. 第2回(地理計算編): pyprojを使い、地球の形を考慮した正確な区間距離・方位角・高低差を自動計算しました。
  3. 第3回(ビジュアル地図編): foliumを使い、拡大縮小できる本格的なインタラクティブWebマイマップを数行で作成しました。
  4. 第4回(立体断面図編): matplotlibを使い、コースの険しさを1枚のグラフに視覚化する標高プロファイルを生成しました。

「楽しかった」「キツかった」という思い出に、「データとしてルートを解剖し、可視化する」というエンジニアならではの視点が加わることで、旅の計画や振り返りは何倍もクリエイティブで楽しいものになります。

このシリーズで学んだコードは、高尾山だけでなく、富士山でも、あなたが計画しているお近くのハイキングコースや、海外の旅行ルートでも、GPXファイルさえあれば全く同じように使い回すことができます。ぜひ、あなたのお気に入りのルートをPythonに読み込ませて、あなただけの特別なルートレポートやマイマップを作ってみてください!

「プログラミング×旅・ハイキング」の冒険はここで一度区切られますが、このブログではこれからもPythonを使った面白いライフハックや実践プロジェクトを発信し続けます。また次の新しい冒険のシリーズでお会いしましょう!

Happy Trails and Happy Coding! 🥾🤖📈

コメント

タイトルとURLをコピーしました