mirror of
https://github.com/Horizontal42/f1-telemetry.git
synced 2026-11-10 16:32:03 +03:00
145 lines
3.8 KiB
Python
145 lines
3.8 KiB
Python
import bisect
|
|
import statistics
|
|
|
|
from .parser import Lap
|
|
from .segments import Corner
|
|
|
|
_DISCRETE = {'Gear', 'DRS', 'ERSMode'}
|
|
|
|
|
|
def auto_steps(lap: Lap) -> tuple[float, float]:
|
|
dist = lap.ch.get('LapDistance', [])
|
|
diffs = [dist[i + 1] - dist[i] for i in range(len(dist) - 1) if dist[i + 1] - dist[i] > 0]
|
|
if not diffs:
|
|
return (4.0, 25.0)
|
|
med = statistics.median(diffs)
|
|
dense = max(round(med, 1), 1.0)
|
|
sparse = max(round(med * 8, 0), 20.0)
|
|
return (dense, sparse)
|
|
|
|
|
|
def adaptive_points(
|
|
lap: Lap,
|
|
corners: list[Corner],
|
|
dense_step: float | None = None,
|
|
sparse_step: float | None = None,
|
|
approach_m: float = 100.0,
|
|
) -> list[float]:
|
|
if dense_step is None or sparse_step is None:
|
|
auto_d, auto_s = auto_steps(lap)
|
|
if dense_step is None:
|
|
dense_step = auto_d
|
|
if sparse_step is None:
|
|
sparse_step = auto_s
|
|
|
|
dist = lap.ch.get('LapDistance', [])
|
|
if not dist:
|
|
return []
|
|
|
|
d_min = dist[0]
|
|
d_max = dist[-1]
|
|
|
|
# build dense/sparse bands
|
|
dense_ranges: list[tuple[float, float]] = []
|
|
for c in corners:
|
|
band_start = max(d_min, c.start_m - approach_m)
|
|
dense_ranges.append((band_start, c.end_m))
|
|
|
|
def in_dense(d: float) -> bool:
|
|
for a, b in dense_ranges:
|
|
if a <= d <= b:
|
|
return True
|
|
return False
|
|
|
|
points: list[float] = [d_min]
|
|
d = d_min
|
|
while d < d_max:
|
|
step = dense_step if in_dense(d) else sparse_step
|
|
d = min(d + step, d_max)
|
|
points.append(d)
|
|
|
|
if points[-1] != d_max:
|
|
points.append(d_max)
|
|
|
|
return sorted(set(points))
|
|
|
|
|
|
def _interp(xs: list[float], ys: list[float], x: float) -> float:
|
|
if x <= xs[0]:
|
|
return ys[0]
|
|
if x >= xs[-1]:
|
|
return ys[-1]
|
|
i = bisect.bisect_left(xs, x)
|
|
x0, x1 = xs[i - 1], xs[i]
|
|
y0, y1 = ys[i - 1], ys[i]
|
|
if x1 == x0:
|
|
return y0
|
|
return y0 + (y1 - y0) * (x - x0) / (x1 - x0)
|
|
|
|
|
|
def _nearest(xs: list[float], ys: list[float], x: float) -> float:
|
|
if x <= xs[0]:
|
|
return ys[0]
|
|
if x >= xs[-1]:
|
|
return ys[-1]
|
|
i = bisect.bisect_left(xs, x)
|
|
if i == 0:
|
|
return ys[0]
|
|
if abs(xs[i] - x) < abs(xs[i - 1] - x):
|
|
return ys[i]
|
|
return ys[i - 1]
|
|
|
|
|
|
def sample_at(lap: Lap, channel: str, dists: list[float]) -> list[float]:
|
|
src_dist = lap.ch.get('LapDistance', [])
|
|
src_vals = lap.ch.get(channel, [])
|
|
if not src_dist or not src_vals:
|
|
return [0.0] * len(dists)
|
|
|
|
discrete = channel in _DISCRETE
|
|
fn = _nearest if discrete else _interp
|
|
return [fn(src_dist, src_vals, d) for d in dists]
|
|
|
|
|
|
def _round_channel(ch: str, v: float) -> str:
|
|
if ch in ('LapTime', 'LapDistance'):
|
|
return f'{v:.0f}' if ch == 'LapDistance' else f'{v:.2f}'
|
|
if ch in ('FuelRemaining',):
|
|
return f'{v:.2f}'
|
|
if ch in ('Steer', 'ThrottlePercentage', 'BrakePercentage'):
|
|
return f'{v:.0f}'
|
|
if ch in ('Speed', 'GForceLatitudinal', 'GForceLongitudinal'):
|
|
return f'{v:.0f}'
|
|
if ch in ('Gear', 'DRS', 'ERSMode'):
|
|
return f'{int(v)}'
|
|
return f'{v:.1f}'
|
|
|
|
|
|
def format_trace_row(
|
|
dist: float,
|
|
laptime: float,
|
|
speed: float,
|
|
throttle: float,
|
|
brake: float,
|
|
steer: float,
|
|
gear: float,
|
|
drs: float,
|
|
g_lat: float,
|
|
g_lon: float,
|
|
fuel: float,
|
|
ers: float = 0.0,
|
|
tf: float = 0.0,
|
|
tr: float = 0.0,
|
|
rpm: float = 0.0,
|
|
slip_f: float = 0.0,
|
|
slip_r: float = 0.0,
|
|
yaw: float = 0.0,
|
|
) -> str:
|
|
steer_norm = 0.0 if steer == 0.0 or steer == -0.0 else steer
|
|
return (
|
|
f'{dist:.0f},{laptime:.2f},{speed:.0f},{throttle:.0f},'
|
|
f'{brake:.0f},{steer_norm:.0f},{int(gear)},{int(drs)},'
|
|
f'{g_lat:.1f},{g_lon:.1f},{fuel:.2f},{ers:.3f},'
|
|
f'{tf:.0f},{tr:.0f},{rpm:.0f},'
|
|
f'{slip_f:.1f},{slip_r:.1f},{yaw:.2f}'
|
|
)
|