KunYu
epsgcoordinate-conversionproj4crs

EPSG座標系間の座標変換:proj4js・pyproj・ogr2ogrの実践ガイド

proj4js、pyproj、ogr2ogrを使ってEPSG座標系間の座標変換を行う方法を、WGS 84・Web Mercator・UTM・中国CRSの実例とともに解説します。

KunYu TeamMarch 25, 202620分で読めます

WGS 84のGPS測量点を受け取り、UTM Zone 50Nで投影されたサイトプランに重ねようとする。座標を貼り付けると、ポイントが海のど真ん中に表示される——数千キロメートルもずれた場所だ。数値だけ見ると一見それらしいが、座標系が異なれば変換なしに互換性はない。EPSG座標系間の変換はGISで最も頻繁に行われる操作の一つであり、間違えた場合のエラーは数メートルの見えない誤差から、まったく別の大陸に着地する壊滅的なものまで幅広い。

EPSG座標変換とは、数学的に定義された測地基準変換と地図投影を使って、ある座標参照系から別の座標参照系へ点座標を変換する処理だ。以下では、JavaScript・Python・コマンドラインでの具体的なコードと、データを静かに破壊するよくある落とし穴を取り上げる。

座標変換とは何か、いつ必要になるか

座標変換は、点データをあるCRSから別のCRSに再投影する処理だ。異なる座標系で収集されたデータセットを統合するとき、GPSデータをウェブ地図に表示するとき、地理座標(度)と投影座標(メートル)の間で変換するとき、あるいは中国のオフセット座標系(GCJ-02やBD-09)を扱うときに必要になる。

混同されがちな二つの操作がある。座標変換(conversion) は同一の測地基準系上で地図投影を変更するもので、例えばWGS 84地理座標(EPSG:4326)からWGS 84 / UTM Zone 50N(EPSG:32650)への変換がこれにあたる。測地基準変換(transformation) は測地基準系そのものを変更する。NAD27からWGS 84への変換は、二つの基準系が地球の形状を異なるモデルで定義しているため、物理モデルパラメータが必要になる。多くのGISライブラリは両方を透過的に処理するが、精度の問題をデバッグするときにはこの区別が重要だ。

座標変換ツールが必要になる5つのシナリオ:

  1. 異なるCRSのデータセットを統合する — 測量委託先がOSGB 1936(EPSG:27700)でデータを納品し、手元のWGS 84ベースラインと結合する必要がある場合。
  2. GPSからウェブ地図へ — 生のGPS出力はWGS 84(度)だが、ウェブ地図はWeb Mercator(メートル)で描画する。タイルベースの地図はすべて内部でこの変換を行っている。
  3. クライアント指定のCRSで測量データを納品する — クライアントのCADシステムがLambert-93(EPSG:2154)を要求し、フィールド機器はWGS 84で記録している場合。
  4. 中国の地図を扱う — Amap(高徳地図)、テンセントマップ、百度地図はそれぞれ独自のオフセットCRS(GCJ-02とBD-09)を使用しており、WGS 84とそのまま互換しない。
  5. UTMゾーンをまたぐ変換 — プロジェクトが2つのUTMゾーンにまたがり、距離・面積計算の一貫性のために単一の投影CRSが必要な場合。

EPSG:4326からEPSG:3857への変換方法

EPSG:4326(WGS 84、度)からEPSG:3857(Web Mercator、メートル)に変換するには、メルカトル投影の公式を適用する。JavaScriptではproj4("EPSG:4326", "EPSG:3857", [lng, lat])、PythonではTransformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)を使う。この変換は経度・緯度をメートル単位のeasting・northingにマッピングする。

北京を例にとる:

// JavaScript — proj4js
import proj4 from "proj4";

const [easting, northing] = proj4(
  "EPSG:4326",
  "EPSG:3857",
  [116.4074, 39.9042] // Beijing (lng, lat)
);
console.log(easting, northing);
// → 12958175.0, 4852834.1
# Python — pyproj
from pyproj import Transformer

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
easting, northing = transformer.transform(116.4074, 39.9042)
print(easting, northing)
# → 12958175.0, 4852834.1

入力は2つの小さな数値(度)で、出力は2つの大きな数値(メートル)になる。これは想定通りだ。Web Mercatorは赤道と本初子午線の交点(0°N, 0°E)からの距離をメートルで測定する。

