国土地理院のジオイドモデル(JPGEO2024)をPythonで読み込む:ジオイド高取得ライブラリの自作

はじめに

衛星測位(GNSS)で得られる高度(楕円体高)を,国土地理院の標高データと整合させるには,ジオイド高による補正が必要になります.

国土地理院では,全国のジオイド高を計算できるモデルを提供しており,2025年4月からは「ジオイド2024日本とその周辺(JPGEO2024)」という新しいモデルが公開されました.

ジオイドモデルのデータをダウンロードすると,コマンドラインでジオイド高を計算できるツール(Windows/Linux対応)も付属しています.測量やGIS用途には非常に便利ですが,自分で開発しているアプリやスクリプトの中で直接使いたい場合は,ファイルをプログラムから読み取れるようにしておく方が取り回しが良くなります.

そこで今回は,この JPGEO2024(ISG形式)に対応したジオイド高読み取りスクリプトをPythonで実装してみました.ファイル構造の解析から補間処理までを行い,任意の緯度・経度に対応するジオイド高を高精度で取得できるようにしています.

ISG形式の仕様やデータのダウンロード方法については,地理院の公式サイトを参照ください.

ジオイド・モデルの概要 | 国土地理院

ジオイドモデルのダウンロードと,データ形式

ジオイドモデルは国土地理院の,基盤地図情報ダウンロードサービスからダウンロードできます(ダウンロードには無料のユーザ登録とログインが必要です).

ダウンロードしたzipファイルを解凍すると幾つかのファイルがでてきますが,「JPGEO2024.isg」がジオイドモデルのファイルになります.国土地理院から配布されているジオイド高計算のコマンドラインツール(win用:geoidcalc_win64.exe,unix用:geoidcalc_linux_x86_64.tar.gz)も同梱されています.

ダウンロードしたジオイドモデル(JPGEO2024.isg)は,プレーンなテキスト形式で書かれたファイルです.ファイルの冒頭には begin_of_headend_of_head までの ヘッダ情報が記述されており,このモデルがどのような基準・構成で作られているかが分かるようになっています.

「JPGEO2024.isg」の冒頭部分を以下に示します.

begin_of_head ================================================
model name     : JPGEO2024
model year     : 2024
model type     : gravimetric
data type      : geoid
data units     : meters
data format    : grid
data ordering  : N-to-S, W-to-E
ref ellipsoid  : GRS80
ref frame      : ---
height datum   : ---
tide system    : ---
coord type     : geodetic
coord units    : dms
map projection : ---
EPSG code      : ---
lat min        =  15°00'00"
lat max        =  50°00'00"
lon min        = 120°00'00"
lon max        = 160°00'00"
delta lat      =   0°01'00"
delta lon      =   0°01'30"
nrows          =        2101
ncols          =        1601
nodata         =  -9999.0000
creation date  =  01/04/2025
ISG format     =         2.0
end_of_head ==================================================
-3.8045    -3.7311    -3.6618    -3.5933    -3.5227    -3.4496    -3.3763    -3.2980    -3.2208    -3.1448    -3.0656    -2.9861    -2.9015    -2.8164    -2.7290    -2.6446    -2.5543    -2.4649    -2.3830    -2.2962    -2.2160    -2.1352    -2.0601    -1.9779    -1.9007    -1.8185    -1.7367    -1.6541    -1.5768    -1.4939    -1.4162    -1.3373    -1.2524    -1.1677    -1.0790    -0.9859    -0.8946    -0.8022    -0.7153    -0.6258    -0.5379    -0.4547    -0.3784    -0.3010    -0.2253    -0.1483    -0.0679     0.0157     

ヘッダやデータ部の意味は以下の通りです.

項目名意味
model nameモデル名(JPGEO2024)
model yearモデルの公開年(2024年)
model typeモデルの種類(gravimetric:重力モデル)
data typeデータの種類(geoid:ジオイド高)
data unitsジオイド高の単位(meters)
data format格子形式(grid:格子データ)
data orderingデータの並び順(緯度は北→南、経度は西→東)
ref ellipsoid基準楕円体(GRS80)
coord type座標の種類(geodetic:測地緯度経度)
coord units座標の単位(dms:度分秒表記)
lat min / lat max緯度の最小値・最大値(南端 / 北端)
lon min / lon max経度の最小値・最大値(西端 / 東端)
delta lat / delta lon緯度・経度方向の格子間隔(1分=0.016666…°、1.5分=0.025°)
nrows / ncols格子の行数・列数(緯度方向/経度方向の分割数)
nodata欠損値に使われている値(-9999)
ISG formatISGファイル形式のバージョン(2.0)

ヘッダの直後から,ジオイド高の数値データが格子状に羅列されています.データは行単位で並び,nrows × ncols 分の数値が格納されています.

緯度方向は 北(lat max)から南(lat min)へ,経度方向は 西(lon min)から東(lon max)へ という順序で並んでいる点に注意が必要です.

Pythonで読み込む際には,まずこのヘッダ情報をパースしてモデルの範囲・解像度を取得し,その後データ部分を格子配列として読み込むような実装になります.

プログラムの流れ

