#!/usr/bin/env python3 """Task 3 (HITL draft): main-line centreline station table from a plan sheet. Method (layers CA-ALGN-TEXT labels and CA-ALGN-STSM station ticks): 1. Station labels: 'N+mmm' is explicit; bare numbers take the km of the next explicit label when they precede it (700 before 5+000 -> 4+700), otherwise the km of the previous explicit label. 2. Each station has two labels (one per side of the road). Each label is paired with its nearest tick; the centreline point is the midpoint of the two tick points. 3. Checks reported (not forced): side offset per station, and consecutive centreline distance vs station difference (residual). DRAFT for HITL review. No value is adjusted to fit.""" import csv import math import re import sys from collections import defaultdict import ezdxf SRC, OUT = sys.argv[1], sys.argv[2] msp = ezdxf.readfile(SRC).modelspace() explicit_re = re.compile(r"^(\d+)\+(\d{3})$") labels = [(e.dxf.insert.x, e.dxf.insert.y, e.dxf.text.strip()) for e in msp.query("TEXT") if e.dxf.layer == "CA-ALGN-TEXT"] ticks = [] for e in msp.query("LINE"): if e.dxf.layer == "CA-ALGN-STSM": a, b = e.dxf.start, e.dxf.end ticks.append(((a.x + b.x) / 2, (a.y + b.y) / 2)) labels = [l for l in labels if explicit_re.match(l[2]) or l[2].isdigit()] labels.sort(key=lambda t: -t[1]) # along the sheet, NNE -> SSW explicit_idx = [i for i, l in enumerate(labels) if explicit_re.match(l[2])] station_of = {} for i, (x, y, s) in enumerate(labels): if explicit_re.match(s): m = explicit_re.match(s) station_of[i] = int(m.group(1)) * 1000 + int(m.group(2)) continue v = int(s) nxt = next((j for j in explicit_idx if j > i), None) prv = next((j for j in reversed(explicit_idx) if j < i), None) if prv is not None: km = int(explicit_re.match(labels[prv][2]).group(1)) else: km = int(explicit_re.match(labels[nxt][2]).group(1)) - 1 station_of[i] = km * 1000 + v groups = defaultdict(list) for i, (x, y, s) in enumerate(labels): groups[station_of[i]].append((x, y)) def nearest_tick(pt): return min(ticks, key=lambda t: math.hypot(t[0] - pt[0], t[1] - pt[1])) centre = [] for sta in sorted(groups): pts = groups[sta] if len(pts) != 2: continue t1, t2 = nearest_tick(pts[0]), nearest_tick(pts[1]) side_gap = math.dist(t1, t2) mid = ((t1[0] + t2[0]) / 2, (t1[1] + t2[1]) / 2) centre.append((sta, mid[0], mid[1], side_gap)) with open(OUT, "w", newline="", encoding="utf-8") as fh: w = csv.writer(fh) w.writerow(["station_m", "centre_x", "centre_y", "side_gap_m"]) for sta, x, y, g in centre: w.writerow([sta, round(x, 3), round(y, 3), round(g, 3)]) print(f"stations={len(centre)} (labels={len(labels)}, ticks={len(ticks)})") prev = None for sta, x, y, g in centre: step = "" if prev is None else f" d={math.dist(prev[1:3], (x, y)):8.3f} dSTA={sta - prev[0]:5d} res={math.dist(prev[1:3], (x, y)) - (sta - prev[0]):+.3f}" print(f" STA {sta:>5} ({x:12.3f}, {y:12.3f}) side_gap={g:6.2f}{step}") prev = (sta, x, y)