# Pano-to-VR

複数パノラマをまとめ、海外向け Pano-to-VR タスクを送信して閲覧用 VR URL を取得します。

Canonical: https://developer.realsee.ai/ja/docs/pano-to-vr
Source: https://developer.realsee.ai/ja/docs/pano-to-vr.md
Language: ja

# Pano-to-VR

Argus を利用しています。

複数パノラマの ZIP を海外向け Pano-to-VR API にアップロードし、Realsee プロジェクトと閲覧 URL を作成します。

結果には SaaS VR の `project_id` と `vr_url` が含まれます。

<span id="before-you-start" class="docs-anchor-alias" aria-hidden="true"></span>

## 開始前の準備

- [アプリ一覧](https://developer.realsee.ai/ja/dashboard/apps) で Team 所有の Developer app を作成または選択します。
- AK、SK、ゲートウェイトークン、タスク制御はバックエンドに置き、一時アップロード認証情報は認証済みのアップロード元クライアントだけに渡します。
- 正距円筒の JPG パノラマと、以下の ZIP マニフェストを準備します。
- アップローダーと COS アダプターの依存関係をインストールします：

```bash
npm install @realsee/universal-uploader@0.1.1 cos-js-sdk-v5@1.8.3
```

全リクエストで海外本番ゲートウェイを使います：

```text
https://app-gateway.realsee.ai
```

<span id="zip-package-format" class="docs-anchor-alias" aria-hidden="true"></span>

## ZIP パッケージの形式

直下に `manifest.json`、`images/` にパノラマを置いて ZIP にします：

```text
my-project.zip
├── manifest.json
└── images/
    ├── entrance.jpg
    ├── entrance-02.jpg
    ├── end-of-nave.jpg
    ├── presbyterium.jpg
    └── high-altar.jpg
```

`manifest.json` の例：

```json
{
  "version": "1.0",
  "floor_map": {
    "0": 1,
    "1": 2
  },
  "project_name": "my-pano-project",
  "scan_list": [
    { "id": "entrance", "floor": 0 },
    { "id": "entrance-02", "floor": 0 },
    { "id": "end-of-nave", "floor": 0 },
    { "id": "presbyterium", "floor": 1 },
    { "id": "high-altar", "floor": 1 }
  ]
}
```

| 項目 | 型 | 説明 |
| --- | --- | --- |
| `version` | string | パッケージ版。このフローでは `"1.0"`。 |
| `floor_map` | object | scan_list の floor キーと表示階数の対応。 |
| `project_name` | string | パッケージ内の名前。送信時も同じ安定した名前を使う。 |
| `scan_list` | array | 順序付きパノラマ一覧。 |
| `scan_list[].id` | string | 対応する JPG の拡張子を除いた名前と一致する。 |
| `scan_list[].floor` | number | floor_map 内の階キー。 |

`images/` の全ファイルに対応する scan_list 項目が必要です。単一階なら `{ "0": 0 }` のような対応で十分です。

<span id="workflow" class="docs-anchor-alias" aria-hidden="true"></span>

## 処理の流れ

```text
Get access_token → get upload credentials → upload ZIP → submit task → poll task_code → read vr_url
```

<span id="get-an-access-token" class="docs-anchor-alias" aria-hidden="true"></span>

### アクセストークンを取得する

`POST /auth/access_token` に `application/x-www-form-urlencoded` で送ります：

```text
app_key=<app_key>&app_secret=<app_secret>
```

成功時は `data.access_token` と `data.expire_at` を返します。後続の Authorization は Bearer なしの生のトークンです。

<span id="get-upload-credentials" class="docs-anchor-alias" aria-hidden="true"></span>

### アップロード認証情報を取得する

トークンで `GET /open/v1/pano/file/token` を呼びます：

```http
GET /open/v1/pano/file/token
Authorization: <raw_gateway_access_token>
```

`data` 直下に `UploadToken` の項目が返ります。

主要な応答項目：

| 項目 | 用途 |
| --- | --- |
| `tmpSecretId`, `tmpSecretKey`, `sessionToken` | 一時ストレージ認証情報。ログ記録禁止。 |
| `bucket`, `region`, `host` | アップロード先。 |
| `prefix` | アップロード ZIP キーの先頭に付けるプレフィックス。 |
| `ttl`, `expire` | 秒単位の有効期間。ここの expire は TTL で、認証の Unix 時刻 expire_at とは異なる。 |
| `download_type`, `download_host` | 送信時に使うファイル位置の項目を決定。 |
| `custom_domain`, `custom_scheme` | アダプター用のプロバイダー接続先。 |

`code: -3` の場合はアクセストークンを更新してから再要求します。

<span id="upload-the-zip-from-your-client" class="docs-anchor-alias" aria-hidden="true"></span>

### クライアントから ZIP をアップロードする

ブラウザーでは `File` と COS アダプターを使い、トークン応答の完全な data を UploadToken として渡します：

```ts
import {
  Uploader,
  type ProviderAdaptor,
  type UploadToken,
} from '@realsee/universal-uploader'

const cosAdaptor: ProviderAdaptor = () => import('@realsee/universal-uploader/adaptors/cos')

export async function uploadPanoramaZip(zipFile: File, token: UploadToken) {
  const uploader = new Uploader(cosAdaptor, {
    getToken: async () => token,
  })
  const uploadKey = 'my-project.zip'

  await uploader.upload(uploadKey, zipFile, {
    parallel: Uploader.defaultUploadHandler.parallel,
    partSize: Uploader.defaultUploadHandler.partSize,
    retry: Uploader.defaultUploadHandler.retry,
  })

  return `${token.prefix}${uploadKey}`
}
```

返された文字列は `private_cos_key` 用の完全パスです。download_host が空でなく、アダプターが download_url を返す場合は zip_cos_url 用に保持します。

<span id="submit-the-processing-task" class="docs-anchor-alias" aria-hidden="true"></span>

### 処理タスクを送信する

`POST /open/v1/pano/task/submit` に project_name と 1 つの ZIP 位置を渡します：

- download_host が空なら、非公開バケットの presign フローを表す `private_cos_key` を使います。
- ダウンロード URL があれば `zip_cos_url` を使います。

非公開バケットの例：

```json
{
  "project_name": "my-pano-project",
  "private_cos_key": "vrfile/release/open_task_original/T-XXXXXXXXXX/pano/1700000000/my-project.zip"
}
```

成功時の data は両方の識別子を含みます：

```ts
type PanoTaskSubmission = {
  project_id: string
  task_code: string
}
```

`task_code` を状態照会用に保持します。

<span id="poll-for-completion" class="docs-anchor-alias" aria-hidden="true"></span>

### 完了までポーリングする

返されたコードで `GET /open/v1/pano/task/status` を呼びます：

```http
GET /open/v1/pano/task/status?task_code=<task_code>
Authorization: <raw_gateway_access_token>
```

応答データのスキーマ：

```ts
type PanoTaskStatus = {
  project_id: string
  status: 'pending' | 'processing' | 'success' | 'failed'
  vr_url: string
}
```

`pending`・`processing` は照会を続けます。success では vr_url に完全な閲覧 URL が入り、failed では停止して再試行や相談の手段を示します。

長いタスク中にトークンが切れる場合があります。`code: -3` と `status: "access_token expired"` なら更新し、同じ task_code で継続します。

<span id="response-envelope" class="docs-anchor-alias" aria-hidden="true"></span>

## 応答の形式

Pano-to-VR はゲートウェイの応答形式を使います：

| 項目 | 説明 |
| --- | --- |
| `request_id`, `trace_id` | サーバー診断用の識別子。 |
| `business_code` | 文字列の業務結果・エラーコード。 |
| `code` | 成功 0、業務失敗 -1、トークン期限切れ -3。 |
| `status` | 成功・エラーの説明。 |
| `data` | API 固有データ。失敗時は null の場合あり。 |
| `cost` | ミリ秒単位のサーバー処理時間。 |

SK、トークン、一時ストレージ認証情報、オブジェクトパス、未加工診断をブラウザーのログや解析に公開しないでください。

