- Grille de calcul du volume adaptee dynamiquement au pas spatial de chaque niveau (au lieu d'une grille fixe derivee du niveau 0) : evite l'effondrement artificiel du "matching cells %" observe des le premier niveau de decimation (99% -> 58% -> 13% -> 3% -> 0.9% -> 0.2%), qui refletait un desalignement grille/densite plutot qu'une vraie perte d'information. - Authentification HTTP Basic sur toute l'app (UI + API), identifiants en env (AUTH_USERNAME / AUTH_PASSWORD_HASH hashe bcrypt), desactivable en dev local si non definis. /health reste public pour le healthcheck Docker/Coolify. |
||
|---|---|---|
| docker | ||
| public | ||
| python | ||
| src | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.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 nuage de points (LAS/LAZ/COPC.laz/BIN) :
- calcule son volume en 2.5D via CloudCompare (plan de reference = altitude minimale du nuage) ;
- le 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 le meme plan de reference, et une grille de calcul adaptee dynamiquement a la densite de chaque niveau (grid step = pas spatial du niveau x2) pour eviter des grilles artificiellement vides quand la decimation depasse la resolution de depart ;
- assemble les 6 nuages (complet + 5 decimations) dans un seul fichier
.binCloudCompare, nomme et consultable dans CloudCompare Desktop ; - produit un dossier de resultats : rapport CSV/JSON, cartes de hauteur par niveau, 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) a la racine du repo fonctionne aussi :
AUTH_USERNAME=nico
AUTH_PASSWORD_HASH=$2a$10$...
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 : le volume
/datadoit etre monte comme un volume nomme/gere par Coolify, pas un bind mount vers le checkout git (Coolify re-clone le repo a chaque deploiement — un bind mount relatif au checkout perdrait toutes les donnees a chaque redeploy). Deux options :- Resource "Docker Compose" dans Coolify pointant sur
docker-compose.ymldu repo : le volume nommeapp-datadeclare dedans est deja correct. - Resource "Application" (Dockerfile) : configurer un volume persistant sur
/datadepuis l'onglet "Storage" de Coolify, et le port expose sur3000(variablePORT, lue par l'appli).
- Resource "Docker Compose" dans Coolify 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 un fichier
.las,.laz,.copc.lazou.bin. - Optionnel : override du plan de reference Z (par defaut : altitude minimale d'un echantillon du nuage) et/ou du pas spatial initial (par defaut : distance mediane au plus proche voisin).
- 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 6 niveaux (points, volume, surface, % de cellules de grille
"matching"), cartes de hauteur, 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 (nuage / plan de reference) sont effectivement comparables. Quand la
decimation depasse la resolution de la grille de calcul, ce pourcentage chute — c'est le signal
principal de perte de robustesse. Un niveau est marque "a verifier" des que ce taux passe sous 90 %
(configurable via
MATCHING_CELLS_WARN_THRESHOLDcote code,config.ts). - La decimation s'arrete automatiquement si un niveau tombe sous 25 points exploitables ; les niveaux suivants sont marques "skipped" avec la raison.
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.