Source: MaprayApi.js

import HTTP, { FetchError } from "./HTTP";
import { Dataset, Dataset3D, PointCloudDataset } from "./MaprayApiModel";
import { DatasetResource, Dataset3DSceneResource, PointCloudDatasetResource,  }  from "./MaprayApiResource";



class MaprayApiError extends FetchError {
    constructor( code, message, url, response, cause )
    {
        super( message + " [" + code + "]", url );
        if ( Error.captureStackTrace ) {
            Error.captureStackTrace( this, MaprayApiError );
        }
        this.name = "MaprayApiError";
        this.code = code;
        this.resonse = response;
        this.cause = cause;
        if (cause) {
            this.stack += "\nCaused-By: " + cause.stack;
        }
    }
}



/**
 * @summary Mapray Cloudへアクセスするためのクラスです。
 * @classdesc
 * <p>
 * このクラスを利用するには事前にMapray Cloudアカウントを作成する必要があります。
 * <a href="https://cloud.mapray.com/">https://cloud.mapray.com</a>からサインアップすることができます。
 * </p>
 * <p>
 * 事前に下記の情報を調べておきます。
 * </p>
 * <dl>
 * <dt>User ID
 * <dd>Mapray Cloudの<a href="https://cloud.mapray.com/settings" target="_blank">ユーザー情報ページ</a>から確認します。
 * 右上メニューのAccountメニューからこのページを開くことができます。
 * <dt>Token
 * <dd>Mapray Cloudの<a href="https://cloud.mapray.com/dashboard" target="_blank">Tokenページ</a>でトークンを作成します。
 * 上部のTokensタブからこのページを開くことができます。
 * <dt>データセット等のID
 * <dd>Mapray Cloudへデータをアップロードし、そのデータを使用するには、<a href="https://cloud.mapray.com/datasetslist">データセットページ</a>
 * からGeoJsonやglTFデータをアップロードしておき、表中のIDを確認します。
 * </dl>
 *
 * @memberof mapray
 * @example
 * const maprayApi = new mapray.MaprayApi({
 *         basePath: "https://api.mapray.com",
 *         version: "v1",
 *         userId: "...",
 *         token: "..."
 * });
 * const datasets = await maprayApi.getDatasets();
 * ...
 */
class MaprayApi extends HTTP {

    /**
     * @param {object} option
     * @param {string} [option.basePath=https://cloud.mapray.com] Mapray CloudのURLを指定します。通常は省略します。
     * @param {string} option.version Mapray Cloud の APIバージョン "v1" のように指定します。
     * @param {string} option.userId Mapray Cloud アカウントの User ID を指定します。
     * @param {string} option.token Mapray Cloud で生成した Token を指定します。
     */
    constructor( option = {} )
    {
        super();
        var basePath = option.basePath.endsWith("/") ? option.basePath.slice(0, -1) : option.basePath;
        this._option = {
            basePath: basePath || DEFAULT_BASE_PATH,
            version: option.version,
            token: option.token,
            userId: option.userId
        };
    }


    // Dataset, 3DDataset, PointCloudDataset

    /**
     * @summary データセットのリストを取得します。
     * ページごとにデータセットリストを取得します。
     * @param {number} [page=1] 取得する要素のページ番号
     * @param {number} [limit=5] 1ページに含まれる要素数。最大100まで指定することができます。
     * @return {Dataset[]}
     */
    async loadDatasets( page=1, limit=5 ) {
        const datasets_json = await this.getDatasets( page, limit );
        return datasets_json.map( dataset_json => Dataset.createFromJson( this, dataset_json ) );
    }

    /**
     * @summary 指定したIDのデータセットを取得します。
     * @param {string} datasetId データセットのID
     * @return {Dataset}
     */
    async loadDataset( datasetId ) {
        const dataset_json = await this.getDataset( datasetId );
        return Dataset.createFromJson( this, dataset_json );
    }