作成したプログラムでの処理の流れは以下のようなものになります.

  • ヘッダ部分begin_of_headend_of_head)を読み取り,以下の情報を抽出して保持します:
    • lat min, lon min:格子の左下(南西端)の座標
    • delta lat, delta lon:格子間隔(緯度方向/経度方向)
    • nrows, ncols:格子の行数・列数(緯度方向/経度方向)
    • nodata:欠損値として使われる数値(例:-9999.0000)
  • ヘッダ終了後の格子データは行単位で nrows × ncols 個のジオイド高(単位:m)が並んでいます.
  • 格子の並び順は,緯度方向:北から南(lat maxlat min経度方向:西から東(lon minlon max です.
  • このため,Pythonで扱いやすくするために:
    • 読み込んだ格子データは .reverse() して緯度方向を「南から北」に整列します.
    • これにより,grid[0][0](lat min, lon min) に対応するようになります.
  • 任意の緯度・経度 (lat, lon) に対して:
    1. 格子上のインデックスを計算(ix, iy
    2. 周囲4点のジオイド高を取得
    3. バイリニア補間でジオイド高を算出

作成したプログラム:geoid_height.py

import os
import re

class GeoidModelISG2024:
    def __init__(self, filepath):
        if not os.path.exists(filepath):
            raise FileNotFoundError(f"ジオイドデータファイルが見つかりません: {filepath}")

        self.grid = []
        self.lat0 = None
        self.lon0 = None
        self.dlat = None
        self.dlon = None
        self.nrows = None
        self.ncols = None
        self.nodata = None

        # 正規表現で DMS を度に変換
        def dms_to_deg(dms_str):
            match = re.match(r"\s*(\d+)°(\d+)'(\d+)\"", dms_str)
            if not match:
                raise ValueError(f"DMS形式が不正です: {dms_str}")
            deg, min_, sec = map(int, match.groups())
            return deg + min_ / 60 + sec / 3600

        with open(filepath, encoding="utf-8") as f:
            in_head = True
            for line in f:
                line = line.strip()
                if in_head:
                    if line.startswith("lat min"):
                        self.lat0 = dms_to_deg(line.split("=")[1].strip())
                    elif line.startswith("lon min"):
                        self.lon0 = dms_to_deg(line.split("=")[1].strip())
                    elif line.startswith("delta lat"):
                        self.dlat = dms_to_deg(line.split("=")[1].strip())
                    elif line.startswith("delta lon"):
                        self.dlon = dms_to_deg(line.split("=")[1].strip())
                    elif line.startswith("nrows"):
                        self.nrows = int(line.split("=")[1].strip())
                    elif line.startswith("ncols"):
                        self.ncols = int(line.split("=")[1].strip())
                    elif line.startswith("nodata"):
                        self.nodata = float(line.split("=")[1].strip())
                    elif line.startswith("end_of_head"):
                        in_head = False
                else:
                    row = [float(x) for x in line.split()]
                    self.grid.append(row)
        self.grid.reverse()  # 緯度方向を南→北へ(grid[0]がlat_min)

        if len(self.grid) != self.nrows:
            raise ValueError(f"データ行数が想定({self.nrows})と一致しません")
        if any(len(row) != self.ncols for row in self.grid):
            raise ValueError("データ列数が不一致な行があります")

        # 格子は北から南に並んでいるので、lat=lat0がgrid[0]
        # grid[y][x] = ジオイド高(m)

    def get_geoid_height(self, lat, lon):
        # 緯度・経度を格子インデックスに変換
        dy = (lat - self.lat0) / self.dlat
        dx = (lon - self.lon0) / self.dlon
        iy = int(dy)
        ix = int(dx)

        if iy < 0 or ix < 0 or iy + 1 >= self.nrows or ix + 1 >= self.ncols:
            print(f"[警告] 緯度経度がジオイド範囲外: ({lat}, {lon})")
            return None

        # 4点の値を取得
        z00 = self.grid[iy][ix]
        z01 = self.grid[iy][ix + 1]
        z10 = self.grid[iy + 1][ix]
        z11 = self.grid[iy + 1][ix + 1]

        if any(z == self.nodata for z in [z00, z01, z10, z11]):
            print(f"[警告] ジオイド高が欠損値です: ({lat}, {lon})")
            return None

        # バイリニア補間
        fy = dy - iy
        fx = dx - ix
        z = (
            (1 - fx) * (1 - fy) * z00 +
            fx * (1 - fy) * z01 +
            (1 - fx) * fy * z10 +
            fx * fy * z11
        )
        return z


if __name__ == "__main__":
    filepath = "JPGEO2024.isg"
    try:
        model = GeoidModelISG2024(filepath)
        lat = 34.785
        lon = 135.438
        h = model.get_geoid_height(lat, lon)
        if h is not None:
            print(f"ジオイド高 @ ({lat}, {lon}) = {h:.3f} m")
        else:
            print("ジオイド高が取得できませんでした。")
    except Exception as e:
        print(f"[エラー] {e}")

完成版のプログラムは,「標高データってどう使うの?Pythonで地理院の数値標高モデル(DEM)を可視化してみた」という記事で作成したプログラムと一緒にまとめてGitHubにアップしています.

GitHub - Space-Sky-Rail/gsi_tools
Contribute to Space-Sky-Rail/gsi_tools development by creating an account on GitHub.

動作チェック

上記のプログラムをスクリプトとして実行すると,北緯34.785度,東経135.438度(伊丹空港付近)のジオイド高が計算されます.

(venv) PS \geoid_tools> python .\geoid_height.py
ジオイド高 @ (34.785, 135.438) = 37.408 m

正しく計算されているかどうか,データに同梱されているコマンドラインツールを利用しても良いですし,国土地理院のジオイド高計算webサイトで確認しても良いと思います.

ジオイド計算

ジオイド高計算webサイトで同じ緯度,経度を入力したところ以下のようになり,正しく動作していることが確認できました.

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