UP | HOME

高度なトピック

ここでは、maprayJSライブラリを利用した開発の高度なトピックについて説明します。

1. データプロバイダの作成

Maprayエンジンはレンダリング時にデータプロバイダから地表の形状(DEM)や地図画像データを取得します。 ライブラリでは StandardDemProviderStandardImageProvider を用意していますが、これらのクラスの機能だけでは対応できないとき、コンテンツ開発者が独自のデータプロバイダを実装できます。 1つのデータプロバイダは固定サイズのタイルを単位にデータを提供します。たとえば典型的な地図画像プロバイダは 256×256 サイズの画像タイルを提供します。 提供される個々のタイルは Z/X/Y 座標で区別されます。Maprayエンジンはデータプロバイダにタイルを要求するときに Z/X/Y 座標を指定します。

1.1. タイルの座標

Z/X/Y 座標の \(Z\) はズームレベルを表し、 \(Z > 0\) となる整数です。 \(X\) はそのズームレベル上のタイルの水平方向の位置、 \(Y\) は垂直方向の位置を表し、 \(0 \le X, Y \le 2^Z - 1\) となる整数です。 北緯および南緯約 85.0511 度以上を除外した、正方形のメルカトル世界地図上に Z/X/Y 座標が定義されます。 \(Z\) 座標が \(z\) のタイルは、この正方形地図を縦横均等に \(2^z\) 分割された領域に対応します。 \(X\) 座標は一番左側のタイルが \(0\) で、一番右側のタイルが \(2^z - 1\) になり、 \(Y\) 座標は一番上側のタイルの \(0\) で、一番下側のタイルが \(2^z - 1\) になります。 数式で表すと、経度 \(\lambda\), 緯度 \(\phi\) と Z/X/Y 座標の関係は次のようになります。ここで \(\mathrm{gd}\) はグーデルマン関数を、 \(c_1, c_2\) は任意の整数を表します。

\begin{equation} \begin{aligned} \lambda &= 180\deg (2^{1-Z}X - 1) + 360\deg c_1 \\ \phi &= \mathrm{gd}(180\deg (1 - 2^{1-Z}Y)) + 360\deg c_2 \\ \end{aligned} \end{equation}

1.2. DEMデータプロバイダの実装

独自のDEMデータプロバイダを定義するには DemProvider を利用します。 このクラスは、コンストラクタ引数で指定される DemProvider.Hook により動作が決定します。 独自の動作をするような DemProvider.Hook を実装することで実現することができます。

DemProvider.Hook を実装するには、下記二つの関数を定義する必要があります。

  • init( options?: { signal?: AbortSignal } ): Promise<DemProvider.Info>
  • requestTile( z: number, x: number, y: number, options?: { signal?: AbortSignal } ): Promise<ArrayBuffer>

init() はタイルプロバイダを初期化してリクエストできる状態にするメソッドです。 レンダラが一度だけ呼び出します。 この関数の戻り値としてこのプロバイダの情報をリターンする必要があります。 リクエストできる状態に遷移できなかった場合は必ず例外をスローしなければなりません。

requestTile() はMaprayエンジンがタイルをリクエストするときに呼び出すメソッドです。 この関数は init() が呼ばれ、エラーが起きなかった場合にのみ呼ばれる関数です。 このメソッドの z , x , y パラメータはタイルの座標で、カメラ位置に応じてアクセスされるタイルは変わります。 signal パラメータは、タイルのリクエストを取り消すときに設定します。

以下は単純なDEMデータプロバイダ MyDemProviderHook の実装例です。 この例では https://user-server/... にタイルデータが配信されている想定で書かれています。

/**
 * https://user-server/ で配信されるタイルを表示するためのプロバイダーフック
 */
export class MyDemProviderHook implements DemProvider.Hook {

    async init()
    {
        // 何らかのAPI呼び出しなどを行いタイル情報を取得し、必要な情報をリターンします。
        const response = await fetch( `https://user-server/...json`, {
            headers: {
                Authorization: "Bearer: (アクセストークン)", // アクセストークンなど接続に必要な認証情報を付与します
            },
        } );

        if ( !response.ok ) {
            throw new Error( "Failed to get info" );
        }

        const info = await response.json();

        // 必要に応じて認証情報などをインスタンス変数などで保持します。
        const resolution_power = info.resolution_power;
        return {
            resolution_power: resolution_power,
        };
    }


    async requestTile( z: number, x: number, y: number ): Promise<ArrayBuffer>
    {
        // サーバへアクセスを行いタイルデータを取得します。
        const url = "https://user-server/" + z + "/" + x + "/" + y + ".bin";

        const response = await fetch( url, {
            headers: {
                Authorization: "Bearer: (アクセストークン)", // アクセストークンなど接続に必要な認証情報を付与します
            },
        } );

        if ( !response.ok ) {
            throw new Error( "Failed to get tile" );
        }

        return await response.arrayBuffer();
    }

}

上記のように init(), requestTile() の最後の引数 { signal?: AbortSignal } は省略することができます。

MyDemProviderHook を利用するには、下記のように DemProvider の引数に Hook を指定します。

new DemProvider( new MyDemProviderHook() );

1.3. 地図画像プロバイダの実装

独自の地図画像プロバイダを定義するためには ImageProvider を利用します。 このクラスは、コンストラクタ引数で指定される ImageProvider.Hook により動作が決定します。 独自の動作をするような ImageProvider.Hook を実装することで実現することができます。

ImageProvider.Hook を実装するには、下記二つの関数を定義する必要があります。

  • init( options?: { signal?: AbortSignal } ): Promise<DemProvider.Info>
  • requestTile( z: number, x: number, y: number, options?: { signal?: AbortSignal } ): Promise<ImageProvider.SupportedImageTypes>

