From 68411a0ce9f055965b63ff121a596786668c98ee Mon Sep 17 00:00:00 2001 From: karim Date: Wed, 20 May 2026 13:24:25 +0200 Subject: [PATCH] swissALTI3D Multi-Tile-Naht: Sub-Sampling global statt tile-lokal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: Jedes Tile berechnete origin_e/origin_n aus seinem eigenen Sample- Punkt-Set und filterte Sub-Sampling-Punkte modulo factor_e relativ dazu. Wenn das File-Ordering tile-individuell andere ersten 200 Punkte lieferte, landete jedes Tile auf einer leicht anderen Phase im 0.5m-Raster — am Tile-Boundary fehlten Faces / das Mesh hatte sichtbare Naht. Fix: Phase aus dem ersten Sample-Punkt detect (e_phase = e mod raw_step). Sub-Sampling-Filter benutzt den GLOBALEN LV95-Raster-Index `round((e - e_phase) / raw_e_step)`. Da swissALTI3D ein globales Raster ist, hat jedes Tile dieselbe Phase → konsistente Punkte am Boundary. Co-Authored-By: Claude Opus 4.7 --- rhino/swisstopo.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/rhino/swisstopo.py b/rhino/swisstopo.py index 43ea645..df91b5f 100644 --- a/rhino/swisstopo.py +++ b/rhino/swisstopo.py @@ -13,6 +13,7 @@ Collections die wir nutzen: import os import re import json +import math import zipfile import urllib.request import urllib.parse @@ -590,7 +591,7 @@ def xyz_to_grid(path, target_step=2.0, clip_bbox=None, progress=None): try: float(first[0]) except Exception: start_idx = 1 - # --- 1. Pass: raw Step + Origin aus ersten ~200 Punkten erkennen + # --- 1. Pass: raw Step aus ersten ~200 Punkten erkennen sample = [] for ln in lines[start_idx:start_idx + 500]: parts = ln.split() @@ -612,18 +613,25 @@ def xyz_to_grid(path, target_step=2.0, clip_bbox=None, progress=None): for i in range(len(sample) - 1) if abs(sample[i+1][1] - sample[i][1]) > 0.001}) raw_n_step = n_diffs[0] if n_diffs else 0.5 - origin_e = min(p[0] for p in sample) - origin_n = min(p[1] for p in sample) # Sub-Sampling-Faktoren — nur ganzzahlig damit das Raster regulaer bleibt factor_e = max(1, int(round(target_step / raw_e_step))) factor_n = max(1, int(round(target_step / raw_n_step))) actual_step_e = raw_e_step * factor_e actual_step_n = raw_n_step * factor_n + # Phase relativ zum globalen LV95-Raster: swissALTI3D 0.5m liegt z.B. + # auf .25-Marken (cell-center). Phase aus erstem Sample-Punkt ermitteln — + # gilt global fuer alle Tiles, da das Raster LV95-aligned ist. + e_phase = sample[0][0] - math.floor(sample[0][0] / raw_e_step) * raw_e_step + n_phase = sample[0][1] - math.floor(sample[0][1] / raw_n_step) * raw_n_step if progress: - progress("XYZ raw {:.2f}m → target {:.2f}m → sub-sample {}x{} ({:.2f}m actual)".format( - raw_e_step, target_step, factor_e, factor_n, actual_step_e)) + progress("XYZ raw {:.2f}m → target {:.2f}m → sub-sample {}x{} ({:.2f}m actual, phase {:.2f}/{:.2f})".format( + raw_e_step, target_step, factor_e, factor_n, actual_step_e, e_phase, n_phase)) # --- 2. Pass: alle Punkte auf dem Sub-Raster behalten (+ optional clip) + # WICHTIG: Sub-Sampling-Filter benutzt den GLOBALEN LV95-Raster-Index + # (mit detected phase) statt eines tile-lokalen origin. Sonst waehlt + # jedes Tile seine eigene Phase und am Tile-Boundary fehlen Faces / + # das Mesh ist nicht durchgehend. points = {} es = set(); ns = set() cb = clip_bbox @@ -635,13 +643,15 @@ def xyz_to_grid(path, target_step=2.0, clip_bbox=None, progress=None): except Exception: continue if cb is not None: if e < cb[0] or e > cb[2] or n < cb[1] or n > cb[3]: continue - # Raster-Pruefung: nur jeden factor_e-ten E-Schritt + factor_n-ten N-Schritt - di = int(round((e - origin_e) / raw_e_step)) - dj = int(round((n - origin_n) / raw_n_step)) - if di % factor_e != 0 or dj % factor_n != 0: continue - # Auf snapped Koords runden um Float-Drift zu vermeiden - e_snap = origin_e + di * raw_e_step - n_snap = origin_n + dj * raw_n_step + # Globaler Index im swissALTI3D-Raster: alle Tiles teilen Phase + gi = int(round((e - e_phase) / raw_e_step)) + gj = int(round((n - n_phase) / raw_n_step)) + if gi % factor_e != 0 or gj % factor_n != 0: continue + # Originale Koordinaten als Key (Tile A und Tile B teilen Phase, + # also matchen ihre Keys am Boundary direkt). round(3) gegen + # Float-Drift. + e_snap = round(e, 3) + n_snap = round(n, 3) points[(e_snap, n_snap)] = z es.add(e_snap); ns.add(n_snap)