Pipeline: corrige le biais de sous-echantillonnage sur median_nn (facteur sqrt(total/echantillon), gonflait le pas spatial jusqu'a x20 sur les gros nuages), applique un decalage global X/Y pour la precision float32 en coordonnees projetees (Lambert-93), fixe la portee de remplissage des trous sur la grille du niveau 0 (au lieu de croitre a chaque niveau), utilise un percentile robuste pour zref au lieu du minimum brut, et filtre plus precisement le fichier de grille parasite genere par -VOLUME. Securite: valide l'id de run (uuid) sur toutes les routes avant de construire un chemin filesystem (traversee de repertoire post-auth via zip/csv/bin/images), assainit le nom de fichier uploade avant multer, retire enableCors() (surface inutile), passe le conteneur en utilisateur non-root. Documente l'ensemble de la methode et ses limites dans CONCEPT.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
63 lines
2.5 KiB
Python
63 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Convertit un fichier LAS/LAZ/COPC (via laspy) en nuage ASCII XYZ brut, lisible
|
|
nativement par CloudCompare sans plugin LAS (le paquet apt Debian de CloudCompare
|
|
ne fournit pas le plugin LAS/LAZ - seulement le plugin "Core I/O").
|
|
|
|
Un .copc.laz est simplement lu comme un LAZ standard (les points sont
|
|
identiques, seule l'indexation octree COPC, inutile ici, est ignoree).
|
|
|
|
Decalage global X/Y : CloudCompare stocke les coordonnees en float32 en interne. En coordonnees
|
|
projetees (ex. Lambert-93 / EPSG:2154, Y ~ 6.8e6 m), l'ULP de float32 a cette magnitude vaut ~0.5m,
|
|
ce qui quantifie silencieusement la position des points sur une grille de 0.5m - incompatible avec
|
|
un pas de calcul de l'ordre du decimetre (voir CONCEPT.md §7.2). On soustrait donc un decalage fixe
|
|
(coin bas de la bbox, arrondi au metre, lu directement dans l'entete LAS - pas de second passage sur
|
|
les points) de X et Y avant ecriture. Le volume 2.5D etant invariant par translation, ce decalage
|
|
n'affecte aucun resultat ; il est renvoye en sortie pour tracabilite. Z n'est pas decale : en
|
|
alti-metrique (~10^2-10^3 m), la precision float32 y est deja largement suffisante.
|
|
|
|
Usage:
|
|
python3 las_to_xyz.py --input nuage.laz --output nuage.xyz
|
|
"""
|
|
import argparse
|
|
import json
|
|
import math
|
|
import sys
|
|
|
|
import laspy
|
|
import numpy as np
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--input", required=True)
|
|
parser.add_argument("--output", required=True)
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
with laspy.open(args.input) as reader:
|
|
offset_x = math.floor(reader.header.mins[0])
|
|
offset_y = math.floor(reader.header.mins[1])
|
|
|
|
total = 0
|
|
with open(args.output, "w", encoding="ascii") as out:
|
|
for chunk in reader.chunk_iterator(2_000_000):
|
|
x = np.asarray(chunk.x) - offset_x
|
|
y = np.asarray(chunk.y) - offset_y
|
|
xyz = np.column_stack([x, y, np.asarray(chunk.z)])
|
|
np.savetxt(out, xyz, fmt="%.6f")
|
|
total += xyz.shape[0]
|
|
except Exception as exc:
|
|
print(json.dumps({"error": f"conversion LAS/LAZ impossible: {exc}"}))
|
|
return 1
|
|
|
|
if total == 0:
|
|
print(json.dumps({"error": "le fichier LAS/LAZ ne contient aucun point"}))
|
|
return 1
|
|
|
|
print(json.dumps({"ok": True, "count": total, "offsetX": offset_x, "offsetY": offset_y}))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|