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:
Antoine
2026-09-27 23:16:45 +02:00
parent cc1c22d2b8
commit fb892ea9f2
52 changed files with 4356 additions and 4323 deletions

View File

@ -1,4 +1,4 @@
"""Tests de l'export PDF (planche d'impression terrain)."""
"""Tests for the PDF export (printable field sheet)."""
import pytest
@ -8,7 +8,7 @@ def test_layout_landscape_a4_dimensions():
lay = layout("A4", "paysage", 2000)
assert (lay.page_w, lay.page_h) == (297.0, 210.0) and lay.dpi == 300
assert lay.map_w > 150 and lay.map_h > 150
assert lay.panel_x >= lay.map_x + lay.map_w # bandeau à droite
assert lay.panel_x >= lay.map_x + lay.map_w # panel on the right
assert lay.map_x + lay.map_w <= lay.page_w and lay.map_y + lay.map_h <= lay.page_h
@ -16,7 +16,7 @@ def test_layout_portrait_a3_panel_below():
from lidar_pipeline.export_pdf import layout
lay = layout("A3", "portrait", 5000)
assert (lay.page_w, lay.page_h) == (297.0, 420.0) and lay.dpi == 250
assert lay.panel_y + lay.panel_h <= lay.map_y # bandeau en bas
assert lay.panel_y + lay.panel_h <= lay.map_y # panel at the bottom
@pytest.mark.parametrize("args", [("A5", "paysage", 2000), ("A4", "biais", 2000),
@ -38,7 +38,7 @@ def test_map_bbox_matches_paper_times_scale():
def test_pixel_size_and_grid_step():
from lidar_pipeline.export_pdf import grid_step, layout, pixel_size
assert pixel_size(layout("A4", "paysage", 1000)) == 0.2 # plafonné au natif
assert pixel_size(layout("A4", "paysage", 1000)) == 0.2 # capped at the native resolution
assert abs(pixel_size(layout("A3", "paysage", 10000)) - 10000 * 0.0254 / 250) < 1e-9
assert [grid_step(s) for s in (1000, 2000, 5000, 10000)] == [100, 100, 500, 1000]
@ -57,7 +57,7 @@ def test_frame_roundtrip():
assert abs(f["cx"] - cx) < 1e-6 and abs(f["cy"] - cy) < 1e-6
assert len(f["corners"]) == 4
nw, ne, se, sw = f["corners"]
assert nw[0] > sw[0] and ne[1] > nw[1] # [lat, lon] : nord en haut, est à droite
assert nw[0] > sw[0] and ne[1] > nw[1] # [lat, lon]: north at the top, east on the right
assert f["width_m"] > f["height_m"] > 0
@ -99,11 +99,11 @@ def test_compose_l93_half_outside_is_white_hatched(tmp_path, monkeypatch):
from lidar_pipeline.export_pdf import compose_l93
monkeypatch.setattr(index, "PANEL_VIZ", None)
_dalle(tmp_path, 1054, 6882)
# moitié ouest dans la dalle, moitié est hors données
# western half inside the tile, eastern half outside the data
img, mask, cells = compose_l93(tmp_path, (1054900.0, 6881400.0, 1055100.0, 6881500.0), 1.0)
assert mask.getpixel((50, 50)) == 255 and mask.getpixel((150, 50)) == 0
east = img.crop((110, 0, 200, 100)).convert("L").getextrema()
assert east[1] == 255 and east[0] < 255 # blanc + hachures
assert east[1] == 255 and east[0] < 255 # white + hatching
def test_compose_l93_nodata_pixels_masked(tmp_path, monkeypatch):
@ -145,12 +145,13 @@ def test_density_color_classes():
def test_pdf_text_replaces_unencodable():
from lidar_pipeline.export_pdf import _pdf_text
assert _pdf_text("Relief orienté — 1:2 000 ≥ 🚀") == "Relief orienté — 1:2 000 ? ?"
# é and — exist in cp1252; ≥ and the emoji do not
assert _pdf_text("Café — 1:2,000 ≥ 🚀") == "Café — 1:2,000 ? ?"
def test_pdf_legend_uses_reading_text(monkeypatch):
"""La légende de la planche PDF affiche le texte « Comment lire » de
VIZ_LEGENDS, habillé à la largeur de la boîte, plutôt que « legend »."""
"""The PDF sheet legend shows the "How to read" text of VIZ_LEGENDS,
wrapped to the box width, rather than "legend"."""
from lidar_pipeline import export_pdf
from lidar_pipeline.index import VIZ_LEGENDS
drawn = []
@ -177,13 +178,13 @@ def test_zone_quality_aggregates_two_cells():
from lidar_pipeline.export_pdf import zone_quality
table = {"LHD_FXX_1054_6882_PTS_LAMB93_IGN69": _q(4.0, "2023-03-15", "2023-03-15"),
"LHD_FXX_1055_6882_PTS_LAMB93_IGN69": _q(8.0, "2023-04-02", "2023-04-03", 0.3)}
bbox = (1054500.0, 6881500.0, 1055500.0, 6881600.0) # moitié de chaque dalle
bbox = (1054500.0, 6881500.0, 1055500.0, 6881600.0) # half of each tile
z = zone_quality(bbox, table, [(1054, 6882), (1055, 6882)])
assert abs(z["density_mean"] - 6.0) < 1e-6 and z["density_min"] == 4.0
assert abs(z["empty_fraction"] - 0.2) < 1e-6
assert (z["acq_start"], z["acq_end"]) == ("2023-03-15", "2023-04-03")
assert z["missing_relief"] == [] and z["missing_quality"] == []
assert len(z["grid_cells"]) == 2 * 10 * 2 # 10 mailles en x × 2 en y par dalle
assert len(z["grid_cells"]) == 2 * 10 * 2 # 10 cells in x × 2 in y per tile
def test_zone_quality_missing_cells():
@ -216,12 +217,13 @@ def test_build_pdf_a4_landscape(tmp_path, monkeypatch):
write_quality(tmp_path, "LHD_FXX_1054_6882_PTS_LAMB93_IGN69", _q(6.5))
lat, lon = _cell_center_wgs84(1054, 6882)
pdf, name = build_pdf(tmp_path, lat, lon, "A4", "paysage", 2000,
title="Prospection 🚀 bois", compress=False)
title="Survey 🚀 woods", compress=False)
assert pdf.startswith(b"%PDF")
w, h = _mediabox(pdf)
assert abs(w - 841.89) < 0.5 and abs(h - 595.28) < 0.5
assert name.startswith("relief_1054.") and name.endswith("_1-2000.pdf")
for s in (b"Prospection ? bois", b"1:2 000", b"Densit", b"2023-03-15", b"LiDAR HD", b"6,5 pts"):
for s in (b"Survey ? woods", b"1:2,000", b"ground density", b"2023-03-15", b"LiDAR HD",
b"6.5 pts", b"landscape"):
assert s in pdf, s
@ -237,7 +239,7 @@ def test_build_pdf_a3_portrait_size(tmp_path, monkeypatch):
def test_build_pdf_partial_zone_hatched(tmp_path, monkeypatch):
"""Zone à cheval sur le bord des données : planche produite, dalle absente listée."""
"""Area straddling the data edge: sheet produced, missing tile listed."""
from lidar_pipeline import index
from lidar_pipeline.export_pdf import build_pdf
from lidar_pipeline.tiles import _transformer
@ -246,7 +248,7 @@ def test_build_pdf_partial_zone_hatched(tmp_path, monkeypatch):
lon, lat = _transformer("EPSG:2154", "EPSG:4326").transform(1055000.0, 6881500.0)
pdf, _ = build_pdf(tmp_path, lat, lon, "A4", "paysage", 2000, compress=False)
assert pdf.startswith(b"%PDF") and b"1055_6882" in pdf
assert b"Donn" in pdf and b"manquante" in pdf
assert b"Missing data" in pdf and b"no relief" in pdf # "(" is escaped in PDF strings
def test_build_pdf_no_data_raises(tmp_path):
@ -258,13 +260,13 @@ def test_build_pdf_no_data_raises(tmp_path):
def test_fmt_int():
from lidar_pipeline.export_pdf import _fmt_int
assert _fmt_int(2000) == "2 000" and _fmt_int(500) == "500"
assert _fmt_int(2000) == "2,000" and _fmt_int(500) == "500"
def test_north_arrow_angle_matches_pyproj_reference():
"""L'angle de rotation reportlab doit amener la flèche (dessinée vers le
nord du quadrillage) sur le nord géographique, dans le bon sens, à l'est
et à l'ouest du méridien central (3°E)."""
"""The reportlab rotation angle must bring the arrow (drawn towards grid
north) onto true north, in the right direction, east and west of the
central meridian (3°E)."""
import math
from lidar_pipeline.export_pdf import _north_arrow_angle, _to_wgs84
from lidar_pipeline.tiles import _transformer, wgs84_to_l93
@ -275,12 +277,12 @@ def test_north_arrow_angle_matches_pyproj_reference():
lat0, lon0 = _to_wgs84(cx, cy)
x0, y0 = to_l93.transform(lon0, lat0)
x1, y1 = to_l93.transform(lon0, lat0 + 0.001)
# azimut (sens horaire depuis le nord du quadrillage) du nord géographique
# azimuth of true north (clockwise from grid north)
psi = math.degrees(math.atan2(x1 - x0, y1 - y0))
return -psi # rotation reportlab (antihoraire) amenant "haut" sur ce nord
return -psi # reportlab rotation (counter-clockwise) bringing "up" onto that north
east = wgs84_to_l93(6.0, 46.0) # à l'est du méridien central
west = wgs84_to_l93(0.0, 46.0) # à l'ouest du méridien central
east = wgs84_to_l93(6.0, 46.0) # east of the central meridian
west = wgs84_to_l93(0.0, 46.0) # west of the central meridian
for cx, cy in (east, west):
assert abs(_north_arrow_angle(cx, cy) - _reference_angle(cx, cy)) < 0.05
assert _north_arrow_angle(*east) > 0
@ -293,7 +295,7 @@ def test_wrap_text_lines_fit_width():
from io import BytesIO
from lidar_pipeline.export_pdf import _wrap_text
c = rl_canvas.Canvas(BytesIO())
text = "Donnée manquante (sans relief) : " + ", ".join(
text = "Missing data (no relief): " + ", ".join(
f"{1050 + i}_6882" for i in range(12))
max_width = 60 * mm
lines = _wrap_text(c, text, "Helvetica", 5.8, max_width, sep=", ")
@ -308,22 +310,22 @@ def test_fit_title_shrinks_then_elides():
from io import BytesIO
from lidar_pipeline.export_pdf import _fit_title
c = rl_canvas.Canvas(BytesIO())
long_title = "Relief orienté - " + ", ".join(f"{1050 + i:04d}_6882" for i in range(6))
long_title = "Oriented relief - " + ", ".join(f"{1050 + i:04d}_6882" for i in range(6))
text, size = _fit_title(c, long_title, "Helvetica-Bold", 10, 60 * mm, min_size=7.0)
assert size >= 7.0
assert c.stringWidth(text, "Helvetica-Bold", size) <= 60 * mm + 1e-6
def test_cartouche_title_avoids_north_arrow_column():
"""Le titre (et le sous-titre) de la cartouche ne doit jamais empiéter sur
la colonne réservée à la flèche du nord, en paysage comme en portrait."""
"""The title block's title (and subtitle) must never encroach on the
column reserved for the north arrow, in landscape as in portrait."""
from reportlab.pdfgen import canvas as rl_canvas
from reportlab.lib.units import mm
from io import BytesIO
from lidar_pipeline.export_pdf import (
_cartouche_text_max_width, _fit_title, _panel_boxes, layout)
c = rl_canvas.Canvas(BytesIO())
long_title = "Relief orienté - " + ", ".join(f"{1050 + i:04d}_6882" for i in range(6))
long_title = "Oriented relief - " + ", ".join(f"{1050 + i:04d}_6882" for i in range(6))
for paper, orient, scale in (("A4", "paysage", 2000), ("A4", "portrait", 10000),
("A3", "portrait", 2000)):
lay = layout(paper, orient, scale)
@ -332,5 +334,5 @@ def test_cartouche_title_avoids_north_arrow_column():
max_w = _cartouche_text_max_width(w, mm)
txt, size = _fit_title(c, long_title, "Helvetica-Bold", 10, max_w)
title_right_mm = x + c.stringWidth(txt, "Helvetica-Bold", size) / mm
arrow_left_mm = (x + w - 8) - 1.8 # bord gauche du triangle de la flèche
arrow_left_mm = (x + w - 8) - 1.8 # left edge of the arrow triangle
assert title_right_mm <= arrow_left_mm, (paper, orient, scale)