| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- # -*- coding: utf-8 -*-
- """
- Лаба 2, рисунок «Продольный профиль трассы» (схематический).
- Масштабы: горизонтальный 1:5000, вертикальный 1:500 (лист 420x297).
- Данные (горизонтали, трасса, уклоны, тоннель) берутся из .work/laba2-plan-data.pkl.
- """
- import numpy as np, pickle
- with open('.work/laba2-plan-data.pkl', 'rb') as f:
- D = pickle.load(f)
- crossings, GRADES = D['crossings'], D['GRADES']
- S_P1, S_P2, L_total = D['S_P1'], D['S_P2'], D['L_total']
- S_A_Z, S_B_Z = D['S_A_Z'], D['S_B_Z']
- def ground(s):
- cs = [0.0] + [c[0] for c in crossings] + [L_total]
- zs = [S_A_Z] + [c[1] for c in crossings] + [S_B_Z]
- return float(np.interp(s, cs, zs))
- def red_h(s):
- h = 250.0
- for (s1, i1), (s2, _i2) in zip(GRADES, GRADES[1:]):
- if s1 <= s < s2:
- return h + (s - s1) * i1 / 1000.0
- h += (s2 - s1) * i1 / 1000.0
- return h + (s - GRADES[-1][0]) * GRADES[-1][1] / 1000.0
- # --- вертикальные кривые на переломах проектной линии ---------------------------
- # радиусы не менее нормативных для IV категории: выпуклые 5000 м, вогнутые 2000 м
- R_CONVEX, R_CONCAVE = 5000.0, 2000.0
- VC = {} # s перелома -> (T, R)
- for k, (s1, i1) in enumerate(GRADES[:-1]):
- s2, i2 = GRADES[k + 1]
- if i1 == i2:
- continue
- R = R_CONVEX if i1 > i2 else R_CONCAVE
- T = abs(i2 - i1) / 1000.0 * R / 2.0
- T = min(T, 0.4 * (s2 - s1), 0.4 * (L_total - s2))
- VC[s2] = (T, R)
- def red_smooth(s):
- """Проектная отметка с параболическим сопряжением на вертикальных кривых."""
- h = red_h(s)
- for sb, (T, _R) in VC.items():
- if T <= 0 or abs(s - sb) >= T:
- continue
- k = max(k for k, (s1, _i) in enumerate(GRADES) if s1 < sb)
- i1, i2 = GRADES[k][1], GRADES[k + 1][1]
- h += (i2 - i1) / 1000.0 * ((s - sb) + T) ** 2 / (4.0 * T)
- return h
- align, s_m, curves = D['align'], D['s_m'], D['curves']
- RED = '#cc1f2d'
- # --- геометрия листа (мм), y вверх ------------------------------------------
- W_SH, H_SH = 357.0, 247.0
- X0 = 40.0 # начало ординат
- def X(s): return X0 + s / 5.0 # 1:5000
- H0 = 246.0 # отметка низа профильной зоны
- Y0 = 95.0 # y листа для H0
- def Y(h): return Y0 + (h - H0) * 2.0 # 1:500
- # строки сетки (снизу вверх): (имя, высота мм)
- ROWS = [('Километры', 7.5), ('Пикеты', 7.5), ('Расстояния', 9.0),
- ('Отметки земли', 9.5), ('Проект. отметки', 9.5),
- ('Уклоны, ‰', 11.0), ('План трассы', 12.0)]
- Y_GRID_BOT, Y_GRID_TOP = 15.0, 15.0 + sum(h for _, h in ROWS)
- fig = plt = None
- import matplotlib
- matplotlib.use('Agg')
- import matplotlib.pyplot as plt
- # Чертёжный шрифт ГОСТ 2.304-81 тип А (прямой, из системных шрифтов Windows)
- import matplotlib.font_manager as fm
- fm.fontManager.addfont('C:/Windows/Fonts/GOST2304A.ttf')
- matplotlib.rcParams['font.family'] = 'GOST 2.304 type A'
- matplotlib.rcParams['mathtext.default'] = 'regular'
- fig = plt.figure(figsize=(W_SH / 25.4, H_SH / 25.4), dpi=250)
- ax = fig.add_axes([0, 0, 1, 1])
- ax.set_xlim(0, W_SH); ax.set_ylim(0, H_SH)
- ax.axis('off')
- FS, FSS = 10.6, 9.6 # кегли, pt (2.83 pt = 1 мм)
- # --- заголовок ---------------------------------------------------------------
- ax.text(W_SH / 2, H_SH - 11, 'ПРОФИЛЬ ТРАССЫ АВТОМОБИЛЬНОЙ ДОРОГИ А–Б',
- fontsize=13.5, weight='normal', ha='center', va='center')
- ax.text(W_SH / 2, H_SH - 18.5, 'Масштабы: горизонтальный 1:5000, вертикальный 1:500 (схематический)',
- fontsize=FS, ha='center', va='center', color='0.25')
- # --- сетка -------------------------------------------------------------------
- y = Y_GRID_BOT
- bounds = [y]
- for name, h in ROWS:
- y += h
- bounds.append(y)
- # горизонтальные линии сетки: верхняя и внутренние жирнее
- X_R, X_L = X(L_total) + 14.0, X0 - 3.0
- for i, b in enumerate(bounds):
- lw = 0.9 if i in (0, len(bounds) - 1) else 0.45
- ax.plot([X_L, X_R], [b, b], color='black', lw=lw)
- ax.plot([X_L, X_L], [bounds[0], bounds[-1]], color='black', lw=0.9)
- ax.plot([X_R, X_R], [bounds[0], bounds[-1]], color='black', lw=0.9)
- # названия строк — слева
- for (name, h), b0, b1 in zip(ROWS, bounds, bounds[1:]):
- ax.text(X_L - 0.8, (b0 + b1) / 2, name, fontsize=8.8, ha='right', va='center')
- # вертикальные линии: пикеты (тонкие), километры (толстые), переломы красной
- grade_s = [g[0] for g in GRADES] + [L_total]
- def vline(s, y1, y2, lw=0.35, color='black'):
- ax.plot([X(s), X(s)], [y1, y2], color=color, lw=lw)
- for pk in range(0, int(L_total // 100) + 1):
- s = pk * 100.0
- if s > L_total: break
- vline(s, Y_GRID_TOP, bounds[0]) # ордината сквозь сетку
- vline(0.0, Y0, bounds[0], lw=0.9)
- vline(1000.0, Y_GRID_TOP, bounds[0], lw=0.9)
- vline(L_total, Y_GRID_TOP, bounds[0], lw=0.9)
- # отметки по левой шкале
- for h in range(245, 311, 5):
- ax.plot([X_L - 1.2, X_L], [Y(h), Y(h)], color='black', lw=0.5)
- ax.text(X_L - 1.8, Y(h), str(h), fontsize=8.6, ha='right', va='center')
- # горизонтальные сетки профильной зоны (каждые 5 м)
- for h in range(245, 311, 5):
- lw = 0.55 if h % 10 == 0 else 0.25
- ax.plot([X0, X_R], [Y(h), Y(h)], color='0.55', lw=lw, zorder=0)
- # --- линия поверхности земли --------------------------------------------------
- ss = np.linspace(0, L_total, 400)
- gs = [ground(s) for s in ss]
- ax.plot([X(s) for s in ss], [Y(g) for g in gs], color='black', lw=1.0, zorder=3)
- # фактические отметки на пикетах
- for pk in range(0, 15):
- s = pk * 100.0
- if s > L_total: break
- g = ground(s)
- ha = 'left' if s == 0 else 'center'
- ax.text(X(s) + (1.0 if s == 0 else 0), (bounds[3] + bounds[4]) / 2, f'{g:.1f}'.replace('.', ','),
- fontsize=FSS, ha=ha, va='center', zorder=5)
- # --- проектная (красная) линия ------------------------------------------------
- rr = [red_smooth(s) for s in ss]
- ax.plot([X(s) for s in ss], [Y(h) for h in rr], color=RED, lw=2.0, zorder=4)
- breaks = [0.0, 400.0, 700.0, 1060.0, 1265.0, L_total]
- def cm(v):
- return f'{v:.2f}'.replace('.', ',')
- pk_even = [pk * 100.0 for pk in range(0, 15) if pk % 2 == 0 and pk * 100 <= L_total]
- # проектные отметки (красные): переломы + чётные пикеты; станции ближе 14 мм
- # на листе разносят по ярусам, иначе соседние цифры сливаются
- MIN_GAP_MM = 14.0
- red_stations = sorted(set(breaks) | set(pk_even))
- red_levels, last_x, lvl = [], None, 0
- for s in red_stations:
- x = X(s)
- lvl = 1 - lvl if last_x is not None and x - last_x < MIN_GAP_MM else 0
- red_levels.append(lvl)
- last_x = x
- # над красной линией; белая подложка отделяет цифры от пересекаемых линий
- for s, lvl in zip(red_stations, red_levels):
- ha = 'left' if s == 0 else 'center'
- ax.text(X(s) + (1.0 if s == 0 else 0),
- Y(red_smooth(s)) + (3.4 if s in breaks else 3.0) + 4.2 * lvl, cm(red_smooth(s)),
- fontsize=8.8, color=RED, ha=ha, va='center', zorder=6,
- bbox=dict(boxstyle='round,pad=0.15', fc='white', ec='none'))
- # проектные отметки в строке сетки
- for s, lvl in zip(red_stations, red_levels):
- ha = 'left' if s == 0 else 'center'
- ax.text(X(s) + (1.0 if s == 0 else 0),
- (bounds[4] + bounds[5]) / 2 + 2.4 - 4.8 * lvl, cm(red_smooth(s)),
- fontsize=8.8, color=RED, ha=ha, va='center', zorder=6,
- bbox=dict(boxstyle='round,pad=0.12', fc='white', ec='none'))
- # подписи радиусов вертикальных кривых у переломов проектной линии
- for sb, (T, R) in VC.items():
- ax.text(X(sb) + 1.2, Y(red_h(sb)) - 4.6, f'R{R:.0f}', fontsize=8.2, color=RED,
- ha='left', va='center', zorder=6,
- bbox=dict(boxstyle='round,pad=0.12', fc='white', ec='none'))
- # --- строка уклонов -----------------------------------------------------------
- b0, b1 = bounds[5], bounds[6]
- for (s1, i1), (s2, _i2) in zip(GRADES, GRADES[1:] + [(None, None)]):
- s2e = L_total if s2 is None else s2
- x1, x2 = X(s1), X(s2e)
- ym = (b0 + b1) / 2
- L = x2 - x1
- dy = np.clip(L * np.tan(np.radians(6.0)) * np.sign(i1), -2.6, 2.6)
- ax.annotate('', xy=(x2, ym + dy), xytext=(x1, ym - dy),
- arrowprops=dict(arrowstyle='-|>', color=RED, lw=1.1, mutation_scale=9))
- ax.text((x1 + x2) / 2, ym + 3.6, ('+' if i1 > 0 else '') + f'{i1:g}',
- fontsize=FSS, color=RED, ha='center', va='center', zorder=6,
- bbox=dict(boxstyle='round,pad=0.12', fc='white', ec='none'))
- ax.text((x1 + x2) / 2, ym - 3.4, f'{s2e - s1:.0f}', fontsize=FSS, ha='center',
- va='center', zorder=6, bbox=dict(boxstyle='round,pad=0.12', fc='white', ec='none'))
- # --- строка расстояний (между переломами) -------------------------------------
- b0, b1 = bounds[2], bounds[3]
- for s1, s2 in zip(grade_s, grade_s[1:]):
- ax.text(X((s1 + s2) / 2), (b0 + b1) / 2, f'{s2 - s1:.0f}', fontsize=FSS, ha='center', va='center')
- # --- строка пикетов ------------------------------------------------------------
- b0, b1 = bounds[1], bounds[2]
- for pk in range(0, 15):
- s = pk * 100.0
- if s > L_total: continue
- ax.text(X(s), (b0 + b1) / 2 + 0.8, str(pk), fontsize=FSS, ha='center', va='center',
- bbox=dict(facecolor='white', edgecolor='none', pad=1.2))
- ax.text(X(L_total), (b0 + b1) / 2 + 0.8, '14+43', fontsize=8.6, ha='center', va='center',
- zorder=6, bbox=dict(boxstyle='round,pad=0.12', fc='white', ec='none'))
- # --- километры ------------------------------------------------------------------
- b0, b1 = bounds[0], bounds[1]
- for km, s in ((0, 0.0), (1, 1000.0)):
- ax.text(X(s), (b0 + b1) / 2 + 0.3, str(km), fontsize=FSS, ha='center', va='center',
- bbox=dict(boxstyle='circle,pad=0.18', fc='white', ec='black', lw=0.6))
- # --- план трассы в своей строке ---------------------------------------------------
- b0, b1 = bounds[-2], bounds[-1]
- ym = (b0 + b1) / 2
- ax.plot([X0, X(L_total)], [ym, ym], color='black', lw=0.9)
- def station_of(pt):
- d = np.hypot(align[:, 0] - pt[0], align[:, 1] - pt[1])
- return s_m[int(np.argmin(d))]
- for c in curves:
- sA, sB = station_of(c['A']), station_of(c['B'])
- x1, x2 = X(sA), X(sB)
- hh = min(4.2, c['R_m'] / 100.0)
- t = np.linspace(0, np.pi, 40)
- xs = np.linspace(x1, x2, 40)
- ys = ym + hh * np.sin(t) * (1 if c['right'] else -1)
- ax.plot(xs, ys, color='black', lw=0.9)
- if c['right']: # подпись сбоку от дуги, чтобы не выходить за строку сетки
- ax.text(x2 + 1.5, ym, f"R{c['R_m']:.0f}", fontsize=8.4, ha='left', va='center',
- bbox=dict(facecolor='white', edgecolor='none', pad=1.2))
- else:
- ax.text((x1 + x2) / 2, ym - hh - 1.2, f"R{c['R_m']:.0f}", fontsize=8.4,
- ha='center', va='top',
- bbox=dict(facecolor='white', edgecolor='none', pad=1.2))
- # тоннель в строке плана: прямоугольник
- x1, x2 = X(S_P1), X(S_P2)
- ax.plot([x1, x2, x2, x1, x1], [ym - 3.4, ym - 3.4, ym + 3.4, ym + 3.4, ym - 3.4],
- color='black', lw=0.8)
- ax.text((x1 + x2) / 2 - 1.5, ym, 'тоннель', fontsize=8.0, ha='center', va='center',
- rotation=0, bbox=dict(facecolor='white', edgecolor='none', pad=1.2))
- # --- тоннель на профиле ---------------------------------------------------------
- x1, x2 = X(S_P1), X(S_P2)
- ytop1, ytop2 = Y(red_h(S_P1)), Y(red_h(S_P2))
- dep = 7.0
- ax.plot([x1, x2, x2, x1, x1], [ytop1, ytop2, ytop2 - dep, ytop1 - dep, ytop1],
- color=RED, lw=1.0)
- ax.plot([x1, x1], [ytop1, Y(ground(S_P1))], color=RED, lw=0.9)
- ax.plot([x2, x2], [ytop2, Y(ground(S_P2))], color=RED, lw=0.9)
- ax.text((x1 + x2) / 2, (ytop1 + ytop2) / 2 - dep / 2, 'Тоннель, 205 м',
- fontsize=8.6, color=RED, ha='center', va='center')
- ax.text(x1 - 1.0, Y(ground(S_P1)) + 3.0, 'портал', fontsize=8.0, color='0.3',
- ha='right', va='center')
- ax.text(x2 + 1.0, Y(ground(S_P2)) + 3.0, 'портал', fontsize=8.0, color='0.3',
- ha='left', va='center')
- # рабочие отметки у порталов (глубина выемок)
- for s in (S_P1, S_P2):
- w = ground(s) - red_h(s)
- ax.text(X(s) + (2.4 if s == S_P2 else -2.4), Y(red_h(s)) - 3.2,
- f'{w:.1f}'.replace('.', ','), fontsize=8.6, color='0.25',
- ha='left' if s == S_P2 else 'right', va='center')
- fig.savefig('assets/images/laba2-profil.png', dpi=250, facecolor='white')
- print('сохранено: assets/images/laba2-profil.png')
|