Gegevens uploaden naar een data bundle
Deze gids legt uit hoe u de ruwe invoerbestanden van een opname uploadt naar een data bundle via de RealityConnect API.
Alle upload-endpoints vereisen de write:bundle-scope; het opsommen van sessies vereist read:bundle.
Uploadflow
Section titled “Uploadflow”Een data bundle bevat de ruwe invoer voor één opname: een puntenwolk, een traject, afbeeldingen, metadata per afbeelding, enzovoort. De exacte set bestanden die een bundle accepteert, hangt af van het apparaat waarvoor de bundle is gemaakt.
Bestanden worden geüpload in uploadsessies. Een sessie is een door de server gemaakte container voor één batch van de bestanden van een bundle. Wanneer u deze opent, geeft u aan hoeveel bestanden de batch zal bevatten (expectedFileCount); de sessie wordt automatisch voltooid zodra dat aantal bestanden is voltooid.
Elk afzonderlijk bestand wordt geüpload met een S3-multipart-upload:
- Open een sessie op de bundle en geef het totale aantal bestanden aan dat u zult verzenden.
- Voor elk bestand: initieer de upload, PUT de delen ervan rechtstreeks naar de geretourneerde URL’s en voltooi het vervolgens.
- Wanneer het aantal voltooide bestanden
expectedFileCountbereikt, wordt de sessie gemarkeerd alscompleted.
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
De upload-requirements (geretourneerd wanneer u een sessie opent) vertellen u precies welke bestandstypen de bundle accepteert, welke verplicht zijn en hoe ze van elkaar afhankelijk zijn.
Het uitgewerkte voorbeeld: een ZF SLAM-opname
Section titled “Het uitgewerkte voorbeeld: een ZF SLAM-opname”Een ZF SLAM-bundle is een goed voorbeeld omdat deze vier verschillende bestandstypen accepteert met een cross-type-afhankelijkheid. Een volledige ZF-opname bestaat uit:
| Bestand | Type | Opmerkingen |
|---|---|---|
scan.las | puntenwolk | precies één |
…laser_0.txt | traject | precies één |
panorama/*.jpg | panorama-afbeeldingen | veel, elk gekoppeld aan metadata |
panorama/*.json | metadata per afbeelding | veel, één per afbeelding |
Een volledige ZF-batch is dus 1 puntenwolk + 1 traject + N afbeeldingen + N metadatabestanden. We uploaden ze allemaal in één sessie.
Endpoints
Section titled “Endpoints”Alle paden zijn relatief ten opzichte van uw regionale api_url. De bundle wordt geadresseerd via zijn eigen id (bundleId).
| Methode | Pad | Scope | Doel |
|---|---|---|---|
POST | /v1/bundles/{bundleId}/upload-sessions | write:bundle | Een sessie openen |
GET | /v1/bundles/{bundleId}/upload-sessions | read:bundle | Sessies opsommen (hervatten / voortgang) |
POST | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files | write:bundle | Een bestandsupload initiëren |
GET | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/refresh/{startIndex} | write:bundle | Verlopen deel-URL’s opnieuw uitgeven |
POST | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/finalize | write:bundle | Een bestandsupload voltooien |
Voordat u begint
Section titled “Voordat u begint”Om gegevens naar een data bundle te uploaden, hebt u nodig:
- een voltooide OAuth-flow met een
access_tokenen uw regionaleapi_url - een bestaande data bundle (
bundleId)
Als u nog geen bundle hebt, maakt u er eerst een door Een data bundle maken te volgen. Die flow haalt de apparaatinvoerboom op, kiest een leaf-dbuPath en maakt de bundle onder een bovenliggende node.
Stap 1: een uploadsessie openen
Section titled “Stap 1: een uploadsessie openen”Geef het totale aantal bestanden aan dat de batch zal bevatten. Voor een volledige ZF-opname met 13 afbeeldingen is dat 1 + 1 + 13 + 13 = 28.
POST {api_url}/v1/bundles/{bundleId}/upload-sessionsAuthorization: Bearer {access_token}Content-Type: application/json
{ "expectedFileCount": 28 }De response retourneert de sessionId en de requirements van de bundle:
{ "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 en cross-afhankelijkheden begrijpen
Section titled “requirements en cross-afhankelijkheden begrijpen”Elke vermelding in requirements beschrijft één bestandstype dat de bundle accepteert:
| Veld | Betekenis |
|---|---|
type | De identifier die u meegeeft als type bij het initiëren van een bestandsupload. |
allowedExtensions | Bestandsextensies die voor dit type worden geaccepteerd. |
optional | Als false, kan de bundle niet worden verwerkt zonder ten minste één bestand van dit type. |
allowMultiples | Als false, mag er slechts één bestand van dit type worden geüpload. |
dependencies | Andere typen die ook aanwezig moeten zijn wanneer dit type aanwezig is. |
Voor het ZF-voorbeeld:
pointcloudsentrajectorieszijn verplicht (optional: false) en enkelvoudig (allowMultiples: false). Elke ZF-bundle heeft precies één van elk nodig.picturesenpicturesMetadatazijn optioneel en accepteren meerdere bestanden.- De twee afbeeldingstypen zijn van elkaar afhankelijk:
pictures.dependencies = ["picturesMetadata"]enpicturesMetadata.dependencies = ["pictures"].
Een afhankelijkheid betekent: als een type aanwezig is, moet elk type dat het vermeldt ook aanwezig zijn. Omdat de ZF-afbeeldingstypen naar elkaar verwijzen, zijn ze alles-of-niets als paar. U kunt de afbeeldingen en hun metadata samen uploaden, of beide overslaan, maar u kunt niet het een zonder het ander uploaden.
Geldige ZF-batches:
- Minimaal —
1 pointcloud + 1 trajectory(geen afbeeldingen).expectedFileCount = 2. - Volledig —
1 pointcloud + 1 trajectory + N pictures + N picturesMetadata.expectedFileCount = 2 + 2N.
Ongeldig:
- Afbeeldingen zonder hun metadata (of omgekeerd), aangezien de cross-afhankelijkheid niet is voldaan.
- Een batch zonder puntenwolk of zonder traject, aangezien een verplicht type ontbreekt.
Voordat u een sessie opent, telt u elk bestand dat u van plan bent te uploaden en stelt u dat totaal in als expectedFileCount. Optionele typen tellen alleen mee als u ze in de batch opneemt. De sessie wordt voltooid wanneer dat aantal bestanden is voltooid, dus het aantal dat u opgeeft, moet overeenkomen met wat u daadwerkelijk uploadt.
Stap 2: elk bestand uploaden
Section titled “Stap 2: elk bestand uploaden”Herhaal de volgende drie aanroepen voor elk bestand in de batch.
2a. Initiëren
Section titled “2a. Initiëren”Geef de bestandsnaam, de grootte ervan in bytes en het type (een van de requirements-typen) mee:
POST {api_url}/v1/bundles/{bundleId}/upload-sessions/{sessionId}/filesAuthorization: Bearer {access_token}Content-Type: application/json
{ "fileName": "scan.las", "fileSize": 734003200, "type": "pointclouds" }De response beschrijft de multipart-upload: hoeveel parts er moeten worden verzonden en één presigned url per deel.
{ "fileId": "c8c8cb0b-f476-4211-a3a4-72e4e04b91d5", "parts": 8, "urls": [ { "partNumber": 1, "url": "https://s3…/part-1?…" }, { "partNumber": 2, "url": "https://s3…/part-2?…" } ]}2b. De delen uploaden
Section titled “2b. De delen uploaden”Splits het bestand in parts opeenvolgende brokken van ceil(fileSize / parts) bytes en PUT elke brok naar de bijbehorende presigned url. Er wordt geen auth-header verzonden bij deze verzoeken; de URL is al ondertekend. Bewaar de ETag-responseheader van elk deel; u hebt deze nodig om te voltooien.
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 partsGeef de ETag exact terug zoals geretourneerd, inclusief de omringende aanhalingstekens.
2c. Voltooien
Section titled “2c. Voltooien”Verzend de verzamelde delen om het bestand te voltooien. Het endpoint retourneert 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…\"" } ] }De response echoot het bestand en rapporteert de sessiestatus:
{ "fileId": "c8c8cb0b-f476-4211-a3a4-72e4e04b91d5", "fileName": "scan.las", "type": "pointclouds", "size": 734003200, "status": "completed", "sessionStatus": "pending"}sessionStatus blijft pending totdat het laatste verwachte bestand is voltooid, waarna het completed wordt.
Verlopen URL’s vernieuwen
Section titled “Verlopen URL’s vernieuwen”Presigned URL’s verlopen. Als een upload lang duurt en een PUT 403 begint te retourneren, geeft u de URL’s opnieuw uit vanaf het deel waar u bent gestopt en gaat u verder:
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?…" } ] }Het bestand hoeft niet opnieuw te worden geïnitieerd; alleen de deel-URL’s worden vernieuwd.
Stap 3: voortgang volgen en hervatten
Section titled “Stap 3: voortgang volgen en hervatten”Som de sessies van een bundle op om de voortgang te controleren of een onderbroken batch te hervatten zonder de sessionId in het geheugen te houden. De lijst is gepagineerd en sorteerbaar, met een optioneel status-filter.
Queryparameters: page (standaard 1), limit (standaard 20, maximum 20), sortBy (createdAt of status, standaard createdAt), sortDir (ASC of DESC, standaard ASC), status (optioneel filter: pending of 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 versus expectedFileCount vertelt u hoeveel bestanden er nog over zijn. Om te hervatten, initieert en voltooit u alleen de bestanden die nog niet zijn voltooid.
Validatie en limieten
Section titled “Validatie en limieten”Lees dit zorgvuldig, want het bepaalt waarvoor u verantwoordelijk bent bij het gebruik van de RealityConnect API om gegevens te uploaden.
- De upload-endpoints registreren elk bestand onder het
typedat u opgeeft en voltooien de sessie puur op basis van het aantal: zodrafinalizedFileCountgelijk is aanexpectedFileCount, is de sessiecompleted. Op het moment van uploaden verifieert de server niet dat hettypeeen van derequirementsis, dat de bestandsextensie is toegestaan, dat verplichte typen aanwezig zijn of dat aan cross-afhankelijkheden is voldaan. - Alle regels (verplichte typen,
allowMultiplesen dedependenciestussen typen) worden afgedwongen nadat de bundle voor verwerking is verzonden. Een batch die derequirementsnegeert, wordt succesvol geüpload maar mislukt bij de verwerking. - Diepgaande inhoudsvalidatie wordt nooit uitgevoerd door de upload-API. Of een
.laseen echte ZF-puntenwolk is, of een.jsoncorrect verwijst naar zijn afbeelding, wordt alleen bij de verwerking gecontroleerd.
Behandel requirements als het contract. Respecteer de typen, extensies, optionaliteit, multipliciteit en afhankelijkheden in uw uploadbatch, zodat de bundle verwerkbaar is zodra deze is geüpload. Verwerkingsfouten die worden veroorzaakt door een onjuiste uploadstructuur worden niet terugbetaald.
Volledig voorbeeld
Section titled “Volledig voorbeeld”Alles samengevoegd voor de ZF-opname. files is de lijst die u opbouwt uit uw lokale dataset, waarbij elke vermelding een pad koppelt aan het type uit 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"Volgende stap
Section titled “Volgende stap”Zodra elk bestand is geüpload en de sessie completed is, gaat u verder naar Verwerking en bewaking om de ruwe invoer om te zetten in weergeefbare uitvoer.