#!/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())