Daten in ein Data Bundle hochladen
Dieser Leitfaden erklärt, wie Sie die Roh-Eingabedateien einer Erfassung in ein Data Bundle über die RealityConnect API hochladen.
Alle Upload-Endpunkte erfordern den Scope write:bundle; zum Auflisten von Sessions ist read:bundle erforderlich.
Upload-Ablauf
Abschnitt betitelt „Upload-Ablauf“Ein Data Bundle enthält die Roh-Eingaben für eine einzelne Erfassung: eine Punktwolke, eine Trajektorie, Bilder, Metadaten pro Bild und so weiter. Die genaue Menge an Dateien, die ein Bundle akzeptiert, hängt vom Gerät ab, für das das Bundle erstellt wurde.
Dateien werden in Upload-Sessions hochgeladen. Eine Session ist ein serverseitig erstellter Container für einen Stapel von Dateien eines Bundles. Beim Öffnen geben Sie an, wie viele Dateien der Stapel enthalten wird (expectedFileCount); die Session wird automatisch abgeschlossen, sobald so viele Dateien finalisiert wurden.
Jede einzelne Datei wird mit einem S3-Multipart-Upload hochgeladen:
- Session öffnen auf dem Bundle und die Gesamtzahl der Dateien angeben, die Sie senden werden.
- Für jede Datei: Upload initiieren, Teile direkt an die zurückgegebenen URLs PUTen, dann finalisieren.
- Wenn die Anzahl finalisierter Dateien
expectedFileCounterreicht, wird die Session alscompletedmarkiert.
sequenceDiagram
participant Client as Client
participant RCAPI as RCAPI
participant S3 as S3
Client->>RCAPI: POST upload-sessions
RCAPI-->>Client: sessionId + requirements
loop for each file
Client->>RCAPI: POST initiate file
RCAPI-->>Client: fileId + presigned part URLs
loop for each part
Client->>S3: PUT part
S3-->>Client: ETag
end
Client->>RCAPI: POST finalize
RCAPI-->>Client: file completed + sessionStatus
end
Die Upload-requirements (beim Öffnen einer Session zurückgegeben) geben genau an, welche Dateitypen das Bundle akzeptiert, welche Pflicht sind und wie sie voneinander abhängen.
Das ausgearbeitete Beispiel: eine ZF-SLAM-Erfassung
Abschnitt betitelt „Das ausgearbeitete Beispiel: eine ZF-SLAM-Erfassung“Ein ZF-SLAM-Bundle ist ein gutes Beispiel, weil es vier verschiedene Dateitypen mit einer typübergreifenden Abhängigkeit akzeptiert. Eine vollständige ZF-Erfassung besteht aus:
| File | Type | Notes |
|---|---|---|
scan.las | point cloud | genau eine |
…laser_0.txt | trajectory | genau eine |
panorama/*.jpg | panorama images | viele, jeweils mit Metadaten gepaart |
panorama/*.json | per-image metadata | viele, eine pro Bild |
Ein vollständiger ZF-Stapel ist also 1 Punktwolke + 1 Trajektorie + N Bilder + N Metadatendateien. Wir laden alle in einer einzigen Session hoch.
Endpunkte
Abschnitt betitelt „Endpunkte“Alle Pfade sind relativ zu Ihrer regionalen api_url. Das Bundle wird über seine eigene ID (bundleId) angesprochen.
| Method | Path | Scope | Purpose |
|---|---|---|---|
POST | /v1/bundles/{bundleId}/upload-sessions | write:bundle | Session öffnen |
GET | /v1/bundles/{bundleId}/upload-sessions | read:bundle | Sessions auflisten (Fortsetzen / Fortschritt) |
POST | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files | write:bundle | Datei-Upload initiieren |
GET | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/refresh/{startIndex} | write:bundle | Abgelaufene Teil-URLs erneut ausstellen |
POST | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/finalize | write:bundle | Datei-Upload finalisieren |
Bevor Sie beginnen
Abschnitt betitelt „Bevor Sie beginnen“Um Daten in ein Data Bundle hochzuladen, benötigen Sie:
- einen abgeschlossenen OAuth-Flow mit
access_tokenund Ihrer regionalenapi_url - ein vorhandenes Data Bundle (
bundleId)
Wenn Sie noch kein Bundle haben, erstellen Sie zuerst eines, indem Sie Data Bundle erstellen folgen. Dieser Ablauf ruft den Geräte-Input-Baum ab, wählt einen Blatt-dbuPath und erstellt das Bundle unter einem übergeordneten Knoten.
Schritt 1: Upload-Session öffnen
Abschnitt betitelt „Schritt 1: Upload-Session öffnen“Geben Sie die Gesamtzahl der Dateien an, die der Stapel enthalten wird. Für eine vollständige ZF-Erfassung mit 13 Bildern ist das 1 + 1 + 13 + 13 = 28.
POST {api_url}/v1/bundles/{bundleId}/upload-sessionsAuthorization: Bearer {access_token}Content-Type: application/json
{ "expectedFileCount": 28 }Die Antwort liefert die sessionId und die requirements des Bundles:
{ "sessionId": "6ebf0fe5-4428-4b26-a7e4-e3274980123b", "expectedFileCount": 28, "requirements": [ { "type": "pointclouds", "allowedExtensions": ["las", "laz"], "optional": false, "allowMultiples": false, "dependencies": [] }, { "type": "trajectories", "allowedExtensions": ["txt"], "optional": false, "allowMultiples": false, "dependencies": [] }, { "type": "pictures", "allowedExtensions": ["jpg", "png", "jpeg"], "optional": true, "allowMultiples": true, "dependencies": ["picturesMetadata"] }, { "type": "picturesMetadata", "allowedExtensions": ["json"], "optional": true, "allowMultiples": true, "dependencies": ["pictures"] } ]}requirements und typübergreifende Abhängigkeiten verstehen
Abschnitt betitelt „requirements und typübergreifende Abhängigkeiten verstehen“Jeder Eintrag in requirements beschreibt einen Dateityp, den das Bundle akzeptiert:
| Field | Meaning |
|---|---|
type | Die Kennung, die Sie als type beim Initiieren eines Datei-Uploads übergeben. |
allowedExtensions | Für diesen Typ akzeptierte Dateierweiterungen. |
optional | Wenn false, kann das Bundle ohne mindestens eine Datei dieses Typs nicht verarbeitet werden. |
allowMultiples | Wenn false, darf nur eine Datei dieses Typs hochgeladen werden. |
dependencies | Andere Typen, die ebenfalls vorhanden sein müssen, wann immer dieser Typ vorhanden ist. |
Für das ZF-Beispiel:
pointcloudsundtrajectoriessind Pflicht (optional: false) und einzeln (allowMultiples: false). Jedes ZF-Bundle benötigt genau je eine.picturesundpicturesMetadatasind optional und akzeptieren mehrere Dateien.- Die beiden Bildtypen hängen voneinander ab:
pictures.dependencies = ["picturesMetadata"]undpicturesMetadata.dependencies = ["pictures"].
Eine Abhängigkeit bedeutet: Wenn ein Typ vorhanden ist, muss jeder Typ, den er auflistet, ebenfalls vorhanden sein. Da die ZF-Bildtypen aufeinander verweisen, gelten sie als Paar alles oder nichts. Sie können die Bilder und ihre Metadaten zusammen hochladen oder beides weglassen, aber Sie können nicht eines ohne das andere hochladen.
Gültige ZF-Stapel:
- Minimal:
1 Punktwolke + 1 Trajektorie(keine Bilder).expectedFileCount = 2. - Vollständig:
1 Punktwolke + 1 Trajektorie + N pictures + N picturesMetadata.expectedFileCount = 2 + 2N.
Ungültig:
- Bilder ohne ihre Metadaten (oder umgekehrt), da die typübergreifende Abhängigkeit nicht erfüllt ist.
- Ein Stapel ohne Punktwolke oder ohne Trajektorie, da ein Pflichttyp fehlt.
Bevor Sie eine Session öffnen, zählen Sie jede Datei, die Sie hochladen möchten, und setzen Sie diese Gesamtzahl als expectedFileCount. Optionale Typen zählen nur, wenn Sie sie in den Stapel aufnehmen. Die Session wird abgeschlossen, wenn so viele Dateien finalisiert sind; die angegebene Zahl muss also dem übereinstimmen, was Sie tatsächlich hochladen.
Schritt 2: Jede Datei hochladen
Abschnitt betitelt „Schritt 2: Jede Datei hochladen“Wiederholen Sie die folgenden drei Aufrufe für jede Datei im Stapel.
2a. Initiieren
Abschnitt betitelt „2a. Initiieren“Übergeben Sie den Dateinamen, die Größe in Bytes und den type (einer der requirements-Typen):
POST {api_url}/v1/bundles/{bundleId}/upload-sessions/{sessionId}/filesAuthorization: Bearer {access_token}Content-Type: application/json
{ "fileName": "scan.las", "fileSize": 734003200, "type": "pointclouds" }Die Antwort beschreibt den Multipart-Upload: wie viele parts zu senden sind und eine presignierte url pro Teil.
{ "fileId": "c8c8cb0b-f476-4211-a3a4-72e4e04b91d5", "parts": 8, "urls": [ { "partNumber": 1, "url": "https://s3…/part-1?…" }, { "partNumber": 2, "url": "https://s3…/part-2?…" } ]}2b. Teile hochladen
Abschnitt betitelt „2b. Teile hochladen“Teilen Sie die Datei in parts sequenzielle Blöcke à ceil(fileSize / parts) Bytes und PUTen Sie jeden Block an seine presignierte url. Bei diesen Anfragen wird kein Auth-Header gesendet; die URL ist bereits signiert. Bewahren Sie den ETag-Antwortheader jedes Teils auf, Sie benötigen ihn zum Finalisieren.
import math, requests
def upload_parts(file_path, file_size, urls): part_size = math.ceil(file_size / len(urls)) parts = [] with open(file_path, "rb") as handle: for entry in sorted(urls, key=lambda u: u["partNumber"]): res = requests.put(entry["url"], data=handle.read(part_size)) res.raise_for_status() parts.append({"partNumber": entry["partNumber"], "etag": res.headers["ETag"]}) return partsGeben Sie den ETag genau so zurück, wie er geliefert wurde, einschließlich der umgebenden Anführungszeichen.
2c. Finalisieren
Abschnitt betitelt „2c. Finalisieren“Senden Sie die gesammelten Teile, um die Datei abzuschließen. Der Endpunkt liefert 200 OK.
POST {api_url}/v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/finalizeAuthorization: Bearer {access_token}Content-Type: application/json
{ "parts": [ { "partNumber": 1, "etag": "\"a1b2…\"" }, { "partNumber": 2, "etag": "\"c3d4…\"" } ] }Die Antwort gibt die Datei zurück und meldet den Session-Status:
{ "fileId": "c8c8cb0b-f476-4211-a3a4-72e4e04b91d5", "fileName": "scan.las", "type": "pointclouds", "size": 734003200, "status": "completed", "sessionStatus": "pending"}sessionStatus bleibt pending, bis die letzte erwartete Datei finalisiert ist; dann wird er completed.
Abgelaufene URLs erneuern
Abschnitt betitelt „Abgelaufene URLs erneuern“Presignierte URLs laufen ab. Wenn ein Upload lange dauert und ein PUT 403 zurückgibt, stellen Sie die URLs ab dem Teil neu aus, an dem Sie angehalten haben, und fahren Sie fort:
GET {api_url}/v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/refresh/0Authorization: Bearer {access_token}{ "startIndex": 0, "urls": [ { "partNumber": 1, "url": "https://s3…/part-1?…" } ] }Die Datei muss nicht erneut initiiert werden; nur die Teil-URLs werden erneuert.
Schritt 3: Fortschritt verfolgen und fortsetzen
Abschnitt betitelt „Schritt 3: Fortschritt verfolgen und fortsetzen“Listen Sie die Sessions eines Bundles auf, um den Fortschritt zu prüfen oder einen unterbrochenen Stapel fortzusetzen, ohne die sessionId im Speicher zu halten. Die Liste ist paginiert und sortierbar, mit optionalem status-Filter.
Query-Parameter: page (Standard 1), limit (Standard 20, Maximum 20), sortBy (createdAt oder status, Standard createdAt), sortDir (ASC oder DESC, Standard ASC), status (optionaler Filter: pending oder completed).
GET {api_url}/v1/bundles/{bundleId}/upload-sessions?page=1&limit=20&sortBy=createdAt&sortDir=DESCAuthorization: Bearer {access_token}{ "items": [ { "sessionId": "6ebf0fe5-4428-4b26-a7e4-e3274980123b", "status": "pending", "expectedFileCount": 28, "finalizedFileCount": 14 } ], "total": 1}finalizedFileCount gegenüber expectedFileCount zeigt, wie viele Dateien noch fehlen. Zum Fortsetzen initiieren und finalisieren Sie nur die Dateien, die noch nicht abgeschlossen sind.
Validierung und Limits
Abschnitt betitelt „Validierung und Limits“Lesen Sie dies sorgfältig, da es festlegt, wofür Sie verantwortlich sind, wenn Sie die RealityConnect API zum Hochladen von Daten nutzen.
- Die Upload-Endpunkte erfassen jede Datei unter dem von Ihnen angegebenen
typeund schließen die Session rein anhand der Anzahl ab: SobaldfinalizedFileCountexpectedFileCountentspricht, ist die Sessioncompleted. Zum Upload-Zeitpunkt prüft der Server nicht, ob dertypeeiner derrequirementsist, ob die Dateierweiterung erlaubt ist, ob Pflichttypen vorhanden sind oder ob typübergreifende Abhängigkeiten erfüllt sind. - Alle Regeln (Pflichttypen,
allowMultiplesund diedependencieszwischen Typen) werden erst durchgesetzt, nachdem das Bundle zur Verarbeitung gesendet wurde. Ein Stapel, der dierequirementsignoriert, wird erfolgreich hochgeladen, schlägt aber bei der Verarbeitung fehl. - Eine tiefe Inhaltsvalidierung erfolgt durch die Upload-API nie. Ob eine
.laseine echte ZF-Punktwolke ist oder eine.jsonkorrekt auf ihr Bild verweist, wird erst bei der Verarbeitung geprüft.
Behandeln Sie requirements als Vertrag. Halten Sie Typen, Erweiterungen, Optionalität, Mehrfachheit und Abhängigkeiten in Ihrem Upload-Stapel ein, damit das Bundle nach dem Upload verarbeitbar ist. Verarbeitungsfehler durch eine falsche Upload-Struktur sind nicht erstattungsfähig.
Vollständiges Beispiel
Abschnitt betitelt „Vollständiges Beispiel“Zusammengefasst für die ZF-Erfassung. files ist die Liste, die Sie aus Ihrem lokalen Datensatz aufbauen; jeder Eintrag verknüpft einen Pfad mit seinem type aus requirements.
import math, requests
API_URL = "https://<your-regional-api-url>"TOKEN = "<access_token>"BUNDLE_ID = "<bundleId>"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
# (local path, schema type) for every file in the batchfiles = [ ("scan.las", "pointclouds"), ("2024-07-scan.laser_laser_0.txt", "trajectories"), ("panorama/2024-07-scan-idx40.jpg", "pictures"), ("panorama/2024-07-scan-idx40.json", "picturesMetadata"), # … remaining image / metadata pairs …]
def upload_one(session_id, path, file_type): size = __import__("os").path.getsize(path) base = f"{API_URL}/v1/bundles/{BUNDLE_ID}/upload-sessions/{session_id}/files"
initiated = requests.post( base, headers=HEADERS, json={"fileName": path.split("/")[-1], "fileSize": size, "type": file_type}, ).json()
part_size = math.ceil(size / initiated["parts"]) parts = [] with open(path, "rb") as handle: for entry in sorted(initiated["urls"], key=lambda u: u["partNumber"]): res = requests.put(entry["url"], data=handle.read(part_size)) res.raise_for_status() parts.append({"partNumber": entry["partNumber"], "etag": res.headers["ETag"]})
return requests.post( f"{base}/{initiated['fileId']}/finalize", headers=HEADERS, json={"parts": parts}, ).json()
# 1. Open the session for the exact number of files we will send.session = requests.post( f"{API_URL}/v1/bundles/{BUNDLE_ID}/upload-sessions", headers=HEADERS, json={"expectedFileCount": len(files)},).json()
# 2. Upload every file under its type.result = Nonefor path, file_type in files: result = upload_one(session["sessionId"], path, file_type)
# 3. The last finalize reports the session as completed.assert result["sessionStatus"] == "completed"Nächster Schritt
Abschnitt betitelt „Nächster Schritt“Sobald jede Datei hochgeladen ist und die Session completed ist, fahren Sie mit Verarbeitung und Überwachung fort, um die Roh-Eingaben in anzeigbare Ausgaben umzuwandeln.