Files
CMPlanning/3d-drawing/scripts/extract_alignment_plan.py

82 lines
3.1 KiB
Python

#!/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)