From f9a33a76c6456a8c6fc2ec7aa3b56366a0ebb791 Mon Sep 17 00:00:00 2001 From: Bill Staib Date: Sun, 28 Dec 2025 20:15:58 -0600 Subject: [PATCH] Support parsing .kml files from ADSBExchange ADSBExchange exported .kml files have two idiosyncrasies that require minor adjustments so FlightawareToForeflight will create valid ForeFlight G1000 files from ADSBExchange exports. 1) ADSBExchange lat/long have 6 decimal places but ForeFlight will not process files with more than 4 decimal places. 2) ADSBExchange time stamps have fractional seconds. The prior kml2g1000.py would crash with fractional seconds in input data. Also, the time that is exported time needs to not have fractional seconds for ForeFlight to be able to import the data properly. Note that currently option 2. "Find files directly on flightaware.com and option" 3. "Input a URL" fail to properly download and parse data from FlightAware.com. Although these changes do not attempt to fix those issues, they should not harm it either. Instead, with the code changes, one can use the Export KML feature on ADSBExchange to obtain similar results. --- kml2g1000.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/kml2g1000.py b/kml2g1000.py index 582c51f..409a139 100644 --- a/kml2g1000.py +++ b/kml2g1000.py @@ -25,6 +25,15 @@ def calcSpeed(fm, to, start, end): dt = (end - start).total_seconds() / 3600.0 # hours return round(dx / dt) if dt else 0 +# Parse date time but do not crash if some entries have fractional seconds and others do not. E.g. ADSBExchange exports with fractional seconds +def parse_datetime(date_str): + # Try the version with fractional seconds first + try: + return datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S.%f") + except ValueError: + # Fall back to the version without fractional seconds + return datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") + # Converts a kml tracklog exported from flightaware.com to G1000 csv format. def export(kml, folder=None): if not folder: @@ -38,10 +47,11 @@ def export(kml, folder=None): print("Skipping " + fileName + " (already exists)") return + # G1000 header, format, and trailing commas for data we do not set. hdr = ' Lcl Date, Lcl Time, UTCOfst, AtvWpt, Latitude, Longitude, AltB, BaroA, AltMSL, OAT, IAS, GndSpd, VSpd, Pitch, Roll, LatAc, NormAc, HDG, TRK, volt1, FQtyL, FQtyR, E1 FFlow, E1 FPres, E1 OilT, E1 OilP, E1 MAP, E1 RPM, E1 CHT1, E1 CHT2, E1 CHT3, E1 CHT4, E1 EGT1, E1 EGT2, E1 EGT3, E1 EGT4, AltGPS, TAS, HSIS, CRS, NAV1, NAV2, COM1, COM2, HCDI, VCDI, WndSpd, WndDr, WptDst, WptBrg, MagVar, AfcsOn, RollM, PitchM, RollC, PichC, VSpdG, GPSfix, HAL, VAL, HPLwas, HPLfd, VPLwas' - fmt = '{date}, {time}, 00:00, , {lat: >12}, {lng: >12}, , , {alt: >7}, , , {gspd: >6}' - tail = ', , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ' + fmt = '{date},{time},00:00,,{lat: >12},{lng: >12},,,{alt: >7},,,{gspd: >6}' + tail = ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,' tree = ET.parse(kml) root = tree.getroot() @@ -67,16 +77,24 @@ def export(kml, folder=None): # This is a very rough estimate based on the reported breadcrumbs. # FlightAware appears to collect actual data from ADS-B, but does not include it in the kml unfortuantely. to = (float(lat), float(lng)) - end = datetime.strptime(date + ' ' + time, '%Y-%m-%d %H:%M:%S') + end = parse_datetime(date + ' ' + time) + + # strip fractional sections such as exported from ADSBExchange, which is not compatible with ForeFlight + timenofraction = end.strftime('%H:%M:%S') + gspd = calcSpeed(fm, to, start, end) if fm and start else 0 fm = to start = end # FlightAware KLM altitude is in meters, while G1000 wants feet. alt = round(float(alt) * 3.28084) + + # round lat, long to 4 decimal places because ForeFlight will fail if more than 4 decimal places and ADSBExchange exports 6 decimal places + lat = round(float(lat), 4) + lng = round(float(lng), 4) # Append data with trailing commas for unset values. - csv.append(fmt.format(date=date, time=time, lat=lat, lng=lng, alt=alt, gspd=gspd) + tail) + csv.append(fmt.format(date=date, time=timenofraction, lat=lat, lng=lng, alt=alt, gspd=gspd) + tail) # Write file to disk. with open(fileName, 'w') as f: