处理与监控
输入文件上传后,平台可以生成处理后的输出(标准化点云、网格 HLOD、全景图等)。本指南说明如何列出处理选项、启动处理,并轮询进度直到每个输出达到终态。
它假定你有一个至少包含一个已完成上传会话的 Bundle,并持有 read:bundle 和 write:bundle 作用域。
处理是即发即忘的:API 将处理作业入队后立即返回。
sequenceDiagram
participant Client as Client
participant RCAPI as RCAPI
Client->>RCAPI: GET processing/options
RCAPI-->>Client: options + metadata
Client->>RCAPI: POST processing
RCAPI-->>Client: accepted or typed error
loop until all terminal
Client->>RCAPI: GET bundle/processings
RCAPI-->>Client: processing records (status, progress)
end
| 方法 | 路径 | 作用域 | 用途 |
|---|---|---|---|
GET | /v1/bundles/{bundleId}/processing/options | read:bundle | 列出可能的输出类型及其元数据 |
POST | /v1/bundles/{bundleId}/processing | write:bundle | 为请求的输出启动处理 |
GET | /v1/bundles/{bundleId}/processings | read:bundle | 列出 Bundle 的处理记录以轮询进度 |
GET | /v1/site/{siteId}/bundles/{bundleId} | read:hierarchy | 获取 Bundle 及其处理后的输出组件(签名链接) |
步骤 1:列出处理选项
Section titled “步骤 1:列出处理选项”在启动之前,发现 Bundle 可以生成哪些输出以及每个输出的成本。
GET {api_url}/v1/bundles/{bundleId}/processing/options?page=1&limit=20&sortBy=type&sortDir=ASCAuthorization: Bearer {access_token}| 参数 | 类型 | 说明 |
|---|---|---|
page | number | 从 1 开始的页码索引(默认 1) |
limit | number | 页面大小(默认 20,最大 20) |
sortBy | string | type 或 cost(默认 type) |
sortDir | string | ASC 或 DESC(默认 ASC) |
响应是一个分页列表({ items, total })。每个选项描述一种输出类型:
| 字段 | 含义 |
|---|---|
type | 启动处理时要传入的输出组件类型(例如 StandardizedPointCloud、OgcPointCloudHlod) |
hasProcessing | 当此输出的处理已被启动时为 true |
cost | 付费输出的处理成本(以字节为单位);免费层输出为 0 |
示例:
{ "items": [ { "type": "StandardizedPointCloud", "hasProcessing": false, "cost": 734003200 }, { "type": "OgcPointCloudHlod", "hasProcessing": false, "cost": 734003200 } ], "total": 2}跳过 hasProcessing 为 true 的输出,除非你打算重新处理。
步骤 2:启动处理
Section titled “步骤 2:启动处理”传入你希望流水线生成的输出组件类型列表,以及一个确认处理总费用的 cost 字段。服务器会对 requestedComponents 中每个类型的步骤 1 cost 值求和,当你的确认值不匹配时拒绝该请求。
一旦成本被接受,处理便异步开始。
POST {api_url}/v1/bundles/{bundleId}/processingAuthorization: Bearer {access_token}Content-Type: application/json
{ "requestedComponents": [ "StandardizedPointCloud", "OgcPointCloudHlod" ], "cost": 1468006400}在上面的示例中,cost 是步骤 1 选项列表中的 734003200 + 734003200。
204 No Content:处理已被接受并入队。
当无法启动处理时,API 返回 409 Conflict 及一个类型化的 error:
error 值 | 含义 |
|---|---|
ProcessingCostMismatch | 提交的 cost 与所请求输出的计算总额不匹配 |
InsufficientProcessingCapacity | 将超出组织处理限额 |
NoInputDataFoundForProcessing | Bundle 上没有可用的已完成输入文件 |
FailedToLaunchProcessing | 流水线提交因意外原因失败 |
示例:
{ "error": "ProcessingCostMismatch"}已上传批次的结构验证(强制类型、扩展名、依赖)由处理流水线在启动后强制执行。如果你的上传会话已完成但违反了 requirements 契约,即使每个文件都成功上传且启动调用返回 204,处理仍可能在稍后失败。
步骤 3:轮询进度
Section titled “步骤 3:轮询进度”成功启动后,轮询 Bundle 的处理记录,直到每一条都达到终态。
GET {api_url}/v1/bundles/{bundleId}/processingsAuthorization: Bearer {access_token}响应是一个列表({ items, total }),包含 Bundle 的每一条处理记录。没有查询参数;所有记录在单次调用中返回。
每个条目跟踪一个处理作业:
| 字段 | 含义 |
|---|---|
id | 处理记录的标识符 |
status | 流水线状态(见下表) |
progress | 活动期间的完成百分比(0–100) |
createdAt | 作业创建的时间 |
updatedAt | 作业上次更改状态的时间 |
launchedById | 启动作业的用户 id,或 null |
knownErrors | 结构化失败,每项带有 errorCode 和受影响的 errorFilepaths(非文件特定时为 null) |
| 状态 | 是否终态? | 含义 |
|---|---|---|
Pending | 否 | 已排队,尚未开始 |
Processing | 否 | 流水线正在运行 |
Restarted | 否 | 作业已重启并再次运行 |
Ready | 是 | 输出已成功生成 |
Failed | 是 | 非结构化的流水线失败 |
FailedToLaunch | 是 | 作业从未进入流水线 |
KnownError | 是 | 结构化验证或格式错误 |
轮询直到每一条记录都处于终态(Ready、Failed、FailedToLaunch 或 KnownError)。
进行中的示例响应:
{ "items": [ { "id": "3f2b1c4d-8e7a-4b2c-9d1e-0a1b3c4d4e6f", "status": "Processing", "progress": 42, "createdAt": "2026-06-01T14:05:00Z", "updatedAt": "2026-06-01T14:12:00Z", "launchedById": "b1a2c3d4-5e6f-7a8b-9c0d-1e2f3a2b2c1d", "knownErrors": [] }, { "id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b2c3d3e4f", "status": "Pending", "progress": 0, "createdAt": "2026-06-01T14:05:00Z", "updatedAt": "2026-06-01T14:05:00Z", "launchedById": "b1a2c3d4-5e6f-7a8b-9c0d-1e2f3a3b1c1d", "knownErrors": [] } ], "total": 2}带有已知错误的示例:
{ "items": [ { "id": "3f2b1c4d-8e7a-4b2c-9d1e-0a2b3c4d4e5f", "status": "KnownError", "progress": 100, "createdAt": "2026-06-01T14:05:00Z", "updatedAt": "2026-06-01T14:48:00Z", "launchedById": "b1a2c3d4-5e6f-7a8b-1c0d-2e6f3a4b5c6d", "knownErrors": [ { "errorCode": "MissingTrajectory", "errorFilepaths": ["scan.las"] } ] } ], "total": 1}处理列表不包含每条记录的输出 type。要将完成的输出映射回其组件类型和签名链接,请获取 Bundle 的组件(参见检索处理后的输出)。
- 间隔: 1-5 分钟是合理的默认值。处理可能运行数分钟到数小时,具体取决于输入大小。
- 速率限制: Bundle 端点与多个其他端点共享一个速率限制桶。避免以低于一分钟的间隔轮询。
检索处理后的输出
Section titled “检索处理后的输出”一旦某条处理记录达到 Ready,获取 Bundle 以得到其输出组件及其签名下载链接。Bundle 详情位于层级端点上:
GET {api_url}/v1/site/{siteId}/bundles/{bundleId}Authorization: Bearer {access_token}此端点需要 read:hierarchy 作用域,并返回带有其 components 数组的 Bundle:
| 字段 | 含义 |
|---|---|
id | Bundle id |
name | Bundle 名称 |
components | 每个可用组件(输入和处理后的输出)一个条目 |
每个组件携带:
| 字段 | 含义 |
|---|---|
id | 组件 id |
type | 组件类型,例如 StandardizedPointCloud、OgcPointCloudHlod |
version | 组件版本 |
signedLink | 用于下载组件的签名 URL;无需 auth 标头 |
roots | 组件内的入口点路径(适用时) |
示例:
{ "id": "a6f75b3c-f261-4b27-9a2a-9a6cc4178a13", "name": "ZF capture 2026-06-01", "components": [ { "id": "5d6e7f8a-9b0c-1d2e-3f4a-1b3c7d4e8f0a", "type": "StandardizedPointCloud", "version": "1", "signedLink": "https://cdn…/components/…?X-Amz-…" } ]}RealityPlan project 通过 GET /v1/reality-plan/{projectId}/bundles/{bundleId}(作用域 read:twin)公开同一个 Bundle。
import timeimport requests
API_URL = "https://<your-regional-api-url>"TOKEN = "<access_token>"BUNDLE_ID = "<bundleId>"
headers = {"Authorization": f"Bearer {TOKEN}"}base = f"{API_URL}/v1/bundles/{BUNDLE_ID}"
# 1. Discover outputs.options = requests.get( f"{base}/processing/options", headers=headers, params={"page": 1, "limit": 20},).json()
options_by_type = {item["type"]: item for item in options["items"]}to_process = [ item["type"] for item in options["items"] if not item["hasProcessing"]]total_cost = sum(options_by_type[t]["cost"] for t in to_process)print(f"Launching: {to_process} (cost: {total_cost})")
# 2. Start processing.response = requests.post( f"{base}/processing", headers=headers, json={"requestedComponents": to_process, "cost": total_cost},)
if response.status_code == 409: raise RuntimeError( f"Processing failed to start: {response.json().get('error')}" )response.raise_for_status() # expect 204
# 3. Poll until every job is terminal.TERMINAL = {"Ready", "Failed", "FailedToLaunch", "KnownError"}POLL_SECONDS = 15TIMEOUT_SECONDS = 3600deadline = time.time() + TIMEOUT_SECONDS
while time.time() < deadline: listing = requests.get( f"{base}/processings", headers=headers, ).json()
processings = listing.get("items", []) for p in processings: print(f" {p['id']}: {p['status']} ({p['progress']}%)")
if processings and all(p["status"] in TERMINAL for p in processings): break
time.sleep(POLL_SECONDS)else: raise TimeoutError("Processing did not finish within the timeout")要检索已上传的原始输入文件(用于存档、重新处理或审计),请参阅下载输入文件。