Coding project that converts .csv of LLA coordinates to ECEF coordinates. Interpolates velocity at given times.

Program Screenshot

(Expand) Read Me ``` Ross Fischer LLAtoECEF Project 2023/07 Virtualenv, Python 3.11 - To Run: - With terminal: python LLAtoECEF.py - Within IDE, run script - .csv to be clean and without nans Purpose & Reqs: - Convert LLA coordinates to ECEf coordinates - Ability to interpolate velocity vectors at entered/given times. (start w/: t = 1532334000 and 1532335268) Assumptions: - LLA data is provided as csv, csv is acceptable as output. - Desired output data: time, x, y, z, velocity vector, velocity magnitude - Interpolate then print velocity vector to sys.stdout for given times Libraries: - os, sys, tkinter - numpy 1.25 - pandas 2.03 Resources: - "Datum Transformations of GPS Positions", by u-blox ag (www.u-blox.ch), July 5th, 1999 - docs.python.org, numpy.org, w3schools.com - github & stackoverflow communities ```
(Expand) Code ``` import os import sys import tkinter as tk from tkinter import filedialog import numpy as np import pandas as pd class Gui: def __init__(self): self.ecefpath = None # initialize instance variables to avoid Unresolved Attribute Reference self.lla = None self.lladf = pd.DataFrame() self.ecefdf = pd.DataFrame() self.root = tk.Tk() # setup GUI interface/buttons/labels/etc self.root.geometry("1000x500") self.root.title("LLA to ECEF") self.description = tk.Label(self.root, text="Convert LLA coordinates to ECEF coordinates \n by Ross Fischer, July 2023", font=('Arial', 14)) self.description.pack(padx=20, pady=10) self.llaformat = tk.Label(self.root, font=('Arial', 8), text="LLA .csv must have the following columns: \n [Seconds since Unix Epoch]" " [WGS84 Latitude (Degrees)]" " [WGS84 Longitude (Degrees)]" " [WGS84 Altitude (km)]") self.llaformat.pack(padx=5) self.frame1 = tk.Frame(self.root) # setup frame for import/export grid self.frame1.columnconfigure(0, weight=1) self.frame1.columnconfigure(1, weight=1) self.frame1.columnconfigure(2, weight=3) self.frame1.pack(pady=20, ipadx=15) self.lbllla = tk.Label(self.frame1, text="Select LLA .csv", font=('Arial', 10)) self.lbllla.grid(column=0, row=0, sticky=tk.W) self.lblecef = tk.Label(self.frame1, text="Select Output Path", font=('Arial', 10)) self.lblecef.grid(column=0, row=1, sticky=tk.W) self.defaultpathtext = "Select File or Path" self.lblllapath = tk.Label(self.frame1, text=self.defaultpathtext, font=('Arial', 10)) self.lblllapath.grid(column=2, row=0) self.lblecefpath = tk.Label(self.frame1, text=self.defaultpathtext, font=('Arial', 10)) self.lblecefpath.grid(column=2, row=1) self.btnlla = tk.Button(self.frame1, text="Browse", font=('Arial', 10), command=lambda: self.llabutton()) self.btnlla.grid(column=1, row=0) self.btnecef = tk.Button(self.frame1, text="Browse", font=('Arial', 10), command=lambda: self.ecefbutton()) self.btnecef.grid(column=1, row=1) self.btnconvert = tk.Button(self.root, text="Convert to ECEF \n (Overwrite Previous File)", font=('Arial', 14), command=lambda: self.convertbutton()) self.btnconvert.pack(pady=10) self.llaformat = tk.Label(self.root, font=('Arial', 8), text="ECEF .csv output columns: \n [Time (s)]" " [X (m)]" " [Y (m)]" " [Z (m)]" " [X Vel (m/s)]" " [Y Vel (m/s)]" " [Z Vel (m/s)]" " [Velocity (m/s)]") self.llaformat.pack(padx=5, pady=10) # setup frame for interpretation grid self.frame2 = tk.Frame(self.root) self.frame2.columnconfigure(0, weight=1) self.frame2.columnconfigure(1, weight=1) self.frame2.columnconfigure(2, weight=1) self.frame2.columnconfigure(3, weight=1) self.frame2.pack(pady=25, ipadx=15) # initialize Instance variables self.interptime1 = 1532334000 self.interptime2 = 1532335268 self.interpvel1 = None self.interpvel1x = None self.interpvel1y = None self.interpvel1z = None self.interpvel2 = None self.interpvel2x = None self.interpvel2y = None self.interpvel2z = None # labels, entrys, and button for interpolation # Row 0 self.lbltime = tk.Label(self.frame2, text="Start and End Time:", font=('Arial', 10)) self.lbltime.grid(column=0, row=0, sticky=tk.W) self.lbltimestart = tk.Label(self.frame2, text="t_start: (must first convert)", font=('Arial', 10)) self.lbltimestart.grid(column=1, row=0) self.lbltimeend = tk.Label(self.frame2, text="t_end: (must first convert)", font=('Arial', 10)) self.lbltimeend.grid(column=2, row=0) # row 1 self.lblinterp1 = tk.Label(self.frame2, text="Unix Time #1:", font=('Arial', 10)) self.lblinterp1.grid(column=0, row=1, sticky=tk.W) self.txtinterp1 = tk.Entry(self.frame2, width=15, font=('Arial', 10)) self.txtinterp1.insert(tk.INSERT, str(self.interptime1)) self.txtinterp1.grid(column=1, row=1) self.lblinterpvel1 = tk.Label(self.frame2, text="(must first convert)", font=('Arial', 10)) self.lblinterpvel1.grid(column=3, row=1) # row 2 self.lblinterp2 = tk.Label(self.frame2, text="Unix Time #2:", font=('Arial', 10)) self.lblinterp2.grid(column=0, row=2, sticky=tk.W) self.txtinterp2 = tk.Entry(self.frame2, width=15, font=('Arial', 10)) self.txtinterp2.insert(tk.INSERT, str(self.interptime2)) self.txtinterp2.grid(column=1, row=2) self.lblinterpvel2 = tk.Label(self.frame2, text="(must first convert)", font=('Arial', 10)) self.lblinterpvel2.grid(column=3, row=2) # button self.btninterp = tk.Button(self.frame2, text="Interpolate \n Velocity", font=('Arial', 10), command=lambda: self.interpbutton()) self.btninterp.grid(column=2, row=1, rowspan=2) def llabutton(self): # button function to choose LLA .csv self.lla = filedialog.askopenfile(mode='r', filetypes=[('CSV Files', '*.csv')]) if self.lla: self.lblllapath = tk.Label(self.frame1, text=str(os.path.abspath(self.lla.name)), font=('Arial', 10)) self.lblllapath.grid(column=2, row=0) def ecefbutton(self): # button function to choose ECEF output location self.ecefpath = filedialog.askdirectory() + '/Converted_ecef.csv' self.ecefpath = self.ecefpath.replace("/", chr(92)) if self.ecefpath: self.lblecefpath.config(text=str(self.ecefpath), font=('Arial', 10)) def convertbutton(self): # button function to convert LLA to ECEF if self.lla and self.ecefpath: self.lladf = pd.read_csv(os.path.abspath(self.lla.name), header=None) # read LLA .csv into df self.lladf.columns = ['time', 'lat', 'lon', 'alt'] # assign column names rada = float(6378137) # semi-major axis (meters) f = 1 / 298.257223563 # ellipsoid flattening factor radb = rada * (1 - f) # semi-minor axis (meters) ecc = np.sqrt((rada ** 2 - radb ** 2) / (rada ** 2)) # first eccentricity # ecc2 = np.sqrt((rada**2 - radb**2)/(radb**2)) # second eccentricity. Unused n = rada / (np.sqrt(1 - (ecc ** 2 * np.sin(np.radians(self.lladf['lat'])) ** 2))) # radius of curve (m) ecefx = np.array((n + self.lladf['alt'] * 1000) * np.cos(np.radians(self.lladf['lat'])) * np.cos(np.radians(self.lladf['lon']))) # x-coord ecefy = np.array((n + self.lladf['alt'] * 1000) * np.cos(np.radians(self.lladf['lat'])) * np.sin(np.radians(self.lladf['lon']))) # y-coord ecefz = np.array(((n * (radb ** 2) / (rada ** 2)) + self.lladf['alt'] * 1000) * np.sin(np.radians(self.lladf['lat']))) # z-coord timediff = np.concatenate((np.array([0]), np.diff(self.lladf['time']))) ecefxdiff = np.concatenate((np.array([0]), np.diff(ecefx))) # variable differences for vel calc ecefydiff = np.concatenate((np.array([0]), np.diff(ecefy))) ecefzdiff = np.concatenate((np.array([0]), np.diff(ecefz))) ecefposdiff = np.array(np.sqrt(ecefxdiff ** 2 + ecefydiff ** 2 + ecefzdiff ** 2)) ecefxvel = np.concatenate(((np.array([0])), np.array(ecefxdiff[1:] / timediff[1:]))) ecefyvel = np.concatenate(((np.array([0])), np.array(ecefydiff[1:] / timediff[1:]))) ecefzvel = np.concatenate(((np.array([0])), np.array(ecefzdiff[1:] / timediff[1:]))) ecefvel = np.concatenate(((np.array([0])), np.array(ecefposdiff[1:] / timediff[1:]))) self.ecefdf = pd.DataFrame({'time': self.lladf['time'], 'x': ecefx, 'y': ecefy, 'z': ecefz, 'xvel': ecefxvel, 'yvel': ecefyvel, 'zvel': ecefzvel, 'vel': ecefvel}) # construct Dataframe self.ecefdf.to_csv(self.ecefpath, index=False, header=False) self.lbltimestart.config(text="t_start: " + str(self.lladf['time'].iat[0])) # find start and end time self.lbltimeend.config(text="t_end: " + str(self.lladf['time'].iat[-1])) self.interpbutton() popup = tk.Tk() # pop-up confirming conversion popup.geometry("200x100") popup.wm_title("Complete") popuptxt = tk.Label(popup, text="Converted. Check output folder.") popuptxt.pack(side='top', pady=10) btnpopup = tk.Button(popup, text="Okay", command=popup.destroy) btnpopup.pack(pady=5) def interpbutton(self): # button function to interpolate velocity at entered times in GUI if not self.ecefdf.empty: self.interptime1 = float(self.txtinterp1.get()) self.interptime2 = float(self.txtinterp2.get()) if self.interptime1 < self.lladf['time'].iat[0] or self.interptime1 > self.lladf['time'].iat[-1]: self.lblinterpvel1.config(text="Entered Time Out of Range") sys.stdout.write("Entered Time Out of Range") else: self.interpvel1 = np.interp(self.interptime1, self.ecefdf['time'], self.ecefdf['vel']) self.interpvel1x = np.interp(self.interptime1, self.ecefdf['time'], self.ecefdf['xvel']) self.interpvel1y = np.interp(self.interptime1, self.ecefdf['time'], self.ecefdf['yvel']) self.interpvel1z = np.interp(self.interptime1, self.ecefdf['time'], self.ecefdf['zvel']) self.lblinterpvel1.config(text=str(round(self.interpvel1, 2)) + ' (m/s) [x,y,z]=[' + str(round(self.interpvel1x, 2)) + ', ' + str(round(self.interpvel1y, 2)) + ', ' + str(round(self.interpvel1z, 2)) + ']') sys.stdout.write("At t=" + self.txtinterp1.get() + " (s), velocity=" + self.lblinterpvel1.cget("text") + '\n') if self.interptime2 < self.lladf['time'].iat[0] or self.interptime2 > self.lladf['time'].iat[-1]: self.lblinterpvel2.config(text="Entered Time Out of Range") sys.stdout.write("Entered Time Out of Range") else: self.interpvel2 = np.interp(self.interptime2, self.ecefdf['time'], self.ecefdf['vel']) self.interpvel2x = np.interp(self.interptime2, self.ecefdf['time'], self.ecefdf['xvel']) self.interpvel2y = np.interp(self.interptime2, self.ecefdf['time'], self.ecefdf['yvel']) self.interpvel2z = np.interp(self.interptime2, self.ecefdf['time'], self.ecefdf['zvel']) self.lblinterpvel2.config(text=str(round(self.interpvel2, 2)) + ' (m/s) [x,y,z]=[' + str(round(self.interpvel2x, 2)) + ', ' + str(round(self.interpvel2y, 2)) + ', ' + str(round(self.interpvel2z, 2)) + ']') sys.stdout.write("At t=" + self.txtinterp2.get() + " (s), velocity=" + self.lblinterpvel2.cget("text") + '\n\n') if __name__ == "__main__": # comment block in case class arguments are desired # parser = argparse.ArgumentParser() # args = parser.parse_args() # gui = Gui(args) # gui.root.mainloop() gui = Gui() gui.root.mainloop() ```

Updated: