82 lines
3.1 KiB
Python
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)
|