トップ地図システム基礎技術 > 座標系および座標値

座標系および座標値

極座標系

極座標を整数化する

極座標は一般には浮動小数点で表されるが、OSM(OpenStreetMap)の場合、小数点以下7桁の精度である。

 <node id='1049' ... lat='34.0682913' lon='139.4840637'/>

このため、10の7乗を掛けて整数化すると、4バイト整数の範囲に収まる。 大量の座標値データを扱う場合、8バイトの浮動小数点として扱うよりも、 4バイト整数とした方がメモリ使用量やファイルサイズが半分で済む。また、計算時間も速い。

整数化した経度(int)、緯度(int)を合わせて一つの8バイト整数(long)で表す

JavaやC#などのメソッドの戻り値を座標値(経度・緯度)とするとき、 二つの値を配列やPointオブジェクトとして戻すには、インスタンスの生成が必要となり、 いずれ、ガーベージコレクションの対象となる。このメソッドの実行頻度が高い場合、 パフォーマンス上の負荷となる。

このため、経度と緯度をそれぞれ4バイト整数で表すときと経度、 緯度を合わせて一つの8バイト整数で表すときがある。

 long makeXY(int x, int y) { 
   return (((long)x) << 32) | (y & 0xffffffffL);
 }

x については、正負に関係なく、32ビット左にシフトするだけでよい。 y については、正に限定すれば & 0xffffffffL はなくてもよいが、 負の値に対しては必須となる。

度数をラジアンに変換する

  static double toRadian(double deg) {
      return deg*Math.PI/180.0;   // ラジアンに変換
  }

Google Map世界平面座標系

Google Map は世界地図を zoom 0 では1枚で表す[1]。

国土地理院の地図や OpenStreetMap など多くのデジタル地図がこの Google Map 方式を採用している。

左上(西北)端の座標を (0, 0)、右下(東南)端の座標を(1, 1)とする。

極座標との関係は以下の通りである。

  世界座標:(0,0)  =>  緯度経度:(85.05112877980659,-180)
  世界座標:(1,1)  =>  緯度経度:(-85.05112877980659,180)

zoom 1 では、上下左右に2分割して、合計4枚の地図(タイル)で表す。

このときのタイル座標は (0, 0)、(1, 0)、(0, 1)、(1, 1) となる。

(0,0)(1,0)
(0,1)(1,1)

zoom 2、3、4、... とこれを繰り返す単純なしくみになっている。

極座標からXY平面座標への変換

Java言語による極座標からXY平面座標への変換メソッドを下に示す。 経度からX座標への変換はシンプルであるが、緯度からY座標への変換は少し複雑である。

極座標で表された地点を地図に表示するには極座標からXY平面座標への変換が必要となる。

  double Lon2X(double lon, int zoom) {
      return (lon + 180.0) / 360.0 * (1<<zoom);
  }

  double Lat2Y(double lat, int zoom) {
      return (1 - Math.log(Math.tan(lat*Math.PI/180.0) +
              1 / Math.cos(lat*Math.PI/180.0))/Math.PI)/2.0 * (1<<zoom);
  }

XY平面座標から極座標への変換

地図上でクリックした地点の極座標を求めるにはXY平面座標から極座標への変換が必要となる。

上記の逆変換であり、当然、X座標から経度への変換は簡単であり、Y座標から緯度への変換は複雑である。 これがメルカトル図法の特徴である。

  double X2Lon(double x, int z) {
      return x / Math.pow(2.0, z) * 360.0 - 180.0;
  }

  double Y2Lat(double y, int z) {
      double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z);
      return 180.0 / Math.PI * Math.atan(Math.sinh(n));
  }

整数化したXY平面座標

OSMでは極座標は 10の7乗をかけて、32ビット整数化することが多い。 double では 8バイトであるが、この整数化では、小数点以下7桁の精度が保たれ、 メモリやファイルサイズが double型の半分で済む。

XY平面座標の場合、unsigned int型の場合、最高精度としては double型の (0.0, 0.0) ~ (1.0, 1.0) を (0, 0) ~ (232, 232) に変換することである。 ただし、経度180度は-180度と同じであるから、232(経度180度)は 0 (経度-180度)とする。 すなわち、数値の範囲は (0, 0) ~ (232-1, 232-1) である。

C/C++ や C# など多くのプログラミング言語に unsigned int 型があるが、Java には unsigned int型はない。 Java でも int型で数値の範囲 (0, 0) ~ (232-1, 232-1) を表現することはできるが、 少し分かりずらい。 1ビット精度を下げて double型の (0.0, 0.0) ~ (1.0, 1.0) を (0, 0) ~ (231-1, 231-1) とした方が無難である。

自作地図アプリでは、更に精度を1ビットさげて double型の (0.0, 0.0) ~ (1.0, 1.0) を (0, 0) ~ (230-1, 230-1) としている。 日常使う地図としては十二分な精度である。

将来的には double型の (0.0, 0.0) ~ (1.0, 1.0) を (0, 0) ~ (231-1, 231-1) とするかも知れない。この場合には 231(経度180度) を 0 (経度-180度)とする必要がある。

更には、最高精度の(0, 0) ~ (232-1, 232-1) とするかも知れない。

現在は 230 を掛けた値を使う。

参考記事

[1] 世界は1枚の画像から : グーグルマップのしくみを探る(1)