Scanbestanden downloaden
Deze gids legt uit hoe u verwerkte scanuitvoer downloadt van de RealityConnect API: puntenwolken, meshes en photosphere-afbeeldingen. De workflow gebruikt de Client Credentials-flow en de download:scan-scope.
Vereisten
Section titled “Vereisten”- Een OAuth-applicatie geconfigureerd voor Client Credentials
- Scopes:
read:basic,read:hierarchy,download:scan - Uw
client_idenclient_secret - Inhoudstoegang tot de site die de bundles bevat die u wilt downloaden
read:twin en read:asset zijn niet vereist voor het downloaden van scanbestanden.
Hoe scangegevens zijn georganiseerd
Section titled “Hoe scangegevens zijn georganiseerd”Verwerkte scangegevens bevinden zich in bundles die aan een site zijn gekoppeld. Elke bundle bevat een of meer componenten, waarbij elke component een ander uitvoerformaat is:
| Componenttype | Formaat | Beschrijving |
|---|---|---|
OgcPointCloudHlod | OGC 3D Tiles (open standaard) | Puntenwolkgegevens. tileset.json-toegangspunt met .glb-tegelbestanden |
RealityMeshHlod | Prevu3D HLOD | Meshgegevens. hlod_tree.json-toegangspunt met .pvt-tegelbestanden |
RealityPhotosphere | JPEG-afbeeldingen | Photosphere-gegevens. stations.json-manifest met .jpeg-afbeeldingen |
End-to-end-flow
Section titled “End-to-end-flow”flowchart LR A["1. Authenticeren"] --> B["2. Een site vinden"] B --> C["3. Bundles opsommen"] C --> D["4. Bundledetails ophalen"] D --> E["5. Toegangsbestanden downloaden"] E --> F["6. Bestandsverwijzingen volgen"]
| Stap | Endpoint | Wat u krijgt |
|---|---|---|
| Authenticeren | POST /oauth/token | Access token |
| API-URL oplossen | GET /oauth/api-info | Regionale apiUrl en organisatie-ID |
| Site vinden | GET /v1/nodes/{id}/browse | Site-ID uit de inhoudshiërarchie |
| Bundles opsommen | GET /v1/site/{siteId}/bundles | Bundle-ID’s voor de site |
| Componenten ophalen | GET /v1/site/{siteId}/bundles/{bundleId} | signedLink en roots per component |
| Bestanden downloaden | GET op de samengestelde CDN-URL | Toegangsbestanden, tegels en afbeeldingen |
Stap 1 — Authenticeren
Section titled “Stap 1 — Authenticeren”Verkrijg een access token met Client Credentials:
POST https://cloud-api.prevu3d.com/oauth/tokenAuthorization: Basic base64(client_id:client_secret)Content-Type: application/x-www-form-urlencoded
grant_type=client_credentialsLos uw regionale API-URL op:
GET https://cloud-api.prevu3d.com/oauth/api-infoAuthorization: Bearer {access_token}De response bevat apiUrl (bijvoorbeeld https://api-ue1.prevu3d.com/realityconnect-api). Gebruik dit als de basis-URL voor alle volgende API-aanroepen.
Stap 2 — Een site vinden
Section titled “Stap 2 — Een site vinden”Blader door de node-hiërarchie, beginnend bij uw organisatie:
GET {api_url}/v1/nodes/{organizationId}/browseAuthorization: Bearer {access_token}Doorloop de boom recursief totdat u een node met type: "Site" vindt. Noteer de id.
Stap 3 — Bundles opsommen
Section titled “Stap 3 — Bundles opsommen”GET {api_url}/v1/site/{siteId}/bundlesAuthorization: Bearer {access_token}Retourneert een gepagineerde lijst met bundles voor die site.
Stap 4 — Bundledetails ophalen
Section titled “Stap 4 — Bundledetails ophalen”GET {api_url}/v1/site/{siteId}/bundles/{bundleId}Authorization: Bearer {access_token}Voorbeeldresponse:
{ "id": "f75de673-...", "name": "My Bundle", "components": [ { "id": "abc123", "type": "OgcPointCloudHlod", "signedLink": "https://cdn.prevu3d.com/.../OgcPointCloudHlod/*?Policy=...&Signature=...&Key-Pair-Id=...", "roots": [ "sessions/0f4a60cb09a8/tileset.json", "sessions/2a5ed597dc0f/tileset.json" ] }, { "id": "def456", "type": "RealityMeshHlod", "signedLink": "https://cdn.prevu3d.com/.../RealityMeshHlod/*?Policy=...&Signature=...&Key-Pair-Id=...", "roots": ["hlod_tree.json"] }, { "id": "ghi789", "type": "RealityPhotosphere", "signedLink": "https://cdn.prevu3d.com/.../RealityPhotosphere/*?Policy=...&Signature=...&Key-Pair-Id=...", "roots": ["stations.json"] } ]}Elke component stelt twee velden beschikbaar die u nodig hebt om te downloaden:
signedLink: een ondertekende CDN-URL die eindigt op/*. De*is een tijdelijke aanduiding. De handtekening verleent leestoegang tot alle bestanden onder dit prefix, niet slechts één bestand.roots: een array met relatieve paden naar het (de) toegangspuntbestand(en) voor deze component.
Stap 5 — Bestanden downloaden
Section titled “Stap 5 — Bestanden downloaden”Stel een downloadbare URL samen door de * in signedLink te vervangen door een bestandspad:
download_url = component["signedLink"].replace("*", root)response = requests.get(download_url)De queryparameters (Policy, Signature, Key-Pair-Id) blijven behouden en authenticeren het verzoek. Download bestandsbytes rechtstreeks van de CDN zonder Authorization-header op de GET.
Componentspecifieke details
Section titled “Componentspecifieke details”OgcPointCloudHlod (puntenwolken)
Section titled “OgcPointCloudHlod (puntenwolken)”Toegangspunt: elke waarde in roots is een pad naar een tileset.json-bestand (één per scansessie).
for root in component["roots"]: url = component["signedLink"].replace("*", root) tileset = requests.get(url).json()Tegeltraversal: tileset.json volgt de OGC 3D Tiles-specificatie. Het bevat een boom van tegelnodes. Elke node kan een content-veld hebben met een uri die verwijst naar een .glb-bestand ten opzichte van de tileset-directory:
{ "asset": { "version": "1.0" }, "root": { "boundingVolume": { }, "content": { "uri": "cell.glb" }, "children": [ { "content": { "uri": "cell0.glb" }, "children": [ ] } ] }}Los tegelpaden op ten opzichte van de tileset-directory:
session_dir = root.rsplit("/tileset.json", 1)[0]tile_path = f"{session_dir}/{tile_uri}"tile_url = component["signedLink"].replace("*", tile_path)tile_data = requests.get(tile_url).contentDoorloop de boom recursief om alle tegels te downloaden.
RealityMeshHlod (meshes)
Section titled “RealityMeshHlod (meshes)”Toegangspunt: roots bevat ["hlod_tree.json"].
url = component["signedLink"].replace("*", "hlod_tree.json")hlod_tree = requests.get(url).json()Boomstructuur: hlod_tree.json bevat een HLOD-boom:
{ "environment_offset": [ ], "version": 1, "root": { "center": [1.17, -1.22, 5.63], "size": [50.54, 46.61, 19.63], "model_path": "cell", "density": 6.73, "children": [ { "model_path": "cell0", "children": [ ] } ], "textures": [ { "name": "color", "min_mip": 5, "max_mip": 11, "path": "cell" }, { "name": "ao", "min_mip": 5, "max_mip": 11, "path": "cell_ao" } ] }}Naamgeving van tegelbestanden: geometrie- en textuurbestanden gebruiken het patroon {model_path}_{mip_level}.pvt:
model_path = node["model_path"]mip_level = texture["min_mip"]tile_file = f"{model_path}_{mip_level}.pvt"
tile_url = component["signedLink"].replace("*", tile_file)tile_data = requests.get(tile_url).contentDoorloop de children-array recursief om alle model_path-waarden op elk detailniveau te ontdekken.
RealityPhotosphere (photospheres)
Section titled “RealityPhotosphere (photospheres)”Toegangspunt: roots bevat ["stations.json"].
url = component["signedLink"].replace("*", "stations.json")stations = requests.get(url).json()Aanvullende bestanden:
index.tsv: een tab-gescheiden indexbestand naaststations.json- Photosphere-afbeeldingen volgen het patroon
station_{n}/H_{face}_{x}_{y}.jpeg
image_path = "station_1/H_0_0_0.jpeg"image_url = component["signedLink"].replace("*", image_path)image_data = requests.get(image_url).contentParseer stations.json om alle stationdirectory’s en de bijbehorende afbeeldingsbestanden te ontdekken.
Volledig voorbeeld
Section titled “Volledig voorbeeld”Dit script authenticeert, vindt een site, haalt de eerste bundle op, downloadt toegangspuntbestanden en volgt één verwezen tegel of afbeelding per component.
import base64import jsonimport osfrom pathlib import Path
import requests
CLIENT_ID = "your-client-id"CLIENT_SECRET = "your-client-secret"CLOUD_API_BASE = "https://cloud-api.prevu3d.com"OUTPUT_DIR = Path("downloads")
credentials = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()token_response = requests.post( f"{CLOUD_API_BASE}/oauth/token", data={"grant_type": "client_credentials"}, headers={"Authorization": f"Basic {credentials}"},)token_response.raise_for_status()access_token = token_response.json()["access_token"]headers = {"Authorization": f"Bearer {access_token}"}
api_info = requests.get(f"{CLOUD_API_BASE}/oauth/api-info", headers=headers).json()api_url = api_info["apiUrl"].rstrip("/")organization_id = api_info["organization"]["id"]
def api_get(path: str) -> dict: response = requests.get(f"{api_url}{path}", headers=headers) response.raise_for_status() return response.json()
def download(url: str, dest: Path) -> bool: response = requests.get(url, timeout=30) if not response.ok: return False dest.parent.mkdir(parents=True, exist_ok=True) dest.write_bytes(response.content) return True
def find_tile_uri(node: dict, depth: int = 0): if depth > 5 or not isinstance(node, dict): return None content = node.get("content", {}) uri = content.get("uri") or content.get("url") if isinstance(uri, str) and not uri.startswith("http"): return uri for value in node.values(): if isinstance(value, dict): found = find_tile_uri(value, depth + 1) if found: return found elif isinstance(value, list): for item in value: found = find_tile_uri(item, depth + 1) if found: return found return None
queue = api_get(f"/v1/nodes/{organization_id}/browse").get("items", [])site_id = Nonevisited = set()
while queue and not site_id: node = queue.pop(0) if node["id"] in visited: continue visited.add(node["id"]) browse = api_get(f"/v1/nodes/{node['id']}/browse") current = browse.get("node", {}) if current.get("type", "").lower() == "site": site_id = current["id"] break queue.extend(browse.get("items", []))
if not site_id: raise RuntimeError("No site found in hierarchy")
bundles = api_get(f"/v1/site/{site_id}/bundles")bundle_id = bundles["items"][0]["id"]bundle = api_get(f"/v1/site/{site_id}/bundles/{bundle_id}")
for component in bundle.get("components", []): comp_type = component.get("type", "unknown") signed_link = component.get("signedLink", "") roots = component.get("roots", [])
if not signed_link or not roots: continue
for root in roots: entry_url = signed_link.replace("*", root) entry_dest = OUTPUT_DIR / comp_type / root.replace("/", os.sep) if not download(entry_url, entry_dest): continue
if root.endswith("tileset.json"): tileset = json.loads(entry_dest.read_bytes()) tile_uri = find_tile_uri(tileset.get("root", tileset)) if tile_uri: session_dir = root.rsplit("/tileset.json", 1)[0] tile_path = f"{session_dir}/{tile_uri}" if session_dir else tile_uri download(signed_link.replace("*", tile_path), OUTPUT_DIR / comp_type / tile_path.replace("/", os.sep))
elif root == "hlod_tree.json": tree = json.loads(entry_dest.read_bytes()) model_path = tree.get("root", {}).get("model_path", "") if model_path: tile_file = f"{model_path}_5.pvt" download(signed_link.replace("*", tile_file), OUTPUT_DIR / comp_type / tile_file)
elif root == "stations.json": download(signed_link.replace("*", "index.tsv"), OUTPUT_DIR / comp_type / "index.tsv") download(signed_link.replace("*", "station_1/H_0_0_0.jpeg"), OUTPUT_DIR / comp_type / "station_1" / "H_0_0_0.jpeg")
breakWat is de volgende stap?
Section titled “Wat is de volgende stap?”- Stel verificatie end-to-end in via de gids Client Credentials-flow.
- Upload en verwerk nieuwe opnames met Data Bundle-workflows.
- Blader door elke bewerking in de API-referentie.