Regroupe chaque nuage avec son role dans le calcul de volume (Ceil = surface superieure, Ground = plan Z constant ou second nuage), cote a cote comme dans la boite de dialogue "Compute Volume" du GUI, au lieu d'un unique bloc de parametres partages en dessous des deux fichiers. La hauteur de reference Z (mode plan constant) est deplacee dans le panneau Ground plutot que noyee dans les parametres avances. Garde les reglages de grille/remplissage (grid step, empty fill, cell height, direction) dans une section UNIQUE et partagee : cote CLI CloudCompare (-PROJ/-EMPTY_FILL/-VERT_DIR), ce sont des reglages appliques a toute la grille, pas un parametre par nuage - les dupliquer par role induirait en erreur sur ce que le backend supporte reellement. Ajoute un tooltip (title) sur chaque champ expliquant sa correspondance avec le flag CLI CloudCompare concerne, et des labels reprenant la terminologie du GUI (Ceil/Ground, Fill empty cells with, Cell height...). Verifie que toutes les options exposees correspondent a des flags CLI CloudCompare reellement supportes par le backend (aucune option inventee). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| docker | ||
| public | ||
| python | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| CONCEPT.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| nest-cli.json | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.build.json | ||
| tsconfig.json | ||
Volume 2.5D & robustesse a la decimation
Application locale (Docker) qui, a partir d'un ou deux nuages de points (LAS/LAZ/COPC.laz/BIN) :
- calcule un volume en 2.5D via CloudCompare, selon 2 modes au choix :
- Plan de reference Z constant : un seul nuage, compare a un plan horizontal (altitude minimale par defaut). Fiable uniquement si le nuage ne contient QUE l'objet mesure (pas de terrain/contexte environnant) ;
- Comparaison a un second nuage : deux nuages, la surface superieure (sommet de l'amas) et la limite inferieure (base/socle) — les densites sont d'abord appariees (le plus dense decime pour matcher l'autre), puis les deux sont decimes ensemble a chaque etape ;
- dans les deux modes, decime progressivement en methode spatiale, facteur x2 sur 5 etapes (le pas spatial double a chaque etape, en partant d'un pas initial estime automatiquement = distance mediane au plus proche voisin, calculee par KD-tree sur un echantillon) ;
- recalcule le volume a chaque niveau avec une grille de calcul adaptee dynamiquement a la densite de chaque niveau (grid step = pas spatial du niveau x2), et comble les trous de scan avant chaque calcul (interpolation Delaunay bornee par une distance max, pour ne pas combler les parties concaves du contour de l'objet) ;
- assemble tous les nuages exploitables (complet + 5 decimations, x1 ou x2 selon le mode) dans un seul
fichier
.binCloudCompare, nomme et consultable dans CloudCompare Desktop ; - produit un dossier de resultats : rapport CSV/JSON, cartes de hauteur par niveau (et par nuage en mode comparaison), graphiques de synthese (volume, nombre de points, robustesse) ;
- expose tout ca dans une UI web simple avec historique des runs.
Demarrage
docker compose up -d --build
Puis ouvrir http://localhost:8080
Les donnees (uploads, resultats, base sqlite) sont persistees dans un volume Docker nomme app-data
(pas un bind mount vers ./data, voir "Deploiement Coolify" ci-dessous pour la raison).
Authentification
L'UI et l'API sont protegees par HTTP Basic Auth (identifiants dans AUTH_USERNAME /
AUTH_PASSWORD_HASH, hash bcrypt — jamais le mot de passe en clair). Seul /health reste public
(healthcheck Docker/Coolify).
Si ces deux variables ne sont pas definies, l'auth est desactivee (pratique pour du dev local, un warning est logge au demarrage). En production/Coolify, toujours les definir.
Generer un hash pour un nouveau mot de passe :
node -e "console.log(require('bcryptjs').hashSync('VOTRE_MOT_DE_PASSE', 10))"
Puis definir dans Coolify (onglet "Environment Variables" de l'application, pas dans
docker-compose.yml du repo, pour ne pas committer le hash) :
AUTH_USERNAME=nico
AUTH_PASSWORD_HASH=$2a$10$... # sortie de la commande ci-dessus
En local avec docker compose, un fichier .env (gitignore, jamais committe) a la racine du repo
fonctionne aussi — docker compose le charge automatiquement :
cp .env.example .env
# puis editer .env avec un vrai AUTH_PASSWORD_HASH genere via la commande ci-dessus
.env.example est committe et documente toutes les variables disponibles ; .env
(vos vraies valeurs) ne l'est jamais.
Deploiement Coolify
Le repo est pret pour un deploiement Coolify via webhook (push sur main -> build + deploy auto) :
- Le
Dockerfilefait tourner lint + typecheck + tests unitaires avant de compiler — un commit qui casse l'un de ces trois echoue le build, donc n'est jamais deploye. HEALTHCHECKintegre au Dockerfile (curlsur/health, route publique non authentifiee) : Coolify l'utilise pour verifier que le deploiement est reellement sain.- Persistance — action requise cote Coolify, sinon les runs disparaissent a chaque redeploy :
- Si la resource est de type "Docker Compose" (pointant sur
docker-compose.ymldu repo) : le volume nommeapp-data:/datadeclare dedans suffit, rien a faire de plus. - Si la resource est de type "Application" (build pack Dockerfile — c'est le cas typique quand
on a du forcer le build pack sur "Dockerfile" pour eviter Nixpacks) : Coolify ignore
completement
docker-compose.yml, y compris sonvolumes:. Sans configuration explicite, le conteneur redemarre a chaque deploy avec un/dataneuf (leVOLUME ["/data"]du Dockerfile cree juste un volume anonyme, jamais reattache au precedent). Il faut aller dans l'onglet "Storages" de l'application Coolify et ajouter un volume persistant monte sur/data— sans ca, tous les runs (nuages, resultats, base sqlite) sont perdus a chaque redeploiement.
- Si la resource est de type "Docker Compose" (pointant sur
- Variables d'environnement a definir dans Coolify :
AUTH_USERNAME/AUTH_PASSWORD_HASH(obligatoire, voir "Authentification" plus haut), et optionnellementMAX_UPLOAD_MB,STATS_SAMPLE_CAP,CC_TIMEOUT_MSsi besoin de changer les defauts (voir tableau plus bas). - Le webhook de deploiement automatique est deja configure cote Coolify sur ce repo.
Utilisation
- "Nouveau run" -> choisir le type de comparaison :
- Plan de reference Z constant : un fichier
.las,.laz,.copc.lazou.bin. - Comparaison a un second nuage : deux fichiers — "Surface superieure" (sommet de l'amas) et "Limite inferieure" (base/socle de l'amas).
- Plan de reference Z constant : un fichier
- Optionnel (options avancees) : override du plan de reference Z (mode Z constant uniquement, par defaut altitude minimale d'un echantillon), du pas spatial initial (par defaut : distance mediane au plus proche voisin) et/ou de la distance max de remplissage des trous (par defaut : grille du niveau x3 — augmenter si des trous de scan legitimes restent vides, diminuer si des creux du contour de l'objet sont a tort combles).
- Le run tourne en arriere-plan (une file d'attente sequentielle traite les runs un par un). La page de detail se rafraichit automatiquement (polling 2s).
- Une fois termine : tableau des niveaux (points par nuage, volume, surface, % de cellules de grille
"matching"), cartes de hauteur (par nuage en mode comparaison), graphiques de synthese, et
telechargements (
.binfusionne,.csv, dossier complet en.zip).
Lecture des resultats
- % de cellules "matching" (rapport de volume 2.5D de CloudCompare) : indique la part de la grille
de calcul ou les deux surfaces sont effectivement comparables. Un niveau est marque "a verifier" des
que ce taux passe sous 90 % (configurable via
matchingCellsWarnThreshold,config.ts). - La decimation s'arrete automatiquement si un niveau (ou l'un des deux nuages en mode comparaison) tombe sous 25 points exploitables ; les niveaux suivants sont marques "skipped" avec la raison.
- Choix du mode : le mode "Z constant" n'est fiable que si le nuage uploade ne contient QUE l'objet a mesurer. S'il contient aussi du terrain/contexte environnant (scan de site complet par exemple), le plan Z constant (altitude minimale de tout le nuage) n'a souvent aucun rapport avec la base reelle de l'objet et donne un volume trompeur — utiliser "Comparaison a un second nuage" avec une base dediee dans ce cas.
Parametres (variables d'environnement, voir docker-compose.yml)
| Variable | Defaut | Description |
|---|---|---|
PORT |
3000 | port interne du serveur |
DATA_DIR |
/data |
dossier de persistance (uploads, resultats, sqlite) |
AUTH_USERNAME |
(non defini = auth off) | nom d'utilisateur HTTP Basic |
AUTH_PASSWORD_HASH |
(non defini = auth off) | hash bcrypt du mot de passe |
MAX_UPLOAD_MB |
2048 | taille max d'un nuage uploade |
STATS_SAMPLE_CAP |
50000 | nb de points echantillonnes pour estimer le pas spatial initial |
CC_TIMEOUT_MS |
600000 | timeout par appel CloudCompare CLI |
Formats supportes
.las, .laz, .copc.laz (lu comme un LAZ standard — l'indexation octree COPC n'est pas utilisee,
seuls les points le sont), .bin (format natif CloudCompare).
E57 non supporte pour l'instant (exclu volontairement du perimetre initial).
Architecture
- Backend : NestJS (TypeScript), file d'attente de jobs en memoire (un run a la fois), SQLite
(
better-sqlite3) pour l'historique des runs. - Orchestration du pipeline CloudCompare : chaque etape (estimation stats, conversion, decimation,
volume, export) est un appel independant au CLI
CloudCompare -SILENT ...(le CLI fonctionne comme une machine a etats sur UNE session de process, donc chaque etape = un process). - Scripts Python (
python/) :stats_helper.py(distance au plus proche voisin via scipy KD-tree),render_images.py(cartes de hauteur + graphiques de synthese via matplotlib/numpy),las_to_xyz.py(conversion LAS/LAZ/COPC -> ASCII, voir "Limitation connue" ci-dessous). - Frontend : HTML/JS vanilla + Tailwind (CDN), servi en statique par NestJS. Pas de framework, pas de build front separe.
- Image Docker :
debian:trixie-slim+ paquet aptcloudcompare(2.13.2, evite une compilation depuis les sources) +xvfb(CloudCompare reste une appli Qt, meme en-SILENTelle a besoin d'un display) + Node 20 + Python 3.
Limitations connues
- Le paquet apt
cloudcompare(Debian) ne fournit pas le plugin LAS/LAZ (uniquement le plugin "Core I/O"). Consequence : les fichiers LAS/LAZ/COPC sont convertis en ASCII XYZ vialaspy(Python) avant d'etre transmis a CloudCompare, qui lit l'ASCII nativement sans plugin. Ca fonctionne bien mais perd les attributs LAS annexes (intensite, classification, RGB...) — non utilises par ce calcul de volume de toute facon. - Le meme paquet plante (assertion GDAL) sur l'export GeoTIFF natif (
-RASTERIZE -OUTPUT_RASTER_Z). Les cartes de hauteur sont donc reconstruites nous-memes (export ASCII du nuage + binning numpy/scipy + rendu matplotlib) plutot que de dependre du rasterizer CloudCompare. - Le plan de reference Z et le pas de grille de volume sont approximatifs quand ils sont auto-detectes (calcules sur un echantillon aleatoire de points, pas la totalite du nuage) — largement suffisant pour comparer les 6 niveaux entre eux, mais a corriger via l'override si une valeur exacte est necessaire.
- Un seul run traite a la fois (file d'attente sequentielle) : adapte a un usage local mono-utilisateur, pas concu pour un usage concurrent multi-utilisateurs.
Developpement local (sans Docker)
Necessite CloudCompare installe localement + Python 3 avec pip install -r python/requirements.txt.
npm install
npm run lint # eslint
npm run typecheck # tsc --noEmit
npm run test # jest (tests unitaires)
npm run build
CLOUDCOMPARE_BIN="/chemin/vers/CloudCompare" PYTHON_BIN=python3 DATA_DIR=./data node dist/main.js
Voir CLAUDE.md pour le detail des comportements CloudCompare CLI verifies empiriquement (pieges non documentes, limitations du paquet apt Debian, etc.) — a lire avant de toucher au pipeline.