GeoCoreの高精度座標変換を支える設計思想——逆変換は順変換の繰り返しで解く

GeoConverterPro の座標変換エンジン GeoCore は、JGD2024B から東京測地系19系への往復変換を通じて、最大でも数ミリ程度の誤差しか生じない高精度を実現しています。この精度は偶然ではありません。設計段階からコードに組み込まれた2つの判断が土台になっています。
1. 逆変換は「逆向きの式」ではなく「順変換の反復」で解く
座標変換の逆変換をどう実装するかは、精度に直結する設計上の分岐点です。
よくある方法は、順変換と逆変換のそれぞれに独立した計算式を用意することです。しかしこのアプローチは問題を抱えています。順と逆のふたつの式がそれぞれ誤差を持ち、往復したときにそれが加算されるのです。
GeoCoreは別の道を選びました。逆変換は、順変換関数を繰り返し呼び出して目標座標に収束させる方法を採用しています。
JGD補正の逆変換——Newton-Raphson法
[BaseJGDCorrection.swift]の applyInverseCorrection が全JGD補正クラスの共通逆変換エンジンです。
public static func applyInverseCorrection(
targetLat: Double,
targetLon: Double,
maxIterations: Int = JGDConstants.JGD_INV_LOOP_MAX, // 最大20回
toleranceMeters: Double = JGDConstants.JGD_INV_CONV_TOL_M, // 収束閾値 0.1mm
forwardTransform: (Double, Double) -> (Double, Double)
) -> (lat: Double, lon: Double) {
var latEstimate = targetLat
var lonEstimate = targetLon
for iteration in 0..<maxIterations {
let (fLat, fLon) = forwardTransform(latEstimate, lonEstimate)
let errorLat = targetLat - fLat
let errorLon = targetLon - fLon
// 収束判定(0.1mm以下)
let errorLatMeters = abs(errorLat) * 111000.0
let errorLonMeters = abs(errorLon) * 111000.0 * cos(targetLat * .pi / 180.0)
if errorLatMeters < toleranceMeters && errorLonMeters < toleranceMeters {
break
}
// Jacobian行列による Newton-Raphson 更新
// ...(数値微分でヤコビアンを計算し推定値を更新)
}
return (latEstimate, lonEstimate)
}
forwardTransform は呼び出し元が渡す順変換関数そのものです。JGD2024B補正でも TKY2JGD 補正でも、逆変換はこのひとつのエンジンを共用します。つまり「逆向きの式」は存在せず、順変換の精度がそのまま逆変換の精度になる構造です。
収束閾値 JGD_INV_CONV_TOL_M = 0.0001 m(0.1mm)は非常に厳しく設定されており、最大20回の反復の中でほぼ常に数回以内で収束します。
ガウス・クリューゲル逆投影も同じ思想
平面直角座標から緯度経度への逆投影([GaussKrugerProjection.swift]の toGeodetic)も同じ構造です。
public static func toGeodetic(
x: Double, y: Double, zoneNumber: Int, ellipsoid: Ellipsoid
) -> (lat: Double, lon: Double)? {
// 初期推定値(原点からの大まかな位置)
var currentLat = origin.lat + (x / 111000.0)
var currentLon = origin.lon + (y / metersPerDegreeLon)
for iteration in 0..<maxIterations { // 最大12回
guard let (calcX, calcY) = toPlaneRectangular(
lat: currentLat, lon: currentLon,
zoneNumber: zoneNumber, ellipsoid: ellipsoid
) else { return nil }
let errorDistance = sqrt((x-calcX)^2 + (y-calcY)^2)
if errorDistance < 1e-3 { // 1mm以下で収束
return (currentLat, currentLon)
}
// ヤコビアン行列で推定値を更新(ニュートン法)
// ...
}
}
ここでも呼び出しているのは toPlaneRectangular(順変換関数)だけです。順変換の計算式を変更すれば、逆変換も自動的にその精度を受け継ぎます。
2. 原点座標は分数で定義——循環小数を回避する
精度に対するこだわりは、ガウス・クリューゲル投影の原点定義にも現れています。
[GaussKrugerProjection.swift]の原点テーブルを見てください。
public static let origins: [Int: (lat: Double, lon: Double)] = [
3: (36.0, 132.0 + 10.0/60.0), // 132°10' = 132.16̄ 循環小数のため分数で定義
4: (33.0, 134.0 + 20.0/60.0), // 134°20' = 134.3̄
5: (36.0, 134.0 + 20.0/60.0), // 134°20' = 134.3̄
7: (36.0, 137.0 + 10.0/60.0), // 137°10' = 137.16̄
9: (36.0, 139.0 + 50.0/60.0), // 139°50' = 139.83̄
10: (40.0, 140.0 + 50.0/60.0), // 140°50' = 140.83̄
// ...
]
例えば第3系の原点経度 132°10′ を10進数で表すと 132.1666…(循環小数)です。これを 132.1666667 のように小数点以下を切り捨てて書いてしまうと、その誤差は計算を経るたびに積み重なります。
132.0 + 10.0/60.0 という分数形式で書けば、Swift の浮動小数点演算の範囲で表現できる限り最も正確な値が得られます。コメント // 循環小数のため分数で定義 は、この選択の理由を次の開発者に伝えるためのものです。
3. 結果——国土地理院比較と往復変換で検証された精度
この設計の結果は、実際の変換結果で確認されています。
国土地理院サイトとの比較
JGD2024Bから東京測地系19系への変換結果を、国土地理院のTKY2JGD変換ツールと比較した結果、複数のテスト地点すべてで出力値が一致しました(詳細は国土地理院との比較記事を参照)。
往復変換の誤差
JGD2024B → 東京測地系19系 → JGD2024B と往復した結果は次の通りです。
| 地点 | 適用補正 | 往復誤差 |
|---|---|---|
| 熊本県(宇土市付近)・第2系 | 熊本地震補正 | 表示桁で完全一致 |
| 石川県(穴水町付近)・第7系 | 能登半島地震×2・東北地震 | 表示桁で完全一致 |
| 新潟県(柏崎市付近)・第8系 | 能登・中越沖・東北地震 | 最大 約1.8mm |
新潟県の地点でも往復8段階の座標変換を経て 1.8mm の差に収まっています。これはガウス・クリューゲル逆投影・双線形補間・表示桁丸めが重なった結果であり、測地計算の許容誤差の範囲です。
まとめ
GeoCoreの精度の高さは、2つの設計判断から来ています。
| 設計判断 | 実装箇所 | 効果 |
|---|---|---|
| 逆変換 = 順変換の反復収束(Newton-Raphson法) | BaseJGDCorrection.applyInverseCorrectionGaussKrugerProjection.toGeodetic |
順逆の誤差加算を防ぐ |
| 原点座標を分数形式で定義 | GaussKrugerProjection.origins |
循環小数による打ち切り誤差を防ぐ |
「逆変換の式は書かない、順変換を繰り返して解く」という判断は一見遠回りに見えますが、精度と保守性の両面で優れた選択です。収束閾値 0.1mm、最大反復20回という設定も、実用上の計算コストと精度のバランスを意識したものです。
関連記事
お願い
本記事の情報は参考目的で掲載しており、正確性・完全性を保証するものではありません。誤記・不正確な情報がございましたら、下のコメント欄よりご指摘いただければ、確認のうえ修正いたします。ご協力をお願いいたします。
コメントを残す