    /**
     * @summary 3Dデータセットのリストを取得します。
     * ページごとにデータセットリストを取得します。
     * @param {number} [page=1] 取得する要素のページ番号
     * @param {number} [limit=5] 1ページに含まれる要素数。最大100まで指定することができます。
     * @return {Dataset3D[]}
     */
    async load3DDatasets( page=1, limit=5 ) {
        const datasets_json = await this.get3DDatasets( page, limit );
        return datasets_json.map( dataset_json => Dataset3D.createFromJson( this, dataset_json ) );
    }

    /**
     * @summary 指定したIDの3Dデータセットを取得します。
     * @param {string} datasetId
     * @return {Dataset3D}
     */
    async load3DDataset( datasetId ) {
        const dataset_json = await this.get3DDataset( datasetId );
        return Dataset3D.createFromJson( this, dataset_json )
    }

    /**
     * @summary 点群データセットのリストを取得します。
     * ページごとにデータセットリストを取得します。
     * @param {number} [page=1] 取得する要素のページ番号
     * @param {number} [limit=5] 1ページに含まれる要素数。最大100まで指定することができます。
     * @return {PointCloudDataset[]}
     */
    async loadPointCloudDatasets( page=1, limit=5 ) {
        const datasets_json = await this.getPointCloudDatasets( page, limit );
        return datasets_json.map( dataset_json => PointCloudDataset.createFromJson( this, dataset_json ) );
    }

    /**
     * @summary 指定したIDの点群データセットを取得します。
     * @param {string} datasetId データセットID
     * @return {PointCloudDataset}
     */
    async loadPointCloudDataset( datasetId ) {
        const dataset_json = await this.getPointCloudDataset( datasetId );
        return PointCloudDataset.createFromJson( this, dataset_json );
    }


    // Resources

    /**
     * @summary 指定したIDのデータセットをリソースとして取得します。
     * @param {string} datasetId データセットID
     * @return {Resource}
     */
    getDatasetAsResource( datasetId ) {
        return new DatasetResource( this, datasetId );
    }

    /**
     * @summary 指定したIDの3Dデータセットのシーンファイルをリソースとして取得します。
     * @param {string[]} datasetId データセットIDのリスト
     * @return {Resource}
     */
    get3DDatasetAsResource( datasetIds ) {
        return new Dataset3DSceneResource( this, datasetIds );
    }

    /**
     * @summary 指定したIDの点群データセットの定義ファイルをリソースとして取得します。
     * @param {string} datasetId データセットID
     * @return {Resource}
     */
    getPointCloudDatasetAsResource( datasetId ) {
        return new PointCloudDatasetResource( this, datasetId );
    }


    // RestAPI

    /**
     * @summary データセットリストを取得します
     * @param {number} [page=1] 取得する要素のページ番号
     * @param {number} [limit=5] 1ページに含まれる要素数。最大100まで指定することができます。
     * @return {object} json
     */
    async getDatasets( page=1, limit=5 )
    {
        var opt = this._option;
        return await this.get( "datasets", [ opt.userId ], { page, limit } );
    }

    /**
     * @summary get dataset
     * @param {string} datasetId
     * @return {object} json
     */
    async getDataset( datasetId )
    {
        var opt = this._option;
        return await this.get( "datasets", [ opt.userId, datasetId ], null );
    }

    /**
     * @summary データセットを作成します。
     * @param {string} name 名前
     * @param {string} description 説明
     * @return {object}
     */
    async createDataset( name, description )
    {
        var opt = this._option;
        var body = {
            name,
            description
        };
        return await this.post( "datasets", [ opt.userId ], null, body );
    }

    /**
     * @summary データセットを削除します。
     * @return {object} json
     */
    async deleteDataset( datasetId/*, option={ wait: true }*/ )
    {
        var opt = this._option;
        return await this.delete( "datasets", [ opt.userId, datasetId ] );
    }

    /**
     * @summary GeoJSONの内容を取得します。
     * @param {string} datasetId データセットID
     * @return {object} json
     */
    async getFeatures( datasetId ) {
        var opt = this._option;
        return await this.get( "datasets", [ opt.userId, datasetId, "features" ] );
    }

