Translate the whole project to English and fix outdated comments and help
Comments, docstrings, logs, CLI help, map UI, legends, PDF sheet, scripts, compose files and AGENTS.md are now English. Data keys stay unchanged (relief_oriente, densite_sol, visualisations/, API JSON keys, link params). Wrong comments and help defaults found along the way are corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,12 +1,12 @@
|
||||
"""Qualité des données LiDAR par dalle : densité de points sol et date d'acquisition.
|
||||
"""Per-tile LiDAR data quality: ground point density and acquisition date.
|
||||
|
||||
Un sidecar JSON par dalle (output/quality/{basename}.json) est écrit par le
|
||||
pipeline, recopié dans l'inventaire (index_tiles.json, table `quality`) et
|
||||
conservé sur les machines légères : l'encart qualité de l'export PDF le lit
|
||||
depuis le disque local, amont joignable ou non.
|
||||
One JSON sidecar per tile (output/quality/{basename}.json) is written by the
|
||||
pipeline, copied into the inventory (index_tiles.json, `quality` table) and
|
||||
kept on the lightweight machines: the quality inset of the PDF export reads it
|
||||
from the local disk, whether the upstream is reachable or not.
|
||||
|
||||
numpy n'est importé que par le calcul : l'image légère (sans numpy) lit et
|
||||
écrit les sidecars.
|
||||
numpy is only imported by the computation: the lightweight image (without
|
||||
numpy) reads and writes the sidecars.
|
||||
"""
|
||||
|
||||
import json
|
||||
@ -20,32 +20,35 @@ logger = logging.getLogger("lidar")
|
||||
|
||||
QUALITY_VERSION = 1
|
||||
QUALITY_DIRNAME = "quality"
|
||||
DENSITY_CELL_M = 50.0 # maille de la grille de densité (20 × 20 par dalle)
|
||||
EMPTY_PIXEL_M = 0.2 # pixel du MNT : part sans point sol ≈ part interpolée
|
||||
DENSITY_CELL_M = 50.0 # density grid cell size (20 × 20 per tile)
|
||||
EMPTY_PIXEL_M = 0.2 # DTM pixel: share without ground point ≈ interpolated share
|
||||
|
||||
_GPS_EPOCH = datetime(1980, 1, 6, tzinfo=timezone.utc)
|
||||
_GPS_ADJUSTED_OFFSET = 1e9 # temps GPS ajusté standard = secondes GPS − 1e9
|
||||
_GPS_ADJUSTED_OFFSET = 1e9 # standard adjusted GPS time = GPS seconds − 1e9
|
||||
|
||||
|
||||
def gps_adjusted_to_date(t):
|
||||
"""Temps GPS ajusté standard → date ISO (UTC ; secondes intercalaires
|
||||
négligées : précision au jour)."""
|
||||
"""Standard adjusted GPS time → ISO date (UTC; leap seconds ignored:
|
||||
accurate to the day)."""
|
||||
return (_GPS_EPOCH + timedelta(seconds=float(t) + _GPS_ADJUSTED_OFFSET)).date().isoformat()
|
||||
|
||||
|
||||
def compute_quality(x, y, gps_time, bounds, gps_adjusted=True, header_date=None):
|
||||
"""Mesure la qualité d'une dalle à partir de ses points sol.
|
||||
"""Measure the quality of a tile from its ground points.
|
||||
|
||||
Args:
|
||||
x, y: coordonnées L93 des points sol.
|
||||
gps_time: temps GPS des points (ou None).
|
||||
bounds: emprise nominale (min_x, min_y, max_x, max_y) ; les points
|
||||
hors emprise (bande de raccord) sont ignorés.
|
||||
gps_adjusted: True si gps_time est en temps GPS ajusté standard.
|
||||
header_date: date ISO de l'en-tête LAS (repli des dates).
|
||||
x, y: L93 coordinates of the ground points.
|
||||
gps_time: GPS time of the points (or None).
|
||||
bounds: nominal footprint (min_x, min_y, max_x, max_y); points
|
||||
outside it (edge buffer) are ignored.
|
||||
gps_adjusted: True if gps_time is standard adjusted GPS time.
|
||||
header_date: ISO date from the LAS header (date fallback).
|
||||
|
||||
Returns:
|
||||
dict JSON-sérialisable (cf. spec, section 1).
|
||||
JSON-serializable dict: version, ground_density (pts/m²),
|
||||
density_grid (pts/m² per 50 m cell, rows north to south),
|
||||
empty_fraction (share of 0.2 m pixels without a ground point),
|
||||
acq_start / acq_end (ISO dates) and acq_source ("gps", "header" or None).
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
@ -98,12 +101,12 @@ def compute_quality(x, y, gps_time, bounds, gps_adjusted=True, header_date=None)
|
||||
|
||||
|
||||
def quality_path(output_dir, basename):
|
||||
"""Chemin du sidecar qualité d'une dalle."""
|
||||
"""Path of a tile's quality sidecar."""
|
||||
return Path(output_dir) / QUALITY_DIRNAME / f"{basename}.json"
|
||||
|
||||
|
||||
def write_quality(output_dir, basename, data):
|
||||
"""Écrit le sidecar (atomique) ; False si le contenu est déjà identique."""
|
||||
"""Write the sidecar (atomically); False if the content is already identical."""
|
||||
path = quality_path(output_dir, basename)
|
||||
payload = json.dumps(data, ensure_ascii=False, sort_keys=True)
|
||||
try:
|
||||
@ -127,7 +130,7 @@ def write_quality(output_dir, basename, data):
|
||||
|
||||
|
||||
def read_quality(output_dir, basename):
|
||||
"""Sidecar d'une dalle, ou None (absent, illisible, autre version)."""
|
||||
"""A tile's sidecar, or None (missing, unreadable, other version)."""
|
||||
try:
|
||||
data = json.loads(quality_path(output_dir, basename).read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError):
|
||||
@ -138,7 +141,7 @@ def read_quality(output_dir, basename):
|
||||
|
||||
|
||||
def load_quality_table(output_dir):
|
||||
"""Tous les sidecars valides : {basename: sidecar}."""
|
||||
"""All valid sidecars: {basename: sidecar}."""
|
||||
folder = Path(output_dir) / QUALITY_DIRNAME
|
||||
table = {}
|
||||
if not folder.is_dir():
|
||||
@ -154,7 +157,7 @@ _LAZ_EXTS = (".copc.laz", ".copc.las", ".laz", ".las")
|
||||
|
||||
|
||||
def _laz_basename(path):
|
||||
"""Nom de dalle sans extension LiDAR (.copc.laz compris)."""
|
||||
"""Tile name without its LiDAR extension (.copc.laz included)."""
|
||||
name = Path(path).name
|
||||
for ext in _LAZ_EXTS:
|
||||
if name.lower().endswith(ext):
|
||||
@ -163,7 +166,7 @@ def _laz_basename(path):
|
||||
|
||||
|
||||
def _cell_bounds_of(basename):
|
||||
"""Emprise nominale 1 km d'une dalle LHD, ou None hors motif LHD."""
|
||||
"""Nominal 1 km footprint of an LHD tile, or None outside the LHD pattern."""
|
||||
from .index import parse_basename_coords
|
||||
coords = parse_basename_coords(basename)
|
||||
if coords is None:
|
||||
@ -173,9 +176,9 @@ def _cell_bounds_of(basename):
|
||||
|
||||
|
||||
def quality_from_las(las_path, bounds, codes=None):
|
||||
"""Qualité mesurée depuis un LAS/LAZ ; None en cas d'échec (jamais d'exception).
|
||||
"""Quality measured from a LAS/LAZ; None on failure (never raises).
|
||||
|
||||
codes : classes à retenir (retours ≥ 1) ; None = fichier déjà filtré sol.
|
||||
codes: classes to keep (returns ≥ 1); None = file already filtered to ground.
|
||||
"""
|
||||
try:
|
||||
import laspy
|
||||
@ -195,16 +198,16 @@ def quality_from_las(las_path, bounds, codes=None):
|
||||
return compute_quality(np.asarray(las.x)[keep], np.asarray(las.y)[keep], gps,
|
||||
bounds, gps_adjusted=adjusted,
|
||||
header_date=created.isoformat() if created else None)
|
||||
except Exception as e: # noqa: BLE001 — qualité best-effort
|
||||
logger.warning(f" Mesure de qualité impossible ({Path(las_path).name}) : {e}")
|
||||
except Exception as e: # noqa: BLE001 — best-effort quality
|
||||
logger.warning(f" Could not measure quality ({Path(las_path).name}): {e}")
|
||||
return None
|
||||
|
||||
|
||||
def ensure_quality(las_path, basename, output_dir, codes=None):
|
||||
"""Garantit le sidecar qualité d'une dalle (calcul si absent ou périmé).
|
||||
"""Ensure a tile's quality sidecar exists (computed if missing or outdated).
|
||||
|
||||
Returns:
|
||||
True si un sidecar valide existe à la sortie.
|
||||
True if a valid sidecar exists on return.
|
||||
"""
|
||||
if read_quality(output_dir, basename) is not None:
|
||||
return True
|
||||
@ -217,16 +220,16 @@ def ensure_quality(las_path, basename, output_dir, codes=None):
|
||||
try:
|
||||
write_quality(output_dir, basename, data)
|
||||
except OSError as e:
|
||||
logger.warning(f" Écriture du sidecar qualité impossible : {e}")
|
||||
logger.warning(f" Could not write the quality sidecar: {e}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def backfill_quality(input_dir, output_dir, codes=(2,)):
|
||||
"""Rattrapage : sidecar qualité de chaque LAZ LHD d'input_dir qui n'en a pas.
|
||||
"""Backfill: quality sidecar for every LHD LAZ in input_dir that lacks one.
|
||||
|
||||
Returns:
|
||||
Nombre de sidecars écrits.
|
||||
Number of sidecars written.
|
||||
"""
|
||||
files = sorted(p for p in Path(input_dir).iterdir()
|
||||
if p.is_file() and p.name.lower().endswith(_LAZ_EXTS))
|
||||
@ -235,7 +238,7 @@ def backfill_quality(input_dir, output_dir, codes=(2,)):
|
||||
base = _laz_basename(laz)
|
||||
if read_quality(output_dir, base) is not None or _cell_bounds_of(base) is None:
|
||||
continue
|
||||
logger.info(f"Qualité [{i}/{len(files)}] {base}")
|
||||
logger.info(f"Quality [{i}/{len(files)}] {base}")
|
||||
if ensure_quality(laz, base, output_dir, codes):
|
||||
written += 1
|
||||
return written
|
||||
|
||||
Reference in New Issue
Block a user