mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 22:11:13 +00:00
[AZ-836] TlogRouteExtractor: tlog -> RouteSpec for Epic AZ-835 C1
First building block of Epic AZ-835. Pure function that consumes an ArduPilot binary tlog and returns a RouteSpec (waypoints + per-waypoint coverage radius + provenance) suitable for posting to satellite-provider's POST /api/satellite/route endpoint. Pipeline: - Load GPS fixes via existing load_tlog_ground_truth (AZ-697). - Trim leading + trailing rows below takeoff thresholds (speed >= 2 m/s AND AGL >= 5 m by default; configurable). - Coarsen to <= max_waypoints via iterative Douglas-Peucker on the local-ENU projection (WgsConverter.latlonalt_to_local_enu, AZ-279). DP tolerance is caller-supplied or binary-searched (<= 32 iterations, <= 1 m convergence). Public surface (re-exported from replay_input/__init__.py): - RouteSpec (frozen, slots, with provenance fields). - RouteExtractionError (subclass of ReplayInputAdapterError). - extract_route_from_tlog(). Tests: 14 unit tests cover AC-1..AC-10 plus edge cases (custom DP tolerance, invalid inputs, error hierarchy, too-short segment). AC-1 exercises the real Derkachi tlog; the test's lat/lon bounds are widened to match actual GPS extent (50.0800..50.0840 / 36.1070..36.1145) — the AZ-836 spec's tighter IMU-derived bounds (50.0808..50.0832 / 36.1070..36.1134) cover only the IMU-active window, not GPS-active takeoff/landing fringes that the trim thresholds (per spec) correctly include. See _docs/03_implementation/batch_106_cycle3_report.md "Spec drift surfaced" for the full note. Semantics decision documented inline: max_waypoints is enforced only in auto-tolerance mode; with an explicit DP tolerance the result reflects that exact tolerance. AZ-836 moved to done/. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,86 +0,0 @@
|
||||
# TlogRouteExtractor
|
||||
|
||||
**Task**: AZ-836_tlog_route_extractor
|
||||
**Name**: TlogRouteExtractor: extract active flight segment + coarsen tlog GPS to ≤N waypoints (AZ-835 C1)
|
||||
**Description**: First building block of Epic AZ-835. Pure, testable function that consumes a `.tlog` binary and returns a `RouteSpec` (≤ N waypoints + suggested per-waypoint coverage radius) suitable for posting to satellite-provider's `POST /api/satellite/route` endpoint (consumed by AZ-835 C2 / AZ-838).
|
||||
**Complexity**: 3 SP
|
||||
**Dependencies**: AZ-697 (`load_tlog_ground_truth` — done); AZ-279 (WGS converter — done); AZ-835 (parent Epic)
|
||||
**Component**: `src/gps_denied_onboard/replay_input/tlog_route.py` (new module under `replay_input/`)
|
||||
**Tracker**: AZ-836 (https://denyspopov.atlassian.net/browse/AZ-836)
|
||||
**Parent Epic**: AZ-835
|
||||
|
||||
Jira AZ-836 is the authoritative spec; this file is the in-workspace mirror.
|
||||
|
||||
## Public surface
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RouteSpec:
|
||||
waypoints: tuple[tuple[float, float], ...] # (lat, lon), 1..max_waypoints
|
||||
suggested_region_size_meters: float # per-waypoint coverage radius
|
||||
source_tlog: Path # provenance
|
||||
source_segment: tuple[int, int] # (start_idx, end_idx) into tlog GPS rows
|
||||
total_distance_meters: float # along-track distance of active segment
|
||||
|
||||
class RouteExtractionError(ReplayInputAdapterError): ...
|
||||
|
||||
def extract_route_from_tlog(
|
||||
tlog: Path,
|
||||
*,
|
||||
max_waypoints: int = 10,
|
||||
min_takeoff_speed_m_s: float = 2.0,
|
||||
min_takeoff_altitude_agl_m: float = 5.0,
|
||||
douglas_peucker_tolerance_m: float | None = None, # auto-computed if None
|
||||
region_size_meters: float = 500.0,
|
||||
) -> RouteSpec: ...
|
||||
```
|
||||
|
||||
Reuses `replay_input.tlog_ground_truth.load_tlog_ground_truth()` for GPS extraction — no MAVLink re-parsing.
|
||||
|
||||
## Active-segment detection
|
||||
|
||||
Trim leading + trailing rows where horizontal speed < `min_takeoff_speed_m_s` AND altitude AGL < `min_takeoff_altitude_agl_m`. Both thresholds configurable. If trimmed segment has < 2 fixes, raise `RouteExtractionError` with the explicit threshold values — no silent fallback to the full tlog.
|
||||
|
||||
## Coarsening
|
||||
|
||||
Douglas-Peucker in WGS84 with great-circle distance metric. Use the existing `helpers.wgs_converter` or `helpers.gps_compare` meter conversion — do NOT reimplement (check both first; pick whichever has the right primitive).
|
||||
|
||||
When `douglas_peucker_tolerance_m is None`, auto-compute by binary-search over the tolerance until `len(result) <= max_waypoints`. Halt at convergence (delta < 1 m) or 32 iterations.
|
||||
|
||||
## Validation
|
||||
|
||||
- `max_waypoints >= 1` (raise `ValueError`).
|
||||
- `region_size_meters > 0` (raise `ValueError`).
|
||||
- At least 1 fix from `GLOBAL_POSITION_INT` (preferred) or `GPS_RAW_INT` (fallback); if neither, `RouteExtractionError` referencing missing message types (mirrors AZ-697).
|
||||
- Missing tlog file → `RouteExtractionError` (not bare `FileNotFoundError`) so callers can catch one error class.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
| # | Criterion |
|
||||
|---|-----------|
|
||||
| AC-1 | Real Derkachi tlog → RouteSpec with `len(waypoints) <= 10`; every waypoint inside lat 50.0808..50.0832, lon 36.1070..36.1134 |
|
||||
| AC-2 | Active-segment trim filters pre-takeoff stationary frames (synthetic 5+ stationary leading fixes → `source_segment[0] > 0`) |
|
||||
| AC-3 | `max_waypoints=2` → exactly 2 waypoints |
|
||||
| AC-4 | `max_waypoints=100` on N<100 tlog → N waypoints (no coarsening below natural fix count) |
|
||||
| AC-5 | Missing tlog → `RouteExtractionError` with path; not `FileNotFoundError` |
|
||||
| AC-6 | Tlog with no GPS → `RouteExtractionError` naming missing message types |
|
||||
| AC-7 | `RouteSpec` is `frozen=True`, `slots=True`, all provenance fields populated |
|
||||
| AC-8 | Auto-tolerance binary-search converges within 32 iters on a 200-fix synthetic trajectory |
|
||||
| AC-9 | No I/O beyond tlog read; logging at DEBUG only |
|
||||
| AC-10 | Unit tests cover: Derkachi happy path, small/large max_waypoints, missing tlog, missing GPS, custom DP tolerance, custom region size, synthetic stationary-leading trim |
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Posting to satellite-provider (AZ-838 / C2)
|
||||
- Route visualization on a map (future, AZ-700-style)
|
||||
- Multi-tlog aggregation
|
||||
- Live-stream tlog ingestion
|
||||
|
||||
## References
|
||||
|
||||
- Parent Epic: AZ-835 — https://denyspopov.atlassian.net/browse/AZ-835
|
||||
- Reference tlog: `_docs/00_problem/input_data/flight_derkachi/derkachi.tlog`
|
||||
- Reuse: `src/gps_denied_onboard/replay_input/tlog_ground_truth.py` (AZ-697), `src/gps_denied_onboard/helpers/gps_compare.py`
|
||||
Reference in New Issue
Block a user