Task 307: .IGC File Format
Task 307: .IGC File Format
1. List of all the properties of this file format intrinsic to its file system
Based on the IGC file format specifications (from the FAI/IGC technical specification and developers' guide), the .IGC format is a text-based structure for recording glider flight data. The "properties" intrinsic to the format refer to the structured data fields and metadata embedded within the file itself, such as headers, identifiers, flight details, and record-specific values. These are not file system metadata (e.g., file size or creation date) but the format's internal properties that define the flight recorder data. The format consists of record types (A through L, plus G for security), each with specific fields. Below is a comprehensive list of all extractable properties, grouped by record type for clarity. These include mandatory and optional fields, with their typical formats and purposes.
A Record Properties (Manufacturer and FR Identification - Single Instance, Mandatory First Record)
- Manufacturer Code (3 alphanumeric characters, e.g., "XXX" for a specific maker)
- FR Serial Number (3-10 alphanumeric characters, unique to the flight recorder)
- Optional Extension Text (variable alphanumeric string, e.g., model details)
H Record Properties (Header Information - Multiple Lines, Mandatory)
These are subtype-specific key-value pairs prefixed with "H" and a source code (F for FR, O for observer, P for pilot).
- Flight Date (DDMMYY, UTC)
- Time Zone (e.g., "UTC")
- Pilot in Charge (variable text string)
- Second Crew Member (variable text string, optional)
- Glider Type (variable text string)
- Glider Registration/ID (variable text string)
- GPS Datum (e.g., "100:WGS 1984")
- Firmware Version (variable text string)
- Hardware Version (variable text string)
- FR Manufacturer (variable text string)
- GPS Receiver Model (variable text string, including accuracy details)
- Pressure Altitude Sensor (variable text string, including range/accuracy)
- Competition ID (variable text string)
- Competition Class (variable text string)
- Club/Site (variable text string)
- Units of Measure (e.g., speed, distance, altitude units)
- Time Zone Offset (numeric)
- MOP Sensor ID (Means of Propulsion, optional)
- Takeoff Date (DDMMYY, optional)
- Security/Validation Info (variable)
I Record Properties (Additions to B Record - Single Instance, Optional)
- Number of Additions (2 digits)
- Addition List (byte positions, codes, and lengths for B-record extensions, e.g., "FXA" for fix accuracy, "SIU" for satellites in use, "ENL" for engine noise level, "RPM" for engine RPM)
J Record Properties (Additions to K Record - Single Instance, Optional)
- Number of Additions (2 digits)
- Addition List (similar to I record, for K-record extensions, e.g., "HDT" for heading, "TRS" for true airspeed)
C Record Properties (Task/Declaration - Multiple Lines, Optional)
- Declaration Date (DDMMYY)
- Declaration Time (HHMMSS)
- Flight Date (DDMMYY)
- Task Number (4 digits)
- Number of Turn Points (4 digits)
- Task Description (variable text)
- Waypoints (multiple: Latitude DDMMmmmN/S, Longitude DDDMMmmmE/W, Description text)
- Area Task Details (optional: distances in km.ddd, bearings in degrees)
D Record Properties (Differential GNSS Station - Multiple Instances, Optional)
- DGPS Station ID (4 characters)
- DGPS Type (1 character)
F Record Properties (Satellite Constellation - Multiple Instances)
- Time of Change (HHMMSS, optional for changes)
- Satellite IDs (2-digit PRN codes, up to 12 or more)
B Record Properties (Fix Data - Multiple Instances, Core Flight Data)
- Time (HHMMSS, UTC)
- Latitude (DDMMmmmN/S)
- Longitude (DDDMMmmmE/W)
- Fix Validity (A=valid 3D, V=2D or invalid)
- Pressure Altitude (5 digits, metres, signed)
- GNSS Altitude (5 digits, metres)
- Extensions (per I record, e.g., FXA: 3 digits metres; SIU: 2 digits satellites; ENL: 3 digits 0-999; RPM: 4 digits)
E Record Properties (Event Logs - Multiple Instances)
- Time (HHMMSS)
- Event Code (3 characters, e.g., "PEV" for pilot event)
- Optional Data (variable, e.g., lat/lon if applicable)
K Record Properties (Additional Data - Multiple Instances)
- Time (HHMMSS)
- Extensions (per J record, e.g., HDT: heading degrees; TRS: true airspeed km/h; VAT: vario m/s)
L Record Properties (Comments/Logbook - Multiple Instances)
- Source (e.g., "PLT" for pilot, "SEE" for SeeYou software)
- Comment Text (variable string)
G Record Properties (Security Check - Multiple Lines, Mandatory)
- Security Hash (64 characters per line, RSA/MD5-based signature covering the file up to but not including G records)
Overall Format Properties
- File Name Convention (e.g., YMDCXXXF.IGC or long form YYYY-MM-DD-XXX-ABC-01.IGC)
- Geodetic Datum (WGS84 mandatory)
- Record Delimiter (CRLF)
- Character Limits (76 characters max per line excluding CRLF)
- Units (UTC time, metres altitude, km distances, km/h speeds, degrees True directions)
- Security Integrity (Validated via manufacturer-specific VALI program)
These properties collectively define the flight's metadata, task, fixes, and integrity. Not all are present in every file; some are optional or depend on the recorder.
2. Two direct download links for files of format .IGC
- https://skypolaris.org/wp-content/uploads/IGS Files/Madrid to Jerez.igc
- https://skypolaris.org/wp-content/uploads/IGS Files/Jarez to Senegal.igc
3. Ghost blog embedded html javascript for drag and drop .IGC file dump
This is a standalone HTML page with embedded JavaScript that allows drag-and-drop of a .IGC file. It parses the file and dumps all the properties from the list above to the screen (in a structured
block for readability). It uses FileReader to handle the file asynchronously.
Drag and Drop .IGC File to Dump Properties
4. Python class for .IGC file handling
This Python class can open, decode/read, write/modify, and print all properties to console.
import json
import os
class IGCFile:
def __init__(self, filepath=None):
self.filepath = filepath
self.properties = {
'A': {}, 'H': {}, 'I': {}, 'J': {}, 'C': [], 'D': [], 'F': [], 'B': [], 'E': [], 'K': [], 'L': [], 'G': []
}
if filepath:
self.read()
def read(self):
if not os.path.exists(self.filepath):
raise FileNotFoundError(f"File {self.filepath} not found.")
with open(self.filepath, 'r') as f:
lines = f.readlines()
for line in lines:
line = line.strip()
if not line:
continue
type_ = line[0].upper()
if type_ == 'A':
self.properties['A']['manufacturerCode'] = line[1:4]
self.properties['A']['serialNumber'] = line[4: min(7, len(line)-1) +1] if len(line) > 4 else ''
self.properties['A']['extension'] = line[7:] if len(line) > 7 else ''
elif type_ == 'H':
colon_idx = line.find(':')
subtype = line[2:colon_idx] if colon_idx > -1 else line[2:]
value = line[colon_idx+1:] if colon_idx > -1 else ''
self.properties['H'][subtype] = value.strip()
elif type_ == 'I':
self.properties['I']['numAdditions'] = int(line[1:3])
self.properties['I']['additions'] = [line[i:i+7] for i in range(3, len(line), 7)]
elif type_ == 'J':
self.properties['J']['numAdditions'] = int(line[1:3])
self.properties['J']['additions'] = [line[i:i+7] for i in range(3, len(line), 7)]
elif type_ == 'C':
self.properties['C'].append({
'dateTime': line[1:15],
'lat': line[15:23],
'lon': line[23:33],
'desc': line[33:].strip()
})
elif type_ == 'D':
self.properties['D'].append({
'stationID': line[1:5],
'type': line[5:].strip()
})
elif type_ == 'F':
self.properties['F'].append({
'time': line[1:7],
'satellites': [line[i:i+2] for i in range(7, len(line), 2)]
})
elif type_ == 'B':
ext_start = 35
self.properties['B'].append({
'time': line[1:7],
'lat': line[7:15],
'lon': line[15:24],
'validity': line[24],
'pressAlt': int(line[25:30]),
'gnssAlt': int(line[30:35]),
'extensions': line[35:].strip()
})
elif type_ == 'E':
self.properties['E'].append({
'time': line[1:7],
'code': line[7:10],
'data': line[10:].strip()
})
elif type_ == 'K':
self.properties['K'].append({
'time': line[1:7],
'extensions': line[7:].strip()
})
elif type_ == 'L':
self.properties['L'].append({
'source': line[1:4],
'comment': line[4:].strip()
})
elif type_ == 'G':
self.properties['G'].append(line[1:].strip())
def print_properties(self):
print(json.dumps(self.properties, indent=4))
def write(self, new_filepath=None):
filepath = new_filepath or self.filepath
if not filepath:
raise ValueError("No filepath specified for writing.")
with open(filepath, 'w') as f:
# Write A record
a = self.properties['A']
f.write(f"A{a['manufacturerCode']}{a['serialNumber']}{a['extension']}\n")
# Write H records
for subtype, value in self.properties['H'].items():
f.write(f"H{subtype}:{value}\n")
# Write I record
i = self.properties['I']
if i.get('numAdditions'):
additions_str = ''.join(i['additions'])
f.write(f"I{str(i['numAdditions']).zfill(2)}{additions_str}\n")
# Write J record
j = self.properties['J']
if j.get('numAdditions'):
additions_str = ''.join(j['additions'])
f.write(f"J{str(j['numAdditions']).zfill(2)}{additions_str}\n")
# Write C records
for c in self.properties['C']:
f.write(f"C{c['dateTime']}{c['lat']}{c['lon']}{c['desc']}\n")
# Write D records
for d in self.properties['D']:
f.write(f"D{d['stationID']}{d['type']}\n")
# Write F records
for ff in self.properties['F']:
sats_str = ''.join(ff['satellites'])
f.write(f"F{ff['time']}{sats_str}\n")
# Write B records
for b in self.properties['B']:
press_alt = str(b['pressAlt']).zfill(5)
gnss_alt = str(b['gnssAlt']).zfill(5)
f.write(f"B{b['time']}{b['lat']}{b['lon']}{b['validity']}{press_alt}{gnss_alt}{b['extensions']}\n")
# Write E records
for e in self.properties['E']:
f.write(f"E{e['time']}{e['code']}{e['data']}\n")
# Write K records
for k in self.properties['K']:
f.write(f"K{k['time']}{k['extensions']}\n")
# Write L records
for l in self.properties['L']:
f.write(f"L{l['source']}{l['comment']}\n")
# Write G records
for g in self.properties['G']:
f.write(f"G{g}\n")
# Example usage:
# igc = IGCFile('example.igc')
# igc.print_properties()
# igc.properties['H']['FPLTPILOT'] = 'New Pilot' # Modify
# igc.write('modified.igc')
5. Java class for .IGC file handling
This Java class can open, decode/read, write/modify, and print all properties to console.
import java.io.*;
import java.util.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class IGCFile {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public IGCFile(String filepath) {
this.filepath = filepath;
initializeProperties();
if (filepath != null) {
read();
}
}
private void initializeProperties() {
properties.put("A", new HashMap<String, String>());
properties.put("H", new HashMap<String, String>());
properties.put("I", new HashMap<String, Object>());
properties.put("J", new HashMap<String, Object>());
properties.put("C", new ArrayList<Map<String, String>>());
properties.put("D", new ArrayList<Map<String, String>>());
properties.put("F", new ArrayList<Map<String, Object>>());
properties.put("B", new ArrayList<Map<String, Object>>());
properties.put("E", new ArrayList<Map<String, String>>());
properties.put("K", new ArrayList<Map<String, String>>());
properties.put("L", new ArrayList<Map<String, String>>());
properties.put("G", new ArrayList<String>());
}
public void read() {
try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
char type = Character.toUpperCase(line.charAt(0));
switch (type) {
case 'A':
Map<String, String> a = (Map<String, String>) properties.get("A");
a.put("manufacturerCode", line.substring(1, Math.min(4, line.length())));
a.put("serialNumber", line.substring(4, Math.min(7, line.length())));
a.put("extension", line.substring(7));
break;
case 'H':
int colonIdx = line.indexOf(':');
String subtype = line.substring(2, colonIdx > -1 ? colonIdx : line.length());
String value = colonIdx > -1 ? line.substring(colonIdx + 1).trim() : "";
((Map<String, String>) properties.get("H")).put(subtype, value);
break;
case 'I':
Map<String, Object> i = (Map<String, Object>) properties.get("I");
i.put("numAdditions", Integer.parseInt(line.substring(1, 3)));
List<String> additions = new ArrayList<>();
for (int idx = 3; idx < line.length(); idx += 7) {
additions.add(line.substring(idx, Math.min(idx + 7, line.length())));
}
i.put("additions", additions);
break;
case 'J':
Map<String, Object> j = (Map<String, Object>) properties.get("J");
j.put("numAdditions", Integer.parseInt(line.substring(1, 3)));
List<String> additionsJ = new ArrayList<>();
for (int idx = 3; idx < line.length(); idx += 7) {
additionsJ.add(line.substring(idx, Math.min(idx + 7, line.length())));
}
j.put("additions", additionsJ);
break;
case 'C':
Map<String, String> c = new HashMap<>();
c.put("dateTime", line.substring(1, 15));
c.put("lat", line.substring(15, 23));
c.put("lon", line.substring(23, 33));
c.put("desc", line.substring(33).trim());
((List<Map<String, String>>) properties.get("C")).add(c);
break;
case 'D':
Map<String, String> d = new HashMap<>();
d.put("stationID", line.substring(1, 5));
d.put("type", line.substring(5).trim());
((List<Map<String, String>>) properties.get("D")).add(d);
break;
case 'F':
Map<String, Object> f = new HashMap<>();
f.put("time", line.substring(1, 7));
List<String> sats = new ArrayList<>();
for (int idx = 7; idx < line.length(); idx += 2) {
sats.add(line.substring(idx, Math.min(idx + 2, line.length())));
}
f.put("satellites", sats);
((List<Map<String, Object>>) properties.get("F")).add(f);
break;
case 'B':
Map<String, Object> b = new HashMap<>();
b.put("time", line.substring(1, 7));
b.put("lat", line.substring(7, 15));
b.put("lon", line.substring(15, 24));
b.put("validity", String.valueOf(line.charAt(24)));
b.put("pressAlt", Integer.parseInt(line.substring(25, 30)));
b.put("gnssAlt", Integer.parseInt(line.substring(30, 35)));
b.put("extensions", line.substring(35).trim());
((List<Map<String, Object>>) properties.get("B")).add(b);
break;
case 'E':
Map<String, String> e = new HashMap<>();
e.put("time", line.substring(1, 7));
e.put("code", line.substring(7, 10));
e.put("data", line.substring(10).trim());
((List<Map<String, String>>) properties.get("E")).add(e);
break;
case 'K':
Map<String, String> k = new HashMap<>();
k.put("time", line.substring(1, 7));
k.put("extensions", line.substring(7).trim());
((List<Map<String, String>>) properties.get("K")).add(k);
break;
case 'L':
Map<String, String> l = new HashMap<>();
l.put("source", line.substring(1, 4));
l.put("comment", line.substring(4).trim());
((List<Map<String, String>>) properties.get("L")).add(l);
break;
case 'G':
((List<String>) properties.get("G")).add(line.substring(1).trim());
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void printProperties() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(properties));
}
public void write(String newFilepath) {
if (newFilepath == null) newFilepath = filepath;
try (BufferedWriter bw = new BufferedWriter(new FileWriter(newFilepath))) {
// Write A
Map<String, String> a = (Map<String, String>) properties.get("A");
bw.write("A" + a.getOrDefault("manufacturerCode", "") + a.getOrDefault("serialNumber", "") + a.getOrDefault("extension", ""));
bw.newLine();
// Write H
Map<String, String> h = (Map<String, String>) properties.get("H");
for (Map.Entry<String, String> entry : h.entrySet()) {
bw.write("H" + entry.getKey() + ":" + entry.getValue());
bw.newLine();
}
// Write I
Map<String, Object> i = (Map<String, Object>) properties.get("I");
if (i.containsKey("numAdditions")) {
String num = String.format("%02d", (int) i.get("numAdditions"));
String adds = String.join("", (List<String>) i.get("additions"));
bw.write("I" + num + adds);
bw.newLine();
}
// Write J
Map<String, Object> j = (Map<String, Object>) properties.get("J");
if (j.containsKey("numAdditions")) {
String num = String.format("%02d", (int) j.get("numAdditions"));
String adds = String.join("", (List<String>) j.get("additions"));
bw.write("J" + num + adds);
bw.newLine();
}
// Write C
List<Map<String, String>> cList = (List<Map<String, String>>) properties.get("C");
for (Map<String, String> c : cList) {
bw.write("C" + c.get("dateTime") + c.get("lat") + c.get("lon") + c.get("desc"));
bw.newLine();
}
// Write D
List<Map<String, String>> dList = (List<Map<String, String>>) properties.get("D");
for (Map<String, String> d : dList) {
bw.write("D" + d.get("stationID") + d.get("type"));
bw.newLine();
}
// Write F
List<Map<String, Object>> fList = (List<Map<String, Object>>) properties.get("F");
for (Map<String, Object> f : fList) {
String sats = String.join("", (List<String>) f.get("satellites"));
bw.write("F" + f.get("time") + sats);
bw.newLine();
}
// Write B
List<Map<String, Object>> bList = (List<Map<String, Object>>) properties.get("B");
for (Map<String, Object> b : bList) {
String pressAlt = String.format("%05d", (int) b.get("pressAlt"));
String gnssAlt = String.format("%05d", (int) b.get("gnssAlt"));
bw.write("B" + b.get("time") + b.get("lat") + b.get("lon") + b.get("validity") + pressAlt + gnssAlt + b.get("extensions"));
bw.newLine();
}
// Write E
List<Map<String, String>> eList = (List<Map<String, String>>) properties.get("E");
for (Map<String, String> e : eList) {
bw.write("E" + e.get("time") + e.get("code") + e.get("data"));
bw.newLine();
}
// Write K
List<Map<String, String>> kList = (List<Map<String, String>>) properties.get("K");
for (Map<String, String> k : kList) {
bw.write("K" + k.get("time") + k.get("extensions"));
bw.newLine();
}
// Write L
List<Map<String, String>> lList = (List<Map<String, String>>) properties.get("L");
for (Map<String, String> l : lList) {
bw.write("L" + l.get("source") + l.get("comment"));
bw.newLine();
}
// Write G
List<String> gList = (List<String>) properties.get("G");
for (String g : gList) {
bw.write("G" + g);
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Example usage:
// public static void main(String[] args) {
// IGCFile igc = new IGCFile("example.igc");
// igc.printProperties();
// ((Map<String, String>) igc.properties.get("H")).put("FPLTPILOT", "New Pilot"); // Modify
// igc.write("modified.igc");
// }
}
6. Javascript class for .IGC file handling
This JavaScript class (ES6) can open (via async read), decode/read, write (to string for download), and print all properties to console. Note: File I/O uses Node.js fs for server-side; for browser, adapt with FileReader.
const fs = require('fs'); // For Node.js; remove for browser
class IGCFile {
constructor(filepath = null) {
this.filepath = filepath;
this.properties = {
A: {}, H: {}, I: {}, J: {}, C: [], D: [], F: [], B: [], E: [], K: [], L: [], G: []
};
if (filepath) {
this.readSync(); // Or use async read
}
}
readSync() {
if (!fs.existsSync(this.filepath)) {
throw new Error(`File ${this.filepath} not found.`);
}
const content = fs.readFileSync(this.filepath, 'utf8');
const lines = content.split(/\r?\n/);
lines.forEach(line => {
line = line.trim();
if (!line) return;
const type = line[0].toUpperCase();
switch (type) {
case 'A':
this.properties.A.manufacturerCode = line.slice(1,4);
this.properties.A.serialNumber = line.slice(4, Math.min(7, line.length));
this.properties.A.extension = line.slice(7);
break;
case 'H':
const colonIdx = line.indexOf(':');
const subtype = line.slice(2, colonIdx > -1 ? colonIdx : line.length);
const value = colonIdx > -1 ? line.slice(colonIdx + 1).trim() : '';
this.properties.H[subtype] = value;
break;
case 'I':
this.properties.I.numAdditions = parseInt(line.slice(1,3));
this.properties.I.additions = [];
for (let i = 3; i < line.length; i += 7) {
this.properties.I.additions.push(line.slice(i, i+7));
}
break;
case 'J':
this.properties.J.numAdditions = parseInt(line.slice(1,3));
this.properties.J.additions = [];
for (let i = 3; i < line.length; i += 7) {
this.properties.J.additions.push(line.slice(i, i+7));
}
break;
case 'C':
this.properties.C.push({
dateTime: line.slice(1,15),
lat: line.slice(15,23),
lon: line.slice(23,33),
desc: line.slice(33).trim()
});
break;
case 'D':
this.properties.D.push({
stationID: line.slice(1,5),
type: line.slice(5).trim()
});
break;
case 'F':
const f = { time: line.slice(1,7), satellites: [] };
for (let i = 7; i < line.length; i += 2) {
f.satellites.push(line.slice(i, i+2));
}
this.properties.F.push(f);
break;
case 'B':
this.properties.B.push({
time: line.slice(1,7),
lat: line.slice(7,15),
lon: line.slice(15,24),
validity: line[24],
pressAlt: parseInt(line.slice(25,30)),
gnssAlt: parseInt(line.slice(30,35)),
extensions: line.slice(35).trim()
});
break;
case 'E':
this.properties.E.push({
time: line.slice(1,7),
code: line.slice(7,10),
data: line.slice(10).trim()
});
break;
case 'K':
this.properties.K.push({
time: line.slice(1,7),
extensions: line.slice(7).trim()
});
break;
case 'L':
this.properties.L.push({
source: line.slice(1,4),
comment: line.slice(4).trim()
});
break;
case 'G':
this.properties.G.push(line.slice(1).trim());
break;
}
});
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 2));
}
write(newFilepath = null) {
let output = '';
// A
const a = this.properties.A;
output += `A${a.manufacturerCode || ''}${a.serialNumber || ''}${a.extension || ''}\n`;
// H
for (const [subtype, value] of Object.entries(this.properties.H)) {
output += `H${subtype}:${value}\n`;
}
// I
const i = this.properties.I;
if (i.numAdditions) {
const num = i.numAdditions.toString().padStart(2, '0');
const adds = (i.additions || []).join('');
output += `I${num}${adds}\n`;
}
// J
const j = this.properties.J;
if (j.numAdditions) {
const num = j.numAdditions.toString().padStart(2, '0');
const adds = (j.additions || []).join('');
output += `J${num}${adds}\n`;
}
// C
this.properties.C.forEach(c => {
output += `C${c.dateTime}${c.lat}${c.lon}${c.desc}\n`;
});
// D
this.properties.D.forEach(d => {
output += `D${d.stationID}${d.type}\n`;
});
// F
this.properties.F.forEach(f => {
const sats = f.satellites.join('');
output += `F${f.time}${sats}\n`;
});
// B
this.properties.B.forEach(b => {
const pressAlt = b.pressAlt.toString().padStart(5, '0');
const gnssAlt = b.gnssAlt.toString().padStart(5, '0');
output += `B${b.time}${b.lat}${b.lon}${b.validity}${pressAlt}${gnssAlt}${b.extensions}\n`;
});
// E
this.properties.E.forEach(e => {
output += `E${e.time}${e.code}${e.data}\n`;
});
// K
this.properties.K.forEach(k => {
output += `K${k.time}${k.extensions}\n`;
});
// L
this.properties.L.forEach(l => {
output += `L${l.source}${l.comment}\n`;
});
// G
this.properties.G.forEach(g => {
output += `G${g}\n`;
});
const path = newFilepath || this.filepath;
if (path) {
fs.writeFileSync(path, output);
}
return output; // For browser download
}
}
// Example usage:
// const igc = new IGCFile('example.igc');
// igc.printProperties();
// igc.properties.H.FPLTPILOT = 'New Pilot'; // Modify
// igc.write('modified.igc');
7. C class for .IGC file handling
C does not have built-in classes, so this uses a struct with functions for open/decode/read, write/modify, and print properties to console. Uses stdio for I/O and dynamic memory for lists (simplified with fixed max sizes for brevity; in production, use linked lists).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_LINES 10000
#define MAX_STR 256
#define MAX_LIST 1000
typedef struct {
char manufacturerCode[4];
char serialNumber[11];
char extension[MAX_STR];
} ARecord;
typedef struct {
char key[MAX_STR];
char value[MAX_STR];
} HPair;
typedef struct {
int numAdditions;
char additions[MAX_LIST][8];
int additionsCount;
} IRecord; // Similar for J
typedef struct {
char dateTime[15];
char lat[9];
char lon[10];
char desc[MAX_STR];
} CRecord;
typedef struct {
char stationID[5];
char type[MAX_STR];
} DRecord;
typedef struct {
char time[7];
char satellites[MAX_LIST][3];
int satellitesCount;
} FRecord;
typedef struct {
char time[7];
char lat[9];
char lon[10];
char validity;
int pressAlt;
int gnssAlt;
char extensions[MAX_STR];
} BRecord;
typedef struct {
char time[7];
char code[4];
char data[MAX_STR];
} ERecord;
typedef struct {
char time[7];
char extensions[MAX_STR];
} KRecord;
typedef struct {
char source[4];
char comment[MAX_STR];
} LRecord;
typedef struct {
char hash[MAX_STR];
} GRecord;
typedef struct {
char* filepath;
ARecord a;
HPair h[MAX_LIST];
int hCount;
IRecord i;
IRecord j; // Reuse struct for J
CRecord c[MAX_LIST];
int cCount;
DRecord d[MAX_LIST];
int dCount;
FRecord f[MAX_LIST];
int fCount;
BRecord b[MAX_LIST];
int bCount;
ERecord e[MAX_LIST];
int eCount;
KRecord k[MAX_LIST];
int kCount;
LRecord l[MAX_LIST];
int lCount;
GRecord g[MAX_LIST];
int gCount;
} IGCData;
void initIGCData(IGCData* data, const char* filepath) {
data->filepath = filepath ? strdup(filepath) : NULL;
memset(&data->a, 0, sizeof(ARecord));
data->hCount = 0;
memset(&data->i, 0, sizeof(IRecord));
memset(&data->j, 0, sizeof(IRecord));
data->cCount = 0;
data->dCount = 0;
data->fCount = 0;
data->bCount = 0;
data->eCount = 0;
data->kCount = 0;
data->lCount = 0;
data->gCount = 0;
}
void readIGC(IGCData* data) {
if (!data->filepath) return;
FILE* fp = fopen(data->filepath, "r");
if (!fp) {
perror("File not found");
return;
}
char line[MAX_STR];
while (fgets(line, sizeof(line), fp)) {
char* trim = strtok(line, "\r\n");
if (!trim || strlen(trim) == 0) continue;
char type = toupper(trim[0]);
switch (type) {
case 'A':
strncpy(data->a.manufacturerCode, trim + 1, 3);
data->a.manufacturerCode[3] = '\0';
strncpy(data->a.serialNumber, trim + 4, strlen(trim + 4) > 3 ? 3 : strlen(trim + 4));
data->a.serialNumber[3] = '\0'; // Simplified
if (strlen(trim) > 7) strncpy(data->a.extension, trim + 7, sizeof(data->a.extension) - 1);
break;
case 'H':
{
char* colon = strchr(trim + 2, ':');
int subtypeLen = colon ? colon - (trim + 2) : strlen(trim + 2);
strncpy(data->h[data->hCount].key, trim + 2, subtypeLen);
data->h[data->hCount].key[subtypeLen] = '\0';
if (colon) strncpy(data->h[data->hCount].value, colon + 1, sizeof(data->h[data->hCount].value) - 1);
data->hCount++;
}
break;
case 'I':
data->i.numAdditions = atoi(trim + 1);
data->i.additionsCount = 0;
for (int idx = 3; idx < strlen(trim); idx += 7) {
strncpy(data->i.additions[data->i.additionsCount++], trim + idx, 7);
}
break;
case 'J':
data->j.numAdditions = atoi(trim + 1);
data->j.additionsCount = 0;
for (int idx = 3; idx < strlen(trim); idx += 7) {
strncpy(data->j.additions[data->j.additionsCount++], trim + idx, 7);
}
break;
case 'C':
strncpy(data->c[data->cCount].dateTime, trim + 1, 14);
strncpy(data->c[data->cCount].lat, trim + 15, 8);
strncpy(data->c[data->cCount].lon, trim + 23, 9);
if (strlen(trim) > 33) strncpy(data->c[data->cCount].desc, trim + 33, sizeof(data->c[data->cCount].desc) - 1);
data->cCount++;
break;
case 'D':
strncpy(data->d[data->dCount].stationID, trim + 1, 4);
if (strlen(trim) > 5) strncpy(data->d[data->dCount].type, trim + 5, sizeof(data->d[data->dCount].type) - 1);
data->dCount++;
break;
case 'F':
strncpy(data->f[data->fCount].time, trim + 1, 6);
data->f[data->fCount].satellitesCount = 0;
for (int idx = 7; idx < strlen(trim); idx += 2) {
strncpy(data->f[data->fCount].satellites[data->f[data->fCount].satellitesCount++], trim + idx, 2);
}
data->fCount++;
break;
case 'B':
strncpy(data->b[data->bCount].time, trim + 1, 6);
strncpy(data->b[data->bCount].lat, trim + 7, 8);
strncpy(data->b[data->bCount].lon, trim + 15, 9);
data->b[data->bCount].validity = trim[24];
char altStr[6];
strncpy(altStr, trim + 25, 5); altStr[5] = '\0';
data->b[data->bCount].pressAlt = atoi(altStr);
strncpy(altStr, trim + 30, 5);
data->b[data->bCount].gnssAlt = atoi(altStr);
if (strlen(trim) > 35) strncpy(data->b[data->bCount].extensions, trim + 35, sizeof(data->b[data->bCount].extensions) - 1);
data->bCount++;
break;
case 'E':
strncpy(data->e[data->eCount].time, trim + 1, 6);
strncpy(data->e[data->eCount].code, trim + 7, 3);
if (strlen(trim) > 10) strncpy(data->e[data->eCount].data, trim + 10, sizeof(data->e[data->eCount].data) - 1);
data->eCount++;
break;
case 'K':
strncpy(data->k[data->kCount].time, trim + 1, 6);
if (strlen(trim) > 7) strncpy(data->k[data->kCount].extensions, trim + 7, sizeof(data->k[data->kCount].extensions) - 1);
data->kCount++;
break;
case 'L':
strncpy(data->l[data->lCount].source, trim + 1, 3);
if (strlen(trim) > 4) strncpy(data->l[data->lCount].comment, trim + 4, sizeof(data->l[data->lCount].comment) - 1);
data->lCount++;
break;
case 'G':
strncpy(data->g[data->gCount].hash, trim + 1, sizeof(data->g[data->gCount].hash) - 1);
data->gCount++;
break;
}
}
fclose(fp);
}
void printIGCProperties(const IGCData* data) {
printf("{\n");
printf(" \"A\": {\n \"manufacturerCode\": \"%s\",\n \"serialNumber\": \"%s\",\n \"extension\": \"%s\"\n },\n", data->a.manufacturerCode, data->a.serialNumber, data->a.extension);
printf(" \"H\": {\n");
for (int idx = 0; idx < data->hCount; idx++) {
printf(" \"%s\": \"%s\"%s\n", data->h[idx].key, data->h[idx].value, idx < data->hCount - 1 ? "," : "");
}
printf(" },\n");
printf(" \"I\": {\n \"numAdditions\": %d,\n \"additions\": [", data->i.numAdditions);
for (int idx = 0; idx < data->i.additionsCount; idx++) {
printf("\"%s\"%s", data->i.additions[idx], idx < data->i.additionsCount - 1 ? ", " : "");
}
printf("]\n },\n");
// Similarly for J (reuse code pattern)
printf(" \"J\": {\n \"numAdditions\": %d,\n \"additions\": [", data->j.numAdditions);
for (int idx = 0; idx < data->j.additionsCount; idx++) {
printf("\"%s\"%s", data->j.additions[idx], idx < data->j.additionsCount - 1 ? ", " : "");
}
printf("]\n },\n");
printf(" \"C\": [\n");
for (int idx = 0; idx < data->cCount; idx++) {
printf(" {\"dateTime\": \"%s\", \"lat\": \"%s\", \"lon\": \"%s\", \"desc\": \"%s\"}%s\n", data->c[idx].dateTime, data->c[idx].lat, data->c[idx].lon, data->c[idx].desc, idx < data->cCount - 1 ? "," : "");
}
printf(" ],\n");
// Repeat similar printf patterns for D, F, B, E, K, L, G
printf(" \"D\": [\n");
for (int idx = 0; idx < data->dCount; idx++) {
printf(" {\"stationID\": \"%s\", \"type\": \"%s\"}%s\n", data->d[idx].stationID, data->d[idx].type, idx < data->dCount - 1 ? "," : "");
}
printf(" ],\n");
printf(" \"F\": [\n");
for (int idx = 0; idx < data->fCount; idx++) {
printf(" {\"time\": \"%s\", \"satellites\": [", data->f[idx].time);
for (int s = 0; s < data->f[idx].satellitesCount; s++) {
printf("\"%s\"%s", data->f[idx].satellites[s], s < data->f[idx].satellitesCount - 1 ? ", " : "");
}
printf("]}%s\n", idx < data->fCount - 1 ? "," : "");
}
printf(" ],\n");
printf(" \"B\": [\n");
for (int idx = 0; idx < data->bCount; idx++) {
printf(" {\"time\": \"%s\", \"lat\": \"%s\", \"lon\": \"%s\", \"validity\": \"%c\", \"pressAlt\": %d, \"gnssAlt\": %d, \"extensions\": \"%s\"}%s\n",
data->b[idx].time, data->b[idx].lat, data->b[idx].lon, data->b[idx].validity, data->b[idx].pressAlt, data->b[idx].gnssAlt, data->b[idx].extensions, idx < data->bCount - 1 ? "," : "");
}
printf(" ],\n");
printf(" \"E\": [\n");
for (int idx = 0; idx < data->eCount; idx++) {
printf(" {\"time\": \"%s\", \"code\": \"%s\", \"data\": \"%s\"}%s\n", data->e[idx].time, data->e[idx].code, data->e[idx].data, idx < data->eCount - 1 ? "," : "");
}
printf(" ],\n");
printf(" \"K\": [\n");
for (int idx = 0; idx < data->kCount; idx++) {
printf(" {\"time\": \"%s\", \"extensions\": \"%s\"}%s\n", data->k[idx].time, data->k[idx].extensions, idx < data->kCount - 1 ? "," : "");
}
printf(" ],\n");
printf(" \"L\": [\n");
for (int idx = 0; idx < data->lCount; idx++) {
printf(" {\"source\": \"%s\", \"comment\": \"%s\"}%s\n", data->l[idx].source, data->l[idx].comment, idx < data->lCount - 1 ? "," : "");
}
printf(" ],\n");
printf(" \"G\": [\n");
for (int idx = 0; idx < data->gCount; idx++) {
printf(" \"%s\"%s\n", data->g[idx].hash, idx < data->gCount - 1 ? "," : "");
}
printf(" ]\n");
printf("}\n");
}
void writeIGC(const IGCData* data, const char* newFilepath) {
const char* path = newFilepath ? newFilepath : data->filepath;
if (!path) return;
FILE* fp = fopen(path, "w");
if (!fp) {
perror("Cannot write file");
return;
}
// Write A
fprintf(fp, "A%s%s%s\n", data->a.manufacturerCode, data->a.serialNumber, data->a.extension);
// Write H
for (int idx = 0; idx < data->hCount; idx++) {
fprintf(fp, "H%s:%s\n", data->h[idx].key, data->h[idx].value);
}
// Write I
if (data->i.numAdditions > 0) {
fprintf(fp, "I%02d", data->i.numAdditions);
for (int idx = 0; idx < data->i.additionsCount; idx++) {
fprintf(fp, "%s", data->i.additions[idx]);
}
fprintf(fp, "\n");
}
// Similarly for J
if (data->j.numAdditions > 0) {
fprintf(fp, "J%02d", data->j.numAdditions);
for (int idx = 0; idx < data->j.additionsCount; idx++) {
fprintf(fp, "%s", data->j.additions[idx]);
}
fprintf(fp, "\n");
}
// Write C
for (int idx = 0; idx < data->cCount; idx++) {
fprintf(fp, "C%s%s%s%s\n", data->c[idx].dateTime, data->c[idx].lat, data->c[idx].lon, data->c[idx].desc);
}
// Write D
for (int idx = 0; idx < data->dCount; idx++) {
fprintf(fp, "D%s%s\n", data->d[idx].stationID, data->d[idx].type);
}
// Write F
for (int idx = 0; idx < data->fCount; idx++) {
fprintf(fp, "F%s", data->f[idx].time);
for (int s = 0; s < data->f[idx].satellitesCount; s++) {
fprintf(fp, "%s", data->f[idx].satellites[s]);
}
fprintf(fp, "\n");
}
// Write B
for (int idx = 0; idx < data->bCount; idx++) {
fprintf(fp, "B%s%s%s%c%05d%05d%s\n", data->b[idx].time, data->b[idx].lat, data->b[idx].lon, data->b[idx].validity, data->b[idx].pressAlt, data->b[idx].gnssAlt, data->b[idx].extensions);
}
// Write E
for (int idx = 0; idx < data->eCount; idx++) {
fprintf(fp, "E%s%s%s\n", data->e[idx].time, data->e[idx].code, data->e[idx].data);
}
// Write K
for (int idx = 0; idx < data->kCount; idx++) {
fprintf(fp, "K%s%s\n", data->k[idx].time, data->k[idx].extensions);
}
// Write L
for (int idx = 0; idx < data->lCount; idx++) {
fprintf(fp, "L%s%s\n", data->l[idx].source, data->l[idx].comment);
}
// Write G
for (int idx = 0; idx < data->gCount; idx++) {
fprintf(fp, "G%s\n", data->g[idx].hash);
}
fclose(fp);
}
// Example usage:
// int main() {
// IGCData data;
// initIGCData(&data, "example.igc");
// readIGC(&data);
// printIGCProperties(&data);
// // Modify example
// strcpy(data.h[0].value, "New Pilot"); // Assume first H is pilot
// writeIGC(&data, "modified.igc");
// free(data.filepath);
// return 0;
// }