import xml.etree.ElementTree as etree import datetime from geopy.distance import geodesic import re import argparse def extract(filename, tz, threshold, verbose): ns = {"kml": "http://www.opengis.net/kml/2.2"} outfile = filename.split(".")[0] + ".csv" tree = etree.parse(filename) root = tree.getroot() track1 = root.find("kml:Document/kml:Folder[@id='Tracks']/kml:Folder[@id='track 1']", ns) points = track1.find("kml:Folder[@id='track 1 points']", ns) placemarks = points.findall("kml:Placemark", ns) placemarks_count = len(placemarks) last_elevation = None last_coordinates = None last_timestamp = None first_timestamp = None distance = 0.0 stop_time = 0 elev_gain = 0 elev_loss = 0 if verbose: print(f"Processing {placemarks_count} points") with open(outfile, 'w') as csv_file: csv_file.write("TimeStamp,Elapsed Time (s),Distance (km),Speed (km/h),Elevation (m),Elev. gain (m),Elev. loss (m),Stop time (min)\n") for placemark in placemarks: timestamp = datetime.datetime.strptime(placemark.find("kml:TimeStamp/kml:when", ns).text, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=datetime.timezone(datetime.timedelta(hours=0))) lng_lat_elev = placemark.find("kml:Point/kml:coordinates", ns).text.split(",") longitude = float(lng_lat_elev[0]) latitude = float(lng_lat_elev[1]) elevation = float(lng_lat_elev[2]) if first_timestamp is None: first_timestamp = timestamp if last_coordinates is not None and last_timestamp is not None: delta_km = geodesic(last_coordinates, (latitude, longitude)).km time_spent = (timestamp - last_timestamp).total_seconds() speed = 3600 * delta_km / time_spent if speed < threshold: stop_time = stop_time + time_spent else: distance = distance + delta_km if last_elevation is not None: delta_elev = elevation - last_elevation if delta_elev >= 0: elev_gain += delta_elev else: elev_loss += delta_elev elapsed_time = timestamp - first_timestamp formatted_stop_time = (datetime.datetime(1,1,1) + datetime.timedelta(seconds=stop_time)).strftime("%H:%M:%S") formatted_elapsed_time = (datetime.datetime(1,1,1) + elapsed_time).strftime("%H:%M:%S") formatted_timestamp = timestamp.astimezone(datetime.timezone(datetime.timedelta(hours=tz))).strftime("%Y-%m-%d %H:%M:%S") line = "{},{},{:.3f},{:.1f},{},{:.0f},{:.0f},{}".format(formatted_timestamp, formatted_elapsed_time, distance, speed, elevation, elev_gain, elev_loss, formatted_stop_time) if verbose: print(line) with open(outfile, 'a+') as csv_file: csv_file.write(f"{line}\n") last_coordinates = (latitude, longitude) last_timestamp = timestamp last_elevation = elevation run_time = elapsed_time - datetime.timedelta(seconds=stop_time) avg_speed = distance / run_time.total_seconds() * 3600 formatted_run_time = (datetime.datetime(1,1,1) + run_time).strftime("%H:%M:%S") line = "{},{},,{},,{:.2f}km,,+{:.0f}m{:.0f}m,{:.2f}km/h".format(formatted_elapsed_time, formatted_run_time, formatted_stop_time, distance, elev_gain, elev_loss, avg_speed) if verbose: print(line) with open(outfile, 'a+') as csv_file: csv_file.write("\ntime,run time,,stop time,,distance,,elev. gain-loss,avg. speed\n") csv_file.write(f"{line}\n") print(f"File {outfile} created.") def get_options(): parser = argparse.ArgumentParser(prog='SpeedTable by ClemRz', description='Extracts a CSV speed, elevation and distance table from a KML file.') parser.add_argument('filename', help='The path to the KML file') parser.add_argument('-tz', '--timezone', dest='timezone', default='0', help='The destination timezone') parser.add_argument('-th', '--threshold', dest='threshold', default='3', help='The speed threshold in km/h') parser.add_argument('-v', '--verbose', dest='verbose', help='Enables verbose output', action='store_true') return parser.parse_args() def main(): options = get_options() extract(options.filename, float(options.timezone), float(options.threshold), options.verbose) if __name__ == '__main__': main()