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

グーグルマップは知的好奇心を満たしてくれる最高の対象です。
調べてゆくとおもしろいことが分かりそうです。
基本的なことから少しずつその仕組みを探ってゆきたいと思います。簡単なgoogle maps APIモドキの作成までを目標とします。なお、検証にはGoogle Maps JavaScript API V3 を使います(APIキーが不要なのでとても使いやすいです)。

グーグルマップは画像の地図(ラスターマップ)ですので、地球上の実際の位置と地図画像の位置を対応付ける必要があります。例えば、北緯35度、東経140度を中心にした地図を表示したい場合、その位置が画像のどこに対応するのかを一意的に求めなければなりません。

まずは、3回に渡り位置の対応の付け方について調べてみます。グーグルのチュートリアルに説明がありますが、自分なりに解釈してゆきたいと思います。

地球上のある位置をディスプレイ画面上の画像に対応付けるためには、

 1.地球上の位置を、緯度経度で表す。
 2.緯度経度を平面地図上の独自座標(世界座標)に一意に変換する。
 3.ズームレベルに応じて、世界座標をピクセル座標に変換する。

このような順序で地図画像上のピクセル座標と対応付けて、ディスプレイに表示を行います。
それでは、一つずつ説明してゆきます。

1.地球上の位置を、緯度経度で表す

原点の位置と座標軸が決まれば、三次元空間における地球上の位置は(x,y,z)で表すことができます。

しかし、通常それでは分かりづらいので、緯度経度(lat,lng)(または(B,L))で表します。緯度と経度だと、2つの値で地球上の位置を表すことができます。そのためには、座標系の他に、地球の形を定義する必要があります。そうしないと、地球上の位置を一意に2つの値だけで表すことができません。通常、地球は楕円体とみなします。

グーグルマップでは、この座標系と楕円体にWGS84規格を利用しています。WGSはWorld Geodetic Systemの略なので、直訳して「世界測地系」と呼ばれていますが、これが世界唯一の標準ということではありません(座標系ではITRF系のほうが世界測地系のイメージに近いかもしれません)。

2.緯度経度を世界座標に変換する

今表示したい場所の緯度経度が分かったら、その値を一意に世界座標に変換します。この世界座標はグーグルマップ上での座標であり、グーグルが独自に定めたものであり、世界標準という意味ではありません。

緯度経度で示される楕円体上の点を平面地図に写すには、様々な方法(図法、投影法)があります。グーグルマップでは、オーソドックスなメルカトル図法を用いて、経線どうしはお互いに平行で(緯線も同様)、経線と緯線は直行しています。タイル状の画像に分割する方法には向いている方法だと思います。

そして、グーグルはこの世界座標を(0,0)から(256,256)の範囲で設定しています。これは、何を隠そう、世界を256ピクセル*256ピクセルの1枚の画像で表した以下の地図です。

→+X、↓+Y

ここで、世界座標の原点は左上隅であることに注意してください。X軸の正方向は右方向、Y軸の正方向は下方向になります。

この図の下に、google maps API v3を使って、世界座標の原点(0,0)、最端点(256,256)に相当する緯度経度を計算してみました。緯度は最大85.0511287798066度になります。90度に満たないのは、90度まで表示しようとすると縦方向の長さが無限大になってしまうからです。なお、この端数の意味は赤道がすべて収まるように、世界を正方形で切り出すとこの緯度の値になるはずです。3回目で変換式を検証したいと思います。

また、経度は上の地図の中心がゼロになり、左側が負値、右側が正値になります。すなわち、日本の経度はプラスの値、アメリカではマイナスの値になります。上の地図で左右の境界はどちらも-180度になってしまいますので、留意してください。そのため、世界座標(255.9,255.9)に対応する緯度経度も示しておきました。

さらに、地球上の地点として北緯35度、東経138度に対応する世界座標を同様にAPIを使って、計算してみました。世界を0~256までの実数に押し込んでいるので、小数点以下がかなり細かいのが分かります。なお、世界座標はxyで表されるので、緯度経度とは値の順番が逆になりますので、注意してください。

3.ズームレベルに応じて、世界座標をピクセル座標に変換する

グーグルマップのズームレベルは0~19の値が用いられます。上記の通りズームレベル=0が世界を1枚のタイルで示したものです。ズームレベル=1は4枚になります。ズームレベル=19は219×219枚になります。

すなわち、ピクセル座標はズームレベルに応じて範囲が変わるのです。ズームレベル=0では(0,0)~(256,256)、ズームレベル=19では(0,0)~(256×219,256×219)のピクセル座標が使用されます。

そして、ここからがグーグルマップのウマイところです。
世界座標は先ほどのとおり、緯度経度から一意に定まる値であり、ズームレベル=0のときは画像のサイズ256ピクセルと同じ範囲の値です。ズームレベル=1になると世界は4枚の画像、つまり512×512ピクセルになります。この画像上の位置を割り出すには、世界座標に2を掛けるだけです。

世界座標にズームレベルに応じた倍率(2zoomlevel)を掛ければ、その値がそのままそのズームレベルのときのピクセル座標になってくれるのです。すなわち、

 x_{pixel}=x_{world}\times2^{zoomlevel}
 y_{pixel}=y_{world}\times2^{zoomlevel}

これにより、世界座標が求まっていれば、ズームレベルが変わっても簡単に、そのズームレベルの画像上の位置を割り出すことができるのです。

とてもクールな設計だと思いませんか。次回はピクセル座標についてまとめてみます。

参考のため、検証に使ったコード(javascript部分)を以下にのせておきます。

    function initialize() {
        var latlng = new google.maps.LatLng(0, 0);
        var myOptions = {
            disableDefaultUI: false,
            navigationControl:false,
            scrollwheel: false,
            draggable:false,
            streetViewControl:false,
            zoom: 0,
            center: latlng,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

        var upperLeft_xy = new google.maps.Point(0, 0);
        var lowerRight_xy = new google.maps.Point(256, 256);
        var nlowerRight_xy = new google.maps.Point(255.9, 255.9);
        var center_dd = new google.maps.LatLng(35,138);

        google.maps.event.addListener(map, 'bounds_changed', function () {
            var proj = map.getProjection();
            if (proj) {
                var upperLeft_dd = proj.fromPointToLatLng(upperLeft_xy);
                var lowerRight_dd = proj.fromPointToLatLng(lowerRight_xy);
                var nlowerRight_dd = proj.fromPointToLatLng(nlowerRight_xy);
                var center_xy = proj.fromLatLngToPoint(center_dd);

                document.getElementById('upperLeft').innerHTML = "緯度経度:(" + upperLeft_dd.lat().toString() + "," + upperLeft_dd.lng().toString() + ")";
                document.getElementById('lowerRight').innerHTML = "緯度経度:(" + lowerRight_dd.lat().toString() + "," + lowerRight_dd.lng().toString() + ")";
                document.getElementById('nlowerRight').innerHTML = "緯度経度:(" + nlowerRight_dd.lat().toString() + "," + nlowerRight_dd.lng().toString() + ")";
                document.getElementById('center').innerHTML = "世界座標:(" + center_xy.x.toString() + "," + center_xy.y.toString() + ")";
            }
        });
    }  

google maps apiでは、ProjectionオブジェクトのfromPointToLatLng、fromPointToLatLngメソッドを利用して、世界座標から緯度経度、緯度経度から世界座標に変換することができます。