    /**
     * @summary GeoJSON要素をアップロード(挿入)します。
     * @param {string} datasetId データセットID
     * @return {object} json
     */
    async insertFeature( datasetId, feature ) {
        var opt = this._option;
        return await this.post( "datasets", [ opt.userId, datasetId, "features" ], null, feature );
    }

    /**
     * @summary GeoJSON要素を更新(上書き)します。
     * @param {string} datasetId データセットID
     * @param {string} featureId GeoJSON要素ID
     * @param {object} feature GeoJSON要素
     * @return {object} json
     */
    async updateFeature( datasetId, featureId, feature )
    {
        var opt = this._option;
        return await this.put( "datasets", [ opt.userId, "features", featureId ], null, feature );
    }

    /**
     * @summary 3Dデータセットのリストを取得します。
     * @param {number} [page=1] 取得する要素のページ番号
     * @param {number} [limit=5] 1ページに含まれる要素数。最大100まで指定することができます。
     * @return {object} json
     */
    async get3DDatasets( page=1, limit=5 ) {
        const opt = this._option;
        return await this.get( "3ddatasets", [ opt.userId ], { page, limit } );
    }

    /**
     * @summary 3D datastを作成します。
     * @param {string} name 名前
     * @param {string} description 説明
     * @param {object} option
     * @param {string} option.path glTFファイルのパスを指定します(アップロードする際はディレクトリを指定するため、ディレクトリルートからのglTFファイルへのパスを指定します)
     * @param {string} option.format "glTF"を指定します
     * @param {string} option.srid 現在は4326(WGS 84)を指定します
     * @param {number} option.x 経度
     * @param {number} option.y 緯度
     * @param {number} option.z 高さ
     * @return {object} json
     */
    async create3DDataset( name, description, option ) {
        const opt = this._option;
        const body = {
            name,
            description,
            path: option.path,
            format: option.format,
            srid: option.srid,
            x: option.x,
            y: option.y,
            z: option.z
        };
        return await this.post( "3ddatasets", [ opt.userId ], null, body );
    }

    /**
     * @summary 3Dデータセットを更新します。
     * @param {string} datasetId データセットId
     * @param {string} name 名前
     * @param {string} description 説明
     * @param {object} option
     * @param {string} option.path glTFファイルのパスを指定します(アップロードする際はディレクトリを指定するため、ディレクトリルートからのglTFファイルへのパスを指定します)
     * @param {string} option.format "glTF"を指定します
     * @param {string} option.srid 現在は4326(WGS 84)を指定します
     * @param {number} option.x 経度
     * @param {number} option.y 緯度
     * @param {number} option.z 高さ
     * @return {object} json
     */
    async update3DDataset( datasetId, name, description, option ) {
        const opt = this._option;
        const body = {
            name,
            description,
            path: option.path,
            format: option.format,
            srid: option.srid,
            x: option.x,
            y: option.y,
            z: option.z
        };
        return await this.patch( "3ddatasets", [ opt.userId, datasetId ], null, body );
    }

    /**
     * @summary 3Dデータセットアップロード用URLを取得します。
     * @param {string} datasetId データセットId
     * @return {object} json
     */
    async create3DDatasetUploadUrl( datasetId ) {
        const opt = this._option;
        return await this.post( "3ddatasets", [ "uploads", opt.userId, datasetId ], null, {} );
    }

    /**
     * @summary 3Dデータセット情報を取得します。
     * データセットが保持するデータにアクセスするには、get3DDatasetScene()を利用します。
     * @param {string} datasetId データセットId
     * @return {object} json
     */
    async get3DDataset( datasetId ) {
        const opt = this._option;
        return await this.get( "3ddatasets", [ opt.userId, datasetId ], null );
    }

    /**
     * @summary 3Dデータセットを削除します。
     * @param {string} datasetId データセットId
     * @return {object} json
     */
    async delete3DDataset( datasetId ) {
        const opt = this._option;
        return await this.delete( "3ddatasets", [ opt.userId, datasetId ] );
    }