注意点が一つ:EPSG:3857は約±85.06°の緯度で切り取られる。メルカトル投影は極を無限遠に写像するため、北緯85°以北または南緯85°以南の座標は表現できない。北極・南極のデータにはEPSG:3413(北極)やEPSG:3031(南極)のような極ステレオ投影が必要だ。

変換結果はKunYuの座標変換ツールですぐに検証できる。座標を貼り付け、変換元と変換先のCRSを選択すれば、インストール不要で結果が得られる。

任意のEPSGコード間での変換

同じパターンがどのEPSGペアでも使える。ビルトインでないコードの場合は、CRS定義(proj4文字列またはWKT)が必要だ。proj4jsはカスタムCRS定義を事前に登録する必要があり、pyprojは完全なEPSGデータベースを内蔵している。

JavaScript (proj4js)

Proj4jsはデフォルトでEPSG:4326とEPSG:3857しか認識しない。それ以外は定義文字列が必要で、epsg.ioで調べられる。

import proj4 from "proj4";

// Register Lambert-93 (France) — definition from epsg.io/2154
proj4.defs(
  "EPSG:2154",
  "+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 " +
    "+x_0=700000 +y_0=6600000 +ellps=GRS80 +units=m +no_defs"
);

// WGS 84 → Lambert-93
const [x, y] = proj4("EPSG:4326", "EPSG:2154", [2.3522, 48.8566]);
console.log(x, y);
// → 652469.5, 6862035.9 (Paris in Lambert-93 meters)

proj4定義文字列は完全一致しなければならない。以前、CGCS2000の投影データセットで3メートルのオフセットを1時間もデバッグしたことがある。原因は、Shapefileに付属する.prjファイルがepsg.ioに掲載されているものとわずかに異なる楕円体パラメータを使っていたことだった。解決策は、EPSGコードを調べて定義が一致するだろうと仮定するのではなく、gdalsrsinfo input.shpを使ってソースデータの.prj内容から直接proj4文字列を取得することだった。精度が求められる場面では、定義を常にソースデータ自体から導出すべきだ。

Python (pyproj)

Pyproj はPROJデータベースを丸ごと内蔵しているため、定義を手動で登録する必要がない。重要なパラメータはalways_xy=Trueだ。これを指定しないと、pyproj はEPSG標準の軸順序(地理CRSでは緯度が先)に従い、座標が暗黙にスワップされる。

from pyproj import Transformer

# pyproj knows all EPSG codes — no manual registration
transformer = Transformer.from_crs("EPSG:4326", "EPSG:2154", always_xy=True)
x, y = transformer.transform(2.3522, 48.8566)
print(x, y)
# → 652469.5, 6862035.9

コマンドライン (ogr2ogr)

Shapefile、GeoJSON、GeoPackageなどのファイルベースの再投影には、GDALのogr2ogrが標準ツールだ。データセット全体のバッチ変換を1コマンドで処理できる。

# Reproject a GeoJSON from WGS 84 to UTM Zone 50N
ogr2ogr -t_srs EPSG:32650 output.geojson input.geojson

# Reproject a Shapefile from OSGB 1936 to WGS 84
ogr2ogr -s_srs EPSG:27700 -t_srs EPSG:4326 output.shp input.shp

入力ファイルにCRSメタデータ(.prjファイルや埋め込み投影情報)がない場合は、-s_srsでソースCRSを明示的に指定する。ソースCRSがファイル内に定義済みなら、-t_srsだけで十分だ。

中国座標の変換(WGS 84、GCJ-02、BD-09)

中国ではすべての公開地図サービスに座標オフセットが義務付けられている。GCJ-02(火星座標)はKrasovsky 1940楕円体に基づく非線形アルゴリズムを使って、WGS 84の点を100〜700メートルずらす。BD-09はGCJ-02の上にさらにオフセットを加える。これらはEPSG登録されていないシステムであり、proj4やpyprojのような標準ツールでは変換できない。カスタムのオフセットアルゴリズムが必要だ。