init() はタイルプロバイダを初期化してリクエストできる状態にするメソッドです。 レンダラが一度だけ呼び出します。 この関数の戻り値としてこのプロバイダの情報をリターンする必要があります。 リクエストできる状態に遷移できなかった場合は必ず例外をスローしなければなりません。

requestTile() はMaprayエンジンがタイルをリクエストするときに呼び出すメソッドです。 この関数は init() が呼ばれ、エラーが起きなかった場合にのみ呼ばれる関数です。 このメソッドの z , x , y パラメータはタイルの座標で、カメラ位置に応じてアクセスされるタイルは変わります。 signal パラメータは、タイルのリクエストを取り消すときに設定します。

以下は単純な地図画像プロバイダ MyImageProviderHook の実装例です。 この例では https://user-server/images/... にタイルデータが配信されている想定で書かれています。

/**
 * https://user-server/images/ で配信される画像タイルを表示するためのプロバイダーフック
 */
export class MyImageProviderHook implements ImageProvider.Hook {

    async init()
    {
        // 何らかのAPI呼び出しなどを行いタイル情報を取得し、必要な情報をリターンします。
        const response = await fetch( `https://user-server/images/...json`, {
            headers: {
                Authorization: "Bearer: (アクセストークン)", // アクセストークンなど接続に必要な認証情報を付与します
            },
        } );

        if ( !response.ok ) {
            throw new Error( "Failed to get info" );
        }

        const info = await response.json();

        // 必要に応じて認証情報などをインスタンス変数などで保持します。
        const image_size = info.image_size;
        const min_level = info.min_level;
        const max_level = info.max_level;
        return {
            image_size: image_size,
            zoom_level_range: new ImageProvider.Range( min_level, max_level ),
        };
    }


    async requestTile( z: number, x: number, y: number ): Promise<ImageProvider.SupportedImageTypes>
    {
        // サーバへアクセスを行いタイルデータを取得します。
        const url = "https://user-server/images/" + z + "/" + x + "/" + y + ".png";

        return await new Promise( (resolve, reject) => {
                const image = new Image();
                image.onload  = event => resolve( image );
                image.onerror = event => reject( new Error("Failed to load image") );
                image.src = url;
        } );
    }

}

上記のように init(), requestTile() の最後の引数 { signal?: AbortSignal } は省略することができます。

MyImageProviderHook を利用するには、下記のように ImageProvider の引数に Hook を指定します。

new ImageProvider( new MyImageProviderHook() );

2. 地形の問い合わせ

コンテンツでマウスなどのインタラクティブ操作を実現する場合、地形に合わせてカメラを動かしたいことがあります。

たとえばカメラが地表の下に移動しないようにするため、カメラ真下の地表の標高を知ることにより、その場所よりも高い場所にカメラ位置を調整できます。

また、マウスのドラッグにより地表を掴んで動かすイメージの操作を実現するとき、ドラッグ開始時のマウスカーソル位置に表示されている地表の位置を知ると、それによりカメラの位置を計算できます。

maprayJS APIでは、DEMデータから地形の情報を得るメソッドとして次のメソッドを用意しています。

  • Viewer#getElevation(lat, lon)
  • Viewer#getRayIntersection(ray)

    Viewer#getElevation() は引数に緯度と経度を与えてその場所の標高を得ます。

    Viewer#getRayIntersection() は引数にレイ(始点と方向)を与えて、そのレイと地表が交差するもっとも近い点の座標 (GOCS) を得ます。

Maprayのシーンが表示されているキャンバス上のある点に対応するレイを次のメソッドにより取得できます。

  • Camera#getCanvasRay(cpos, oray)

2.1. 精度に関する注意点

Viewer#getElevation()Viewer#getRayIntersection() により得られる値は、その時の状況により精度が変化します。 これは現在メモリ内に存在するもっとも精度が高いDEMデータを使い、即時に値を計算しているからです。

Viewer#getElevation() は、さらに詳細なDEMデータが DemProvider により取得することができれば、そのデータをリクエストします。そのため、時間を置いてこのメソッドを呼び出すと、さらに正確な値を取得できることがあります。

これに対し Viewer#getRayIntersection() は新たにDEMデータをリクエストしません。このメソッドは基本的に現在画面に表示されてる場所が対象になり、一般的に、近くに表示されている部分は精度が高く、遠くに表示されている部分は精度が低くなります。

3. エンティティの表示

Maprayエンジンは地表と同時にポリゴンモデルや文字などのオブジェクトも表示できます。

fuji-motosuko.png

maprayJS APIではこのオブジェクトのことをエンティティと呼び、 Scene インスタンスに Entity インスタンスを追加することによってエンティティを表示できます。 Scene インスタンスは Viewer#scene プロパティによりアクセスできます。

maprayJSライブラリは現在、以下に示す Entity のサブクラスを用意しています。

クラス 表示内容
GenericEntity ポリゴンモデル
TextEntity 複数のテキスト
MarkerLineEntity 連続ライン

次のコードは TextEntity を使い、シーンに2つのテキストを表示する例です。

var entity = new mapray.TextEntity( viewer.scene );

entity.addText( "富士山",
                [-3911845.4, 3433281.3, 3693469.5],
                { color: [1, 1, 0], font_size: 30 } );

entity.addText( "本栖湖",
                [-3896100.8, 3437552.8, 3700948.3],
                { color: [0, 1, 1], font_size: 25 } );

viewer.scene.addEntity( entity );

各エンティティにはさまざまなプロパティがあります。詳細はリファレンスマニュアルを参照してください。