Téléversement de données dans un Data Bundle
Ce guide explique comment téléverser les fichiers d’entrée bruts d’une capture dans un Data Bundle via la RealityConnect API.
Tous les points de terminaison de téléversement requièrent le scope write:bundle ; la liste des sessions requiert read:bundle.
Flux de téléversement
Section intitulée « Flux de téléversement »Un Data Bundle contient les entrées brutes d’une seule capture : un nuage de points, une trajectoire, des images, des métadonnées par image, etc. L’ensemble exact de fichiers acceptés par un bundle dépend du périphérique pour lequel le bundle a été créé.
Les fichiers sont téléversés dans des sessions de téléversement. Une session est un conteneur créé par le serveur pour un lot de fichiers du bundle. Lorsque vous l’ouvrez, vous déclarez le nombre de fichiers que le lot contiendra (expectedFileCount) ; la session se complète automatiquement une fois ce nombre de fichiers finalisés.
Chaque fichier individuel est téléversé avec un téléversement S3 multipart :
- Ouvrir une session sur le bundle, en déclarant le nombre total de fichiers que vous allez envoyer.
- Pour chaque fichier : initier le téléversement, PUT ses parties directement vers les URL retournées, puis finaliser.
- Lorsque le nombre de fichiers finalisés atteint
expectedFileCount, la session est marquéecompleted.
sequenceDiagram
participant Client as Client
participant RCAPI as RCAPI
participant S3 as S3
Client->>RCAPI: POST upload-sessions
RCAPI-->>Client: sessionId + requirements
loop pour chaque fichier
Client->>RCAPI: POST initiate file
RCAPI-->>Client: fileId + presigned part URLs
loop pour chaque partie
Client->>S3: PUT part
S3-->>Client: ETag
end
Client->>RCAPI: POST finalize
RCAPI-->>Client: fichier complété + sessionStatus
end
Les requirements de téléversement (retournés lorsque vous ouvrez une session) indiquent exactement quels types de fichiers le bundle accepte, quels types sont obligatoires et comment ils dépendent les uns des autres.
L’exemple complet : une capture ZF SLAM
Section intitulée « L’exemple complet : une capture ZF SLAM »Un bundle ZF SLAM est un bon exemple car il accepte quatre types de fichiers différents avec une dépendance inter-types. Une capture ZF complète consiste en :
| Fichier | Type | Notes |
|---|---|---|
scan.las | nuage de points | exactement un |
…laser_0.txt | trajectoire | exactement un |
panorama/*.jpg | images panorama | plusieurs, chacune associée à des métadonnées |
panorama/*.json | métadonnées par image | plusieurs, une par image |
Un lot ZF complet est donc 1 nuage de points + 1 trajectoire + N images + N fichiers de métadonnées. Nous téléverserons tous ces fichiers dans une seule session.
Points de terminaison
Section intitulée « Points de terminaison »Tous les chemins sont relatifs à votre api_url régionale. Le bundle est adressé par son propre id (bundleId).
| Méthode | Chemin | Scope | Objectif |
|---|---|---|---|
POST | /v1/bundles/{bundleId}/upload-sessions | write:bundle | Ouvrir une session |
GET | /v1/bundles/{bundleId}/upload-sessions | read:bundle | Lister les sessions (reprise / progression) |
POST | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files | write:bundle | Initier un téléversement de fichier |
GET | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/refresh/{startIndex} | write:bundle | Réémettre des URL de parties expirées |
POST | /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/finalize | write:bundle | Finaliser un téléversement de fichier |
Avant de commencer
Section intitulée « Avant de commencer »Pour téléverser des données dans un Data Bundle, vous avez besoin de :
- un flux OAuth terminé avec un
access_tokenet votreapi_urlrégionale - un Data Bundle existant (
bundleId)
Si vous n’avez pas encore un bundle, créez-en un en suivant Création d’un Data Bundle. Ce flux obtient l’arbre d’entrée du périphérique, choisit un dbuPath feuille et crée le bundle sous un nœud parent.
Étape 1 : ouvrir une session de téléversement
Section intitulée « Étape 1 : ouvrir une session de téléversement »Déclarez le nombre total de fichiers que le lot contiendra. Pour une capture ZF complète avec 13 images, c’est 1 + 1 + 13 + 13 = 28.
POST {api_url}/v1/bundles/{bundleId}/upload-sessionsAuthorization: Bearer {access_token}Content-Type: application/json
{ "expectedFileCount": 28 }La réponse retourne le sessionId et les requirements du 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"] } ]}Comprendre requirements et les dépendances croisées
Section intitulée « Comprendre requirements et les dépendances croisées »Chaque entrée dans requirements décrit un type de fichier que le bundle accepte :
| Champ | Signification |
|---|---|
type | L’identifiant que vous transmettez comme type lors de l’initiation d’un téléversement de fichier. |
allowedExtensions | Extensions de fichier acceptées pour ce type. |
optional | Si false, le bundle ne peut pas être traité sans au moins un fichier de ce type. |
allowMultiples | Si false, un seul fichier de ce type peut être téléversé. |
dependencies | Autres types qui doivent également être présents lorsque ce type est présent. |
Pour l’exemple ZF :
pointcloudsettrajectoriessont obligatoires (optional: false) et uniques (allowMultiples: false). Chaque bundle ZF a besoin d’exactement un fichier de chaque type.picturesetpicturesMetadatasont facultatifs et acceptent plusieurs fichiers.- Les deux types d’images dépendent l’un de l’autre :
pictures.dependencies = ["picturesMetadata"]etpicturesMetadata.dependencies = ["pictures"].
Une dépendance signifie : si un type est présent, chaque type qu’il liste doit également être présent. Comme les types d’images ZF référencent l’un l’autre, ils sont tout ou rien en paire. Vous pouvez téléverser les images et leurs métadonnées ensemble, ou ignorer les deux, mais vous ne pouvez pas téléverser l’un sans l’autre.
Lots ZF valides :
- Minimal :
1 nuage de points + 1 trajectoire(sans images).expectedFileCount = 2. - Complet :
1 nuage de points + 1 trajectoire + N pictures + N picturesMetadata.expectedFileCount = 2 + 2N.
Non valide :
- Images sans leurs métadonnées (ou inversement), car la dépendance croisée n’est pas satisfaite.
- Un lot sans nuage de points ou sans trajectoire, car un type obligatoire est manquant.
Avant d’ouvrir une session, comptez chaque fichier que vous prévoyez de téléverser et définissez ce total comme expectedFileCount. Les types facultatifs ne comptent que si vous les incluez dans le lot. La session se complète lorsque ce nombre de fichiers est finalisé, donc le nombre déclaré doit correspondre à ce que vous téléversez réellement.
Étape 2 : téléverser chaque fichier
Section intitulée « Étape 2 : téléverser chaque fichier »Répétez les trois appels suivants pour chaque fichier du lot.
2a. Initier
Section intitulée « 2a. Initier »Transmettez le nom du fichier, sa taille en octets et son type (l’un des types de requirements) :
POST {api_url}/v1/bundles/{bundleId}/upload-sessions/{sessionId}/filesAuthorization: Bearer {access_token}Content-Type: application/json
{ "fileName": "scan.las", "fileSize": 734003200, "type": "pointclouds" }La réponse décrit le téléversement multipart : le nombre de parts à envoyer et une url pré-signée par partie.
{ "fileId": "c8c8cb0b-f476-4211-a3a4-72e4e04b91d5", "parts": 8, "urls": [ { "partNumber": 1, "url": "https://s3…/part-1?…" }, { "partNumber": 2, "url": "https://s3…/part-2?…" } ]}2b. Téléverser les parties
Section intitulée « 2b. Téléverser les parties »Divisez le fichier en parts blocs séquentiels de ceil(fileSize / parts) octets et effectuez un PUT de chaque bloc vers son url pré-signée. Aucun en-tête d’authentification n’est envoyé sur ces requêtes ; l’URL est déjà signée. Conservez l’en-tête de réponse ETag de chaque partie, vous en aurez besoin pour finaliser.
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 partsTransmettez l’ETag exactement comme retourné, y compris ses guillemets.
2c. Finaliser
Section intitulée « 2c. Finaliser »Envoyez les parties collectées pour compléter le fichier. Le point de terminaison retourne 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…\"" } ] }La réponse renvoie le fichier et indique le statut de la session :
{ "fileId": "c8c8cb0b-f476-4211-a3a4-72e4e04b91d5", "fileName": "scan.las", "type": "pointclouds", "size": 734003200, "status": "completed", "sessionStatus": "pending"}sessionStatus reste pending jusqu’à la finalisation du dernier fichier attendu, moment auquel il devient completed.
Réémission d’URL expirées
Section intitulée « Réémission d’URL expirées »Les URL pré-signées expirent. Si un téléversement prend longtemps et qu’un PUT commence à retourner 403, réémettez les URL depuis la partie où vous avez arrêté et continuez :
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?…" } ] }Le fichier n’a pas besoin d’être ré-initié ; seules les URL de parties sont renouvelées.
Étape 3 : suivre la progression et reprendre
Section intitulée « Étape 3 : suivre la progression et reprendre »Listez les sessions d’un bundle pour vérifier la progression ou reprendre un lot interrompu sans garder le sessionId en mémoire. La liste est paginée et triable, avec un filtre status facultatif.
Paramètres de requête : page (défaut 1), limit (défaut 20, maximum 20), sortBy (createdAt ou status, défaut createdAt), sortDir (ASC ou DESC, défaut ASC), status (filtre facultatif : pending ou 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 par rapport à expectedFileCount indique combien de fichiers restent. Pour reprendre, initiez et finalisez uniquement les fichiers qui ne sont pas encore complétés.
Validation et limites
Section intitulée « Validation et limites »Lisez ceci attentivement, car cela détermine ce dont vous êtes responsable lors de l’utilisation de la RealityConnect API pour téléverser des données.
- Les points de terminaison de téléversement enregistrent chaque fichier sous le
typeque vous fournissez et complètent la session uniquement sur le nombre : une fois quefinalizedFileCountatteintexpectedFileCount, la session estcompleted. Au moment du téléversement, le serveur ne vérifie pas que letypeest l’un desrequirements, que l’extension du fichier est autorisée, que les types obligatoires sont présents ou que les dépendances croisées sont satisfaites. - Toutes les règles (types obligatoires,
allowMultiplesetdependenciesentre types) sont appliquées après que le bundle est envoyé pour traitement. Un lot qui ignore lesrequirementsse téléversera avec succès mais échouera au traitement. - La validation profonde du contenu n’est jamais effectuée par l’API de téléversement. Qu’un
.lassoit un nuage de points ZF authentique, ou qu’un.jsonréférence correctement son image, n’est vérifié qu’au traitement.
Traitez requirements comme le contrat. Respectez les types, extensions, optionnalité, multiplicité et dépendances dans votre lot de téléversement pour que le bundle soit traitable une fois téléversé. Les échecs de traitement causés par une structure de téléversement incorrecte ne sont pas remboursables.
Exemple complet
Section intitulée « Exemple complet »Mise en pratique pour la capture ZF. files est la liste que vous construisez depuis votre jeu de données local, chaque entrée associant un chemin à son type issu de 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"Étape suivante
Section intitulée « Étape suivante »Une fois chaque fichier téléversé et la session completed, continuez vers Traitement et suivi pour transformer les entrées brutes en sorties visualisables.