オフセットが存在する理由は、中国の測量規制により公開されるデジタル地図はすべて暗号化された座標系を使用することが求められているためだ。順変換(WGS 84 → GCJ-02)は決定的な公式だ。逆変換(GCJ-02 → WGS 84)は閉形式の解がなく、反復近似が必要になる。通常10回の反復でサブメートル精度(約0.1m)が達成できる。

各サービスがどの座標系を使っているか:

地図サービス 座標系 WGS 84からのオフセット
Google Maps(中国版) GCJ-02 100〜700 m
Amap(高徳地図) GCJ-02 100〜700 m
テンセントマップ GCJ-02 100〜700 m
Apple Maps(中国版) GCJ-02 100〜700 m
百度地図 BD-09 100〜700 m + 追加オフセット
OpenStreetMap WGS 84 なし

変換チェーンは WGS 84 ↔ GCJ-02 ↔ BD-09 だ。GCJ-02を中間ステップとせずにWGS 84とBD-09を直接変換すると、結果が不正確になる。KunYuの座標変換ツールは、反復逆変換アルゴリズムを含む完全なチェーンを実装している。ほとんどの海外GISツールにはこの機能がない。

経験上、オフセットは中国西部で最も大きい。ウルムチ付近では600メートル超のずれを確認したことがあり、これは地点が川の対岸や高速道路の反対側に移るのに十分な誤差だ。上海のような東部沿岸都市ではオフセットが小さめ(200〜300メートル程度)だが、それでも無視できる量ではない。厄介な罠は、オフセットが国土全体で滑らかに変化することだ。1地点だけをスポットチェックしても見つけられない。北京で「合っているように見える」変換が、成都では目に見えてずれることがある。

座標変換

EPSG座標系間の変換。バッチ処理と自動検出に対応。

Try it now

座標変換でよくある5つのミス

座標変換エラーで最も多いのは、ソースCRSの選択ミス、軸順序の混同、Web Mercatorでの測定、測地基準変換の欠落、度表記の座標をすべてWGS 84と仮定してしまうことだ。いずれも微妙に誤った結果を生み、下流で実害を引き起こすまで気づかれないことがある。

ソースCRSの選択ミス

ソースデータがNAD83(EPSG:4269)なのにWGS 84(EPSG:4326)と指定した場合、北米のほとんどの地域で差は約1〜2メートルにとどまる。テストでは見落としやすいが、測量レベルの精度では重大だ。NAD27をWGS 84と混同するとさらに深刻で、場所によって10〜200メートルのオフセットが生じ、建物が隣の区画に配置されるのに十分だ。

軸順序の混同(lat/lon vs lon/lat)

EPSG:4326の正式な定義は緯度が先(lat, lng)だ。しかしGeoJSON、proj4js、LeafletのL.latlng()コンストラクタ(名前に反して)、そして多くのウェブ地図ライブラリは経度が先(lng, lat)を期待する。変換後の点が地図上でランダムに散らばっているがスケールはおおむね正しい場合、latとlonを入れ替えている可能性が高い。Pyprojのalways_xy=Trueパラメータはまさにこの罠を回避するために存在する。

Web Mercatorでの測定

EPSG:3857は表示用であり、計算用ではない。Web Mercatorで面積を計算すると高緯度で結果が膨張する。グリーンランドは実際の14倍の大きさに見える。距離・面積の計算には、まずローカルの投影CRS(UTMゾーンや国家座標系)に変換してから測定すべきだ。

測地基準変換の欠落

異なる測地基準系を持つCRS間の変換、例えばヨーロッパでのED50からETRS89への変換には、特定のパラメータを持つ測地基準変換が必要だ。汎用的なデフォルト変換を使うと数メートルの誤差が生じる可能性がある。GDALとpyprojはグリッドシフトファイルがあれば自動的に処理するが、使用された変換メソッドを確認すべきだ。

度表記の座標をすべてWGS 84と仮定する

度を単位とする地理CRSは多数ある:EPSG:4326(WGS 84)、EPSG:4269(NAD83)、EPSG:4490(CGCS2000)、EPSG:4612(JGD2000)。139.6917, 35.6895という点はこれらのどれでもありうる。フォーマットは同一だが使用する測地基準系が異なる。変換前に必ずソースメタデータを確認すること。

座標変換ツールの選び方

