# libIGC Flight Log Parser

> A Python package that parses IGC glider logs into validated fixes, tasks, thermals, glides, and common export formats.

- URL: https://mandalsuraj.com/blog/libigc
- Author: Suraj Mandal (https://mandalsuraj.com)
- Published: 2024-09-30
- Project date: 2024-11-30
- Tags: python, data, gis, parsing, datascience
- Source: https://github.com/surajmandalcell/libigc
- PyPI package: https://pypi.org/project/libigc/

![libIGC Flight Log Parser cover](https://mandalsuraj.com/images/blog/libigc/cover.png)

IGC files are text logs written by flight recorders. `libIGC` turns those logs into Python objects that describe the route, altitude, flight state, thermals, glides, and task progress.

The examples below use the real `new_zealand.igc` file from the test suite. The cover route, terminal output, and numbers all come from that flight.

`libIGC` needs Python 3.12 or later.

```bash
pip install libigc
```


![Three-dimensional New Zealand flight path colored from blue to amber by altitude, above verified libIGC terminal output](https://mandalsuraj.com/images/blog/libigc/flight-visual.png)

  The approved cover diagram uses longitude, latitude, and altitude from 5,367 parsed fixes.

## What You Can Do With libIGC


    Flight Overview
    See The Whole Flight
    Find takeoff, landing, flight time, route shape, and altitude changes without reading raw recorder lines.


    Thermal Analysis
    Compare Every Climb
    See how long each thermal lasted, how much height it gained, and how quickly the glider climbed.


    Glide Analysis
    Measure Each Glide
    Measure distance, speed, altitude loss, and glide ratio between climbs.


    Course Progress
    Check Course Progress
    Load an LK8000 task and see when the flight reached each start, turnpoint, speed section, and goal.


    Reusable Exports
    Use Flight Data Anywhere
    Open KML in mapping tools or send CSV data to Python, R, spreadsheets, notebooks, and dashboards.


    Data Checks
    Catch Bad Flight Data
    Find broken time gaps, impossible altitude changes, sensor problems, missing dates, and incomplete flights.


## From One Command To A Flight Model

The repository includes a demo that runs the complete path. Give it an IGC file and an output directory.

```bash
uv run examples/libigc_demo.py \
  tests/testfiles/new_zealand.igc \
  -o output
```

The command validates the log, prints detected flight phases, and generates five files.

```text
Flight: Flight(valid=True, fixes: 5367, thermals: 27)
thermal[0]: Thermal(vertical_velocity=1.25 m/s, duration=4m 51s)
```

```mermaid
flowchart LR
  accTitle: libIGC Processing Flow
  accDescr: An IGC recorder log moves through parsing, validation, flight analysis, and export.
  %% caption: One public API turns recorder text into reusable flight data.
  log["<strong>IGC Log</strong><br/><small>A · B · H · I Records</small>"] --> parse["<strong>Parse</strong><br/><small>Build Fixes</small>"]
  parse --> validate["<strong>Validate</strong><br/><small>Time · Altitude</small>"]
  validate --> analyze["<strong>Analyze</strong><br/><small>Thermals · Glides</small>"]
  analyze --> export["<strong>Export</strong><br/><small>KML · CSV<br/>WPT · CUP</small>"]
```

| Generated file | What it contains | Useful in |
|---|---|---|
| `new_zealand-flight.kml` | Route, takeoff, landing, and thermal points | Google Earth and GIS tools |
| `new_zealand-flight.csv` | Fix time, position, bearing, speed, and state | Python, R, and spreadsheets |
| `new_zealand-thermals.csv` | Thermal entry and exit times | Custom analysis |
| `new_zealand-thermals.wpt` | Thermal waypoints | Navigation tools |
| `new_zealand-thermals.cup` | SeeYou thermal waypoints | Soaring software |

## The Code Behind The Command

`Flight.create_from_file` is the main API. Check `valid` before reading derived values because validation can stop analysis early.

```python
from libigc import Flight

flight = Flight.create_from_file("new_zealand.igc")
if not flight.valid:
    raise ValueError("; ".join(flight.notes))

print(len(flight.fixes))
print(flight.takeoff_fix)
print(flight.landing_fix)
print(len(flight.thermals), len(flight.glides))
```

Every valid B record becomes a `GNSSFix`. The parser keeps its time, coordinates, validity, pressure altitude, GNSS altitude, and extension text.

```mermaid
flowchart TB
  accTitle: IGC B Record Anatomy
  accDescr: A B record splits into record type, UTC time, latitude, longitude, validity, pressure altitude, and GNSS altitude.
  %% caption: The fixed-width recorder line becomes a typed point in the flight.
  raw["B1227484612592N01249579EA0043700493"]
  raw --> record["<strong>B</strong><br/><small>Record</small>"]
  raw --> time["<strong>122748</strong><br/><small>12:27:48 UTC</small>"]
  raw --> latitude["<strong>4612592N</strong><br/><small>Latitude</small>"]
  raw --> longitude["<strong>01249579E</strong><br/><small>Longitude</small>"]
  raw --> validity["<strong>A</strong><br/><small>Valid</small>"]
  raw --> pressure["<strong>00437</strong><br/><small>437 m Pressure</small>"]
  raw --> gnss["<strong>00493</strong><br/><small>493 m GNSS</small>"]
```

## What The New Zealand Flight Revealed

The sample crosses midnight UTC. `libIGC` repairs the day boundary before it calculates flight duration and state.

```mermaid
flowchart LR
  accTitle: New Zealand Flight Statistics
  accDescr: The parsed flight has 5,367 fixes, lasts 4 hours 19 minutes, covers 519 kilometers, and contains 27 thermals and 28 glides.
  %% caption: These values are calculated from the checked-in sample, not placeholder data.
  fixes["<strong>5,367</strong><br/><small>Valid Fixes</small>"] ~~~ airborne["<strong>4h 19m</strong><br/><small>Airborne</small>"]
  airborne ~~~ track["<strong>519 km</strong><br/><small>Recorded Track</small>"]
  track ~~~ thermals["<strong>27</strong><br/><small>Thermals</small>"]
  thermals ~~~ glides["<strong>28</strong><br/><small>Glides</small>"]
```

Each thermal has entry and exit fixes, duration, altitude gain, and average vertical speed. Each glide has distance, duration, average speed, altitude change, and glide ratio.

```python
for thermal in flight.thermals:
    print(thermal.time_change())
    print(thermal.alt_change())
    print(thermal.vertical_velocity())

for glide in flight.glides:
    print(glide.track_length)
    print(glide.speed())
    print(glide.glide_ratio())
```

## How The Cover Diagram Was Made

`libIGC` supplies the data. A small renderer turns each flying fix into an east, north, altitude point. This keeps the artwork tied to the real route.

```python
from math import cos, pi

fixes = flight.fixes[
    flight.takeoff_fix.index : flight.landing_fix.index + 1
]
lat0 = sum(fix.lat for fix in fixes) / len(fixes)
lon0 = sum(fix.lon for fix in fixes) / len(fixes)

route = [
    (
        (fix.lon - lon0) * 111_320 * cos(lat0 * pi / 180),
        (fix.lat - lat0) * 110_540,
        fix.alt,
    )
    for fix in fixes
]
```

The cover renderer rotates this 3D route for composition. It maps low altitude to blue, high altitude to amber, and keeps faint XYZ axes so the path retains depth.

## Check A Task

Tasks can come from an LK8000 `.lkt` file or be built from `Turnpoint` objects. The result is the fix at which each turnpoint was reached.

```python
from libigc import Task

task = Task.create_from_lkt_file("task.lkt")
reached = task.check_flight(flight)

for turnpoint, fix in zip(task.turnpoints, reached):
    print(turnpoint.kind, fix.rawtime)
```

The task checker supports start-enter, start-exit, cylinder, end-of-speed-section, and goal-cylinder logic within a task time window.

## Export The Result

The dumpers write common flight and waypoint formats. No private object conversion is needed.

```python
from libigc.lib.dumpers import (
    dump_flight_to_csv,
    dump_flight_to_kml,
    dump_thermals_to_cup_file,
    dump_thermals_to_wpt_file,
)

dump_flight_to_kml(flight, "flight.kml")
dump_flight_to_csv(flight, "track.csv", "thermals.csv")
dump_thermals_to_wpt_file(flight, "thermals.wpt", endpoints=True)
dump_thermals_to_cup_file(flight, "thermals.cup")
```

## Functionality At A Glance

| Area | What `libIGC` does |
|---|---|
| Records | Parses A, B, H, and I records and ignores unsupported record types |
| Metadata | Reads date, glider, class, recorder, firmware, hardware, GPS, and pressure-sensor fields when present |
| Fixes | Stores UTC time, timestamp, coordinates, validity, both altitudes, chosen altitude, speed, bearing, flying state, and circling state |
| Validation | Checks fix count, time gaps, midnight crossings, altitude range, altitude movement, date, takeoff, and usable altitude sensors |
| Geometry | Calculates Earth distance, bearing, and spherical angle |
| Flight state | Detects flying, takeoff, landing, straight flight, and circling with smoothed state sequences |
| Analysis | Builds thermal and glide sections with duration, climb, speed, distance, and ratio metrics |
| Tasks | Parses LK8000 tasks and checks timed cylinders against the flight |
| Exports | Writes WPT, CUP, KML, track CSV, and thermal CSV files |

## Tune Detection When Needed

The defaults cover normal logs. A custom `FlightParsingConfig` can change validation, landing, and thermal thresholds for another recorder or analysis rule.

```python
from libigc import Flight, FlightParsingConfig

class LongThermalConfig(FlightParsingConfig):
    min_time_for_thermal = 90.0

flight = Flight.create_from_file("flight.igc", LongThermalConfig)
```

## Why The State Looks Stable

Raw speed and bearing changes are noisy. `libIGC` uses a two-state Viterbi decoder for flying and circling, then applies time rules for landing and thermal duration.

That smoothing prevents one unusual fix from splitting a long glide or inventing a landing.

## Repository Evidence

The test suite covers record parsing, date changes, altitude checks, flight state, thermals, glides, tasks, geography, Viterbi smoothing, and every export format.


![libIGC README with installation, parsing, analysis, task, and export examples](https://mandalsuraj.com/images/blog/libigc/readme.png)


![libIGC GitHub repository with Python source, examples, and tests](https://mandalsuraj.com/images/blog/libigc/github.png)
