{"kind":"post","path":"/blog/libigc","post":{"metadata":{"title":"libIGC Flight Log Parser","summary":"A Python package that parses IGC glider logs into validated fixes, tasks, thermals, glides, and common export formats.","publishedAt":"2024-09-30","projectDate":"2024-11-30","ascent":"#3BAF75","github":"surajmandalcell/libigc","links":[{"label":"PyPI package","href":"https://pypi.org/project/libigc/","type":"package"}],"tags":["python","data","gis","parsing","datascience"],"weight":80},"slug":"libigc","content":"\u003c!-- cover: /images/blog/libigc/cover.png --\u003e\n\nIGC 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.\n\nThe 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.\n\n`libIGC` needs Python 3.12 or later.\n\n```bash\npip install libigc\n```\n\n\u003cfigure className=\"bp-native-hero bp-native-media\" data-preview-gallery=\"true\"\u003e\n \u003cimg src=\"/images/blog/libigc/flight-visual.png\" alt=\"Three-dimensional New Zealand flight path colored from blue to amber by altitude, above verified libIGC terminal output\" width=\"1500\" height=\"1050\" /\u003e\n \u003cfigcaption\u003eThe approved cover diagram uses longitude, latitude, and altitude from 5,367 parsed fixes.\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n## What You Can Do With libIGC\n\n\u003cdiv className=\"bp-capability-grid\"\u003e\n \u003cdiv className=\"bp-capability-card\" data-mark=\"F\"\u003e\n \u003cspan className=\"bp-capability-key\"\u003eFlight Overview\u003c/span\u003e\n \u003ch3\u003eSee The Whole Flight\u003c/h3\u003e\n \u003cp\u003eFind takeoff, landing, flight time, route shape, and altitude changes without reading raw recorder lines.\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv className=\"bp-capability-card\" data-mark=\"T\"\u003e\n \u003cspan className=\"bp-capability-key\"\u003eThermal Analysis\u003c/span\u003e\n \u003ch3\u003eCompare Every Climb\u003c/h3\u003e\n \u003cp\u003eSee how long each thermal lasted, how much height it gained, and how quickly the glider climbed.\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv className=\"bp-capability-card\" data-mark=\"G\"\u003e\n \u003cspan className=\"bp-capability-key\"\u003eGlide Analysis\u003c/span\u003e\n \u003ch3\u003eMeasure Each Glide\u003c/h3\u003e\n \u003cp\u003eMeasure distance, speed, altitude loss, and glide ratio between climbs.\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv className=\"bp-capability-card\" data-mark=\"C\"\u003e\n \u003cspan className=\"bp-capability-key\"\u003eCourse Progress\u003c/span\u003e\n \u003ch3\u003eCheck Course Progress\u003c/h3\u003e\n \u003cp\u003eLoad an LK8000 task and see when the flight reached each start, turnpoint, speed section, and goal.\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv className=\"bp-capability-card\" data-mark=\"↗\"\u003e\n \u003cspan className=\"bp-capability-key\"\u003eReusable Exports\u003c/span\u003e\n \u003ch3\u003eUse Flight Data Anywhere\u003c/h3\u003e\n \u003cp\u003eOpen KML in mapping tools or send CSV data to Python, R, spreadsheets, notebooks, and dashboards.\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv className=\"bp-capability-card\" data-mark=\"!\"\u003e\n \u003cspan className=\"bp-capability-key\"\u003eData Checks\u003c/span\u003e\n \u003ch3\u003eCatch Bad Flight Data\u003c/h3\u003e\n \u003cp\u003eFind broken time gaps, impossible altitude changes, sensor problems, missing dates, and incomplete flights.\u003c/p\u003e\n \u003c/div\u003e\n\u003c/div\u003e\n\n## From One Command To A Flight Model\n\nThe repository includes a demo that runs the complete path. Give it an IGC file and an output directory.\n\n```bash\nuv run examples/libigc_demo.py \\\n tests/testfiles/new_zealand.igc \\\n -o output\n```\n\nThe command validates the log, prints detected flight phases, and generates five files.\n\n```text\nFlight: Flight(valid=True, fixes: 5367, thermals: 27)\nthermal[0]: Thermal(vertical_velocity=1.25 m/s, duration=4m 51s)\n```\n\n```mermaid\nflowchart LR\n accTitle: libIGC Processing Flow\n accDescr: An IGC recorder log moves through parsing, validation, flight analysis, and export.\n %% caption: One public API turns recorder text into reusable flight data.\n log[\"\u003cstrong\u003eIGC Log\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eA · B · H · I Records\u003c/small\u003e\"] --\u003e parse[\"\u003cstrong\u003eParse\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eBuild Fixes\u003c/small\u003e\"]\n parse --\u003e validate[\"\u003cstrong\u003eValidate\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eTime · Altitude\u003c/small\u003e\"]\n validate --\u003e analyze[\"\u003cstrong\u003eAnalyze\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eThermals · Glides\u003c/small\u003e\"]\n analyze --\u003e export[\"\u003cstrong\u003eExport\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eKML · CSV\u003cbr/\u003eWPT · CUP\u003c/small\u003e\"]\n```\n\n| Generated file | What it contains | Useful in |\n|---|---|---|\n| `new_zealand-flight.kml` | Route, takeoff, landing, and thermal points | Google Earth and GIS tools |\n| `new_zealand-flight.csv` | Fix time, position, bearing, speed, and state | Python, R, and spreadsheets |\n| `new_zealand-thermals.csv` | Thermal entry and exit times | Custom analysis |\n| `new_zealand-thermals.wpt` | Thermal waypoints | Navigation tools |\n| `new_zealand-thermals.cup` | SeeYou thermal waypoints | Soaring software |\n\n## The Code Behind The Command\n\n`Flight.create_from_file` is the main API. Check `valid` before reading derived values because validation can stop analysis early.\n\n```python\nfrom libigc import Flight\n\nflight = Flight.create_from_file(\"new_zealand.igc\")\nif not flight.valid:\n raise ValueError(\"; \".join(flight.notes))\n\nprint(len(flight.fixes))\nprint(flight.takeoff_fix)\nprint(flight.landing_fix)\nprint(len(flight.thermals), len(flight.glides))\n```\n\nEvery valid B record becomes a `GNSSFix`. The parser keeps its time, coordinates, validity, pressure altitude, GNSS altitude, and extension text.\n\n```mermaid\nflowchart TB\n accTitle: IGC B Record Anatomy\n accDescr: A B record splits into record type, UTC time, latitude, longitude, validity, pressure altitude, and GNSS altitude.\n %% caption: The fixed-width recorder line becomes a typed point in the flight.\n raw[\"B1227484612592N01249579EA0043700493\"]\n raw --\u003e record[\"\u003cstrong\u003eB\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eRecord\u003c/small\u003e\"]\n raw --\u003e time[\"\u003cstrong\u003e122748\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003e12:27:48 UTC\u003c/small\u003e\"]\n raw --\u003e latitude[\"\u003cstrong\u003e4612592N\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eLatitude\u003c/small\u003e\"]\n raw --\u003e longitude[\"\u003cstrong\u003e01249579E\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eLongitude\u003c/small\u003e\"]\n raw --\u003e validity[\"\u003cstrong\u003eA\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eValid\u003c/small\u003e\"]\n raw --\u003e pressure[\"\u003cstrong\u003e00437\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003e437 m Pressure\u003c/small\u003e\"]\n raw --\u003e gnss[\"\u003cstrong\u003e00493\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003e493 m GNSS\u003c/small\u003e\"]\n```\n\n## What The New Zealand Flight Revealed\n\nThe sample crosses midnight UTC. `libIGC` repairs the day boundary before it calculates flight duration and state.\n\n```mermaid\nflowchart LR\n accTitle: New Zealand Flight Statistics\n accDescr: The parsed flight has 5,367 fixes, lasts 4 hours 19 minutes, covers 519 kilometers, and contains 27 thermals and 28 glides.\n %% caption: These values are calculated from the checked-in sample, not placeholder data.\n fixes[\"\u003cstrong\u003e5,367\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eValid Fixes\u003c/small\u003e\"] ~~~ airborne[\"\u003cstrong\u003e4h 19m\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eAirborne\u003c/small\u003e\"]\n airborne ~~~ track[\"\u003cstrong\u003e519 km\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eRecorded Track\u003c/small\u003e\"]\n track ~~~ thermals[\"\u003cstrong\u003e27\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eThermals\u003c/small\u003e\"]\n thermals ~~~ glides[\"\u003cstrong\u003e28\u003c/strong\u003e\u003cbr/\u003e\u003csmall\u003eGlides\u003c/small\u003e\"]\n```\n\nEach 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.\n\n```python\nfor thermal in flight.thermals:\n print(thermal.time_change())\n print(thermal.alt_change())\n print(thermal.vertical_velocity())\n\nfor glide in flight.glides:\n print(glide.track_length)\n print(glide.speed())\n print(glide.glide_ratio())\n```\n\n## How The Cover Diagram Was Made\n\n`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.\n\n```python\nfrom math import cos, pi\n\nfixes = flight.fixes[\n flight.takeoff_fix.index : flight.landing_fix.index + 1\n]\nlat0 = sum(fix.lat for fix in fixes) / len(fixes)\nlon0 = sum(fix.lon for fix in fixes) / len(fixes)\n\nroute = [\n (\n (fix.lon - lon0) * 111_320 * cos(lat0 * pi / 180),\n (fix.lat - lat0) * 110_540,\n fix.alt,\n )\n for fix in fixes\n]\n```\n\nThe 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.\n\n## Check A Task\n\nTasks can come from an LK8000 `.lkt` file or be built from `Turnpoint` objects. The result is the fix at which each turnpoint was reached.\n\n```python\nfrom libigc import Task\n\ntask = Task.create_from_lkt_file(\"task.lkt\")\nreached = task.check_flight(flight)\n\nfor turnpoint, fix in zip(task.turnpoints, reached):\n print(turnpoint.kind, fix.rawtime)\n```\n\nThe task checker supports start-enter, start-exit, cylinder, end-of-speed-section, and goal-cylinder logic within a task time window.\n\n## Export The Result\n\nThe dumpers write common flight and waypoint formats. No private object conversion is needed.\n\n```python\nfrom libigc.lib.dumpers import (\n dump_flight_to_csv,\n dump_flight_to_kml,\n dump_thermals_to_cup_file,\n dump_thermals_to_wpt_file,\n)\n\ndump_flight_to_kml(flight, \"flight.kml\")\ndump_flight_to_csv(flight, \"track.csv\", \"thermals.csv\")\ndump_thermals_to_wpt_file(flight, \"thermals.wpt\", endpoints=True)\ndump_thermals_to_cup_file(flight, \"thermals.cup\")\n```\n\n## Functionality At A Glance\n\n| Area | What `libIGC` does |\n|---|---|\n| Records | Parses A, B, H, and I records and ignores unsupported record types |\n| Metadata | Reads date, glider, class, recorder, firmware, hardware, GPS, and pressure-sensor fields when present |\n| Fixes | Stores UTC time, timestamp, coordinates, validity, both altitudes, chosen altitude, speed, bearing, flying state, and circling state |\n| Validation | Checks fix count, time gaps, midnight crossings, altitude range, altitude movement, date, takeoff, and usable altitude sensors |\n| Geometry | Calculates Earth distance, bearing, and spherical angle |\n| Flight state | Detects flying, takeoff, landing, straight flight, and circling with smoothed state sequences |\n| Analysis | Builds thermal and glide sections with duration, climb, speed, distance, and ratio metrics |\n| Tasks | Parses LK8000 tasks and checks timed cylinders against the flight |\n| Exports | Writes WPT, CUP, KML, track CSV, and thermal CSV files |\n\n## Tune Detection When Needed\n\nThe defaults cover normal logs. A custom `FlightParsingConfig` can change validation, landing, and thermal thresholds for another recorder or analysis rule.\n\n```python\nfrom libigc import Flight, FlightParsingConfig\n\nclass LongThermalConfig(FlightParsingConfig):\n min_time_for_thermal = 90.0\n\nflight = Flight.create_from_file(\"flight.igc\", LongThermalConfig)\n```\n\n## Why The State Looks Stable\n\nRaw 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.\n\nThat smoothing prevents one unusual fix from splitting a long glide or inventing a landing.\n\n## Repository Evidence\n\nThe test suite covers record parsing, date changes, altitude checks, flight state, thermals, glides, tasks, geography, Viterbi smoothing, and every export format.\n\n\u003cdiv className=\"bp-evidence-grid bp-evidence-grid--compact\" data-preview-gallery=\"true\"\u003e\n \u003cimg src=\"/images/blog/libigc/readme.png\" alt=\"libIGC README with installation, parsing, analysis, task, and export examples\" /\u003e\n \u003cimg src=\"/images/blog/libigc/github.png\" alt=\"libIGC GitHub repository with Python source, examples, and tests\" /\u003e\n\u003c/div\u003e"}}