単発や少量の変換には、ブラウザベースのツールが最速だ。インストールもコードも不要。ファイルの再投影にはogr2ogrかQGISを使う。プログラムのパイプラインには、proj4js(JavaScript)またはpyproj(Python)が直接コードに組み込める。中国CRSについては、海外の大半のツールでは対応が不十分だ。

シナリオ 最適なツール 理由
単一ポイントの簡易チェック KunYu座標変換ツール ブラウザベース、6000以上のEPSG、中国CRS対応
ファイル再投影(SHP/GeoJSON) ogr2ogr (GDAL) あらゆるフォーマット、バッチ処理、スクリプト化可能
QGISプロジェクト レイヤの再投影ツール GUIワークフロー、属性とスタイルを保持
JavaScriptウェブアプリ proj4js クライアントサイド、約100KB、サーバー不要
Pythonデータパイプライン pyproj 完全なPROJデータベース、NumPy互換
中国CRS(GCJ-02/BD-09) KunYu座標変換ツール 他のツールにないオフセットアルゴリズムを内蔵

個人的には、繰り返す作業にはogr2ogrを使うことが多い。以前、自治体データセットから200以上のGeoJSONファイル(すべてローカルのLambert投影)をウェブアプリ用にWGS 84へ再投影する必要があった。QGISなら「エクスポート → 名前を付けて保存 → CRS設定」を200回クリックすることになる。ogr2ogrならシェルのワンライナーで済む:for f in *.geojson; do ogr2ogr -t_srs EPSG:4326 out/"$f" "$f"; done。1分もかからず完了した。再投影結果を目視確認したいときや、ソースCRSがファイルに埋め込まれていなくていくつかの候補をインタラクティブに試したいときは、QGISの方が適している。

EPSG検索

EPSG座標参照系データベースの検索・閲覧。

Try it now

FAQ

EPSG:4326からUTMに変換するには?

まず、座標がどのUTMゾーンに入るかを判定する:zone = floor((longitude + 180) / 6) + 1。次に、対応するEPSGコードを使って変換する。北半球はEPSG:326xx、南半球はEPSG:327xx(xxはゾーン番号)。例えば、東経116.4°の北京はUTM Zone 50Nに該当し、EPSG:32650になる。

座標変換(conversion)と測地基準変換(transformation)の違いは?

座標変換は同一の測地基準系上で地図投影を変更する(例:WGS 84地理座標 → WGS 84 / UTM)。測地基準変換は測地基準系そのものを変更する(例:NAD27 → WGS 84)ため、物理モデルパラメータが必要になる。pyprojやGDALのようなGISツールは両方を透過的に処理するが、精度の問題をデバッグするときにはこの区別が重要だ。

変換後の座標が数百メートルずれるのはなぜ?

最も多い原因はソースCRSの誤りだ。中国では、WGS 84とGCJ-02を混同すると100〜700メートルのオフセットが生じる。これはバグではなく仕様だ。中国以外では、NAD27とWGS 84の混同や、不正確な測地基準変換パラメータの使用が同様のエラーを引き起こす。変換前に必ずソースCRSを確認すること。

コーディングなしで座標を一括変換できるか?

できる。KunYuの座標変換ツールは複数の座標(1行に1点)を受け付け、ブラウザ上で直接バッチ変換が可能だ。登録不要で、データはデバイスから外に出ない。ShapefileやGeoJSONのファイルベースの一括変換には、フォーマット変換ツールまたはコマンドラインのogr2ogrが使える。

proj4jsとPROJは同じもの?

異なる。PROJはOSGeoコミュニティが保守するC/C++の座標変換ライブラリで、10,000以上のCRS定義とグリッドベースの測地基準変換をサポートしている。Proj4jsはそのJavaScriptポートで、同じコアアルゴリズムを実装しているが、デフォルトではEPSG:4326とEPSG:3857のみを含み、他のCRS定義はすべて手動登録が必要だ。

覚えておくべきこと

パターンは常に同じだ:ソースCRSを特定し、ターゲットCRSを特定し、変換を実行する。手早い確認にはオンライン変換ツールを使い、ファイルレベルの再投影にはogr2ogrを使い、アプリケーションコードにはproj4jsまたはpyprojを組み込む。どのEPSGコードが必要か分からなければ、EPSGコード解説から始めるか、EPSG Searchツールで直接検索するとよい。