Files
lidar_rendu/lidar_pipeline/export_pdf.py
2026-09-27 15:35:21 +02:00

120 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Export PDF d'une zone : planche d'impression terrain du relief orienté.
Composée directement en Lambert 93 depuis les sources de la pyramide
(tiles.py) — échelle exacte, quadrillage aligné sur les dalles — puis
dessinée avec reportlab (texte, grille et légende vectoriels). Tourne dans
l'image légère : Pillow + pyproj + reportlab, sans numpy.
"""
import logging
import math
from dataclasses import dataclass
logger = logging.getLogger("lidar")
LAYER = "relief_oriente"
PAPERS_MM = {"A4": (210.0, 297.0), "A3": (297.0, 420.0)}
ORIENTS = ("portrait", "paysage")
SCALES = (1000, 2000, 5000, 10000)
DPI = {"A4": 300, "A3": 250} # borne la mémoire du Pi (~36 Mo en A3)
NATIVE_RES_M = 0.2
MARGIN_MM = 10.0 # bord non imprimable
ANNOT_MM = 7.0 # bande des coordonnées autour de la carte
PANEL_SIDE_MM = 64.0 # bandeau à droite (paysage)
PANEL_BOTTOM_MM = 72.0 # bandeau en bas (portrait)
_GRID_STEPS = {1000: 100, 2000: 100, 5000: 500, 10000: 1000}
@dataclass(frozen=True)
class Layout:
"""Géométrie de la planche, en mm, origine en bas à gauche (reportlab)."""
paper: str
orient: str
scale: int
dpi: int
page_w: float
page_h: float
map_x: float
map_y: float
map_w: float
map_h: float
panel_x: float
panel_y: float
panel_w: float
panel_h: float
def layout(paper, orient, scale):
"""Géométrie d'une planche ; ValueError si un réglage est invalide."""
if paper not in PAPERS_MM:
raise ValueError(f"format inconnu : {paper} (A4 ou A3)")
if orient not in ORIENTS:
raise ValueError(f"orientation inconnue : {orient} (portrait ou paysage)")
try:
scale = int(scale)
except (TypeError, ValueError):
raise ValueError(f"échelle invalide : {scale}") from None
if scale not in SCALES:
raise ValueError("échelle non proposée : 1:" + str(scale)
+ " (1:1000, 1:2000, 1:5000 ou 1:10000)")
w, h = PAPERS_MM[paper]
if orient == "paysage":
w, h = h, w
inner = MARGIN_MM + ANNOT_MM
if orient == "paysage":
map_w = w - 2 * inner - PANEL_SIDE_MM
map_h = h - 2 * inner
return Layout(paper, orient, scale, DPI[paper], w, h, inner, inner, map_w, map_h,
w - MARGIN_MM - PANEL_SIDE_MM, MARGIN_MM, PANEL_SIDE_MM, h - 2 * MARGIN_MM)
map_w = w - 2 * inner
map_h = h - 2 * inner - PANEL_BOTTOM_MM
return Layout(paper, orient, scale, DPI[paper], w, h, inner, MARGIN_MM + PANEL_BOTTOM_MM + ANNOT_MM,
map_w, map_h, MARGIN_MM, MARGIN_MM, w - 2 * MARGIN_MM, PANEL_BOTTOM_MM)
def center_l93(lat, lon):
"""Centre WGS84 → Lambert 93 ; ValueError si non fini."""
from .tiles import wgs84_to_l93
lat, lon = float(lat), float(lon)
if not (math.isfinite(lat) and math.isfinite(lon)):
raise ValueError("coordonnées du centre invalides")
cx, cy = wgs84_to_l93(lon, lat)
if not (math.isfinite(cx) and math.isfinite(cy)):
raise ValueError("centre hors du domaine Lambert 93")
return cx, cy
def map_bbox(cx, cy, lay):
"""Emprise terrain L93 de la zone carte (papier × échelle)."""
half_w = lay.map_w / 1000.0 * lay.scale / 2
half_h = lay.map_h / 1000.0 * lay.scale / 2
return (cx - half_w, cy - half_h, cx + half_w, cy + half_h)
def pixel_size(lay):
"""Taille terrain d'un pixel imprimé (m), jamais plus fine que le natif."""
return max(NATIVE_RES_M, lay.scale * 0.0254 / lay.dpi)
def grid_step(scale):
"""Pas du quadrillage L93 (m) selon l'échelle."""
return _GRID_STEPS[int(scale)]
def _to_wgs84(x, y):
from .tiles import _transformer
lon, lat = _transformer("EPSG:2154", "EPSG:4326").transform(x, y)
return lat, lon
def frame(lat, lon, paper, orient, scale):
"""Cadre imprimable pour la carte : emprise L93 et coins WGS84 (NO, NE, SE, SO)."""
lay = layout(paper, orient, scale)
cx, cy = center_l93(lat, lon)
b = map_bbox(cx, cy, lay)
corners = [_to_wgs84(b[0], b[3]), _to_wgs84(b[2], b[3]),
_to_wgs84(b[2], b[1]), _to_wgs84(b[0], b[1])]
return {"cx": cx, "cy": cy, "bbox_l93": list(b),
"corners": [[round(a, 7), round(o, 7)] for a, o in corners],
"width_m": round(b[2] - b[0]), "height_m": round(b[3] - b[1])}