    /**
     * @summary 3Dデータセットに含まれる scene情報 を取得します。
     * @param {string|string[]} datasetIds
     * @return {object} シーンファイルの実体
     */
    async get3DDatasetScene( datasetIds ) {
        const opt = this._option;
        const response = await this.get( "3ddatasets", [ "scene", opt.userId ], { "3ddatasets_ids": Array.isArray(datasetIds) ? datasetIds.join(",") : datasetIds } );
        response.entity_list.forEach(entity => {
                const indexStr = entity.index;
                const index = parseInt(indexStr);
                if (index.toString() !== indexStr) {
                    throw new Error("Internal Error: ID couldn't be convert to 'number'");
                }
                entity.index = index;
        });
        return response;
    }

    /**
     * @summary 点群データセットリストを取得します。
     * @param {number} [page=1] 取得する要素のページ番号
     * @param {number} [limit=5] 1ページに含まれる要素数。最大100まで指定することができます。
     * @return {object} json
     */
    async getPointCloudDatasets( page=1, limit=5 ) {
        const opt = this._option;
        return await this.get( "pcdatasets", [ opt.userId ], { page, limit } );
    }

    /**
     * @summary 点群データセットを取得します。
     * @param {string} datasetId データセットId
     * @return {object} json
     */
    async getPointCloudDataset( datasetId ) {
        const opt = this._option;
        return await this.get( "pcdatasets", [ opt.userId, datasetId ] )
    }

    /**
     * @private
     * @return {object} json
     */
    async get( api, args, query, option={} )
    {
        return await this.fetchAPI( HTTP.METHOD.GET, api, args, query, null, option );
    }

    /**
     * @private
     * @return {object} json
     */
    async post( api, args, query, body, option={} )
    {
        if ( typeof( body ) !== "string" ) {
            body = JSON.stringify(body);
        }
        return await this.fetchAPI( HTTP.METHOD.POST, api, args, query, body, option );
    }

    /**
     * @private
     * @return {object} json
     */
    async patch( api, args, query, body, option={} )
    {
        if ( typeof( body ) !== "string" ) {
            body = JSON.stringify(body);
        }
        return await this.fetchAPI( HTTP.METHOD.PATCH, api, args, query, body, option );
    }

    /**
     * @private
     * @return {object} json
     */
    async put( api, args, query, body, option={} )
    {
        if ( typeof( body ) !== "string" ) {
            body = JSON.stringify(body);
        }
        return await this.fetchAPI( HTTP.METHOD.PUT, api, args, query, body, option );
    }

    /**
     * @private
     * @return {object} json
     */
    async delete( api, args, query, option={} )
    {
        return await this.fetchAPI( HTTP.METHOD.DELETE, api, args, query, null, option );
    }

    /**
     * @private
     * @return {object} json
     */
    async fetchAPI( method, api, args, query, body, option={} )
    {
        var opt = this._option;
        var url = opt.basePath + "/" + api + "/" + opt.version + (args.length > 0 ? "/" + args.join("/") : "");
        // console.log( "MaprayAPI: " + method + " " + api + " (" + args.join("/") + ")" );
        const response = await this.fetch( method, url, query, body, option );
        return await response.json();
    }

    /**
     * @private
     */
    async fetch( method, url, query, body, option={} )
    {
        var opt = this._option;
        var headers = option.headers || (option.headers={});
        headers["x-api-key"] = opt.token;

        let response;
        try {
            response = await HTTP.fetch( method, url, query, body, option );
        }
        catch( error ) {
            if ( error.name === "FetchError" && error.response ) {
                let errorResponseJson;
                try {
                    errorResponseJson = await error.response.json();
                }
                catch( additionalError ) {
                    // Couldn't get additional info of the error.
                    // throw original error.
                    throw new MaprayApiError( -1, "Failed to fetch", url, null, error );
                }
                throw new MaprayApiError( errorObject.code, errorObject.error, url, error.response, error );
            }
            else {
                throw new MaprayApiError( -1, "Failed to fetch", url, null, error );
            }
        }
        return response;
    }
}



MaprayApi.DEFAULT_BASE_PATH = "https://cloud.mapray.com";



export default MaprayApi;