Commits

Colin Dietrich committed 8d6f6db

Cleaning and renaming, kinetamap rev 48.

Comments (0)

Files changed (1)

kinetamap_48.py

-"""Parser and Plotter for the Sparkfun KinetaMap, v48
-Copyright (C) 2011  Colin Dietrich
-
-Requirements:
-python 2.6
-numpy 1.4.0+
-matplotlib 0.99.3+
-
-Useage:
-Select KinetaMap log file either by importing the KinetaMapParser class,
-
-test = KinetaMapParse()
-test.parse('D:\\logs\\my_file')
-    
-or run the tkinter class and select the file with the GUI. Launch using the
-command prompt / shell: >python kinetamap.py
-
-The default directory opened in the GUI can be changed by editing 
-self.default_log_dir in the MainWindow class.
-
-Outputs:
-All output files are saved to the same directory as the data file and are
-named 'input file name' + 'data and time' + 'description'
-
-Comma delimited files
-altitude profile = gps altitude values recorded
-locations only = gps locations with a valid fix
-interpolated locations = gps locations and all interpolated locations for 
-    accelerometer readings
-
-png images
-plotted elevation profiles = gps altitude records in feet and meters
-latitude and lonitude records = scatter plot
-
-kml files
-location only clamped = lat/lon records points clamped to the ground
-location only GPS altitude = lat/lon with GPS altitude plotted relative
-    above the ground. Labels ON includes information on each point.
-interpolated - clamped = lat/lon gps records and interpolated locations
-    with altitude records clamped to the ground. Labels ON includes
-    information on each point.
-interpolated - GPS altitude = lat/lon with GPS altitude plotted relative
-    above the ground. Labels ON includes information on each point.
-interpolated - n acceleration = interpolated locations with n = (x,y,z)
-    acceleration plotted as relative altitude to the ground.
-interpolated - n magnitude = interpolated locations with n = (xy,yz,zx,xyz)
-    vector magnitude of acceleration plotted as relative altitude to 
-    the ground.
-
-text files
-log.txt = contains track statistics and any errors encountered.
-
-Notes:
-Tested using Windows XP and KinetaMap firmware version 1.1.
-Sparkfun KinetaMap product page: http://www.sparkfun.com/products/8725
-
-License:
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or 
-any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-from __future__ import division
-
-import Tkinter
-import tkFileDialog
-import string
-import cStringIO
-import time
-import os
-
-import numpy as np
-import matplotlib.pyplot as plt
-
-class KinetaMapParse():
-    """Functions to parse, clean and analyze a KinetaMap data file.
-    """
-    
-    def __init__(self):
-        
-        self.record_time = time.strftime('20%y_%m_%d - %H_%M_%S')
-        self.statistics = ''
-        self.read_file_error = ''
-        self.parse_error = ''
-        
-        self.converters = {0: lambda s: (s or 0),   # Record number
-                           1: lambda s: (s or 0),   # UTC Time
-                           2: lambda s: (s or 0),   # Acceleration X
-                           3: lambda s: (s or 0),   # Acceleration Y
-                           4: lambda s: (s or 0),   # Acceleration Z
-                           5: lambda s: (s or 0),   # Battery
-                           6: lambda s: (s or 0),   # Fix
-                           7: lambda s: (s or 0),   # Latitude
-                           8: lambda s: (s or 0),   # Latitude Direction
-                           9: lambda s: (s or 0),   # Longitude
-                           10: lambda s: (s or 0),  # Longitude Direction
-                           11: lambda s: (s or 0),  # Altitude
-                           12: lambda s: (s or 0),  # Type
-                           }
-        
-        self.dataType = [('record', np.int32),          # 0
-                          ('time', np.float64),         # 1
-                          ('ax', np.float64),           # 2
-                          ('ay', np.float64),           # 3
-                          ('az', np.float64),           # 4
-                          ('bat', np.float64),          # 5
-                          ('fix', np.int8),             # 6
-                          ('latitude', np.float64),     # 7
-                          ('NS', 'S1'),                 # 8
-                          ('longitude', np.float64),    # 9 
-                          ('EW', 'S1'),                 # 10
-                          ('altitude', np.float64),     # 11
-                          ('type', 'S5'),               # 12
-                          ]
-    
-    def read_file(self, filepath):
-        """Open a KinetaMap data file and return a Numpy Array of data.
-        
-        Before scipy will import the file, the two 'nul' characters the 
-        KinetaMap sometimes outputs at columns [46:48] must be removed.
-        
-        Records are checked for length requirements, GPS fix and then the
-        GPS row records are combined with their immediately previous 
-        accelerometer record.
-        
-        Also, a row number record is inserted at column 0 for later use. 
-        """
-        
-        io_file = cStringIO.StringIO()
-        row_number = -1
-        data_shuffle_check = False
-        
-        with open(filepath) as data_file:
-            for row in data_file:
-                if row_number == -1:
-                    if (row[0:40] != 
-                        'UTC, X, Y, Z, Batt, Fix, Lat., Lat. Dir.'):
-                        return(None)
-                    row_number += 1
-                    continue
-                    
-                row = string.replace(row, '\x00', '')
-                row = row.strip("\n\r")
-                single_row = row.split(",")
-                
-                # Could also to a checksum calc using the GPS string
-                
-                # Skip poorly formed rows of data
-                if len(single_row) != 12:
-                    self.read_file_error += ('Malformed row data: %s\n' %
-                                             single_row)
-                    continue
-                
-                # Skip GPS records that are not correct lengths
-                row_lat = str(single_row[6])
-                if row_lat != '':
-                    row_lat = row_lat.split('.')
-                    if len(str(abs(int(row_lat[0])))) != 4:
-                        self.parse_error += (
-                            'Incorrect GPS data \'%s\' in row: %s\n' % 
-                            (row_lat[0], row))
-                        continue
-                    if len(row_lat[1]) != 4:
-                        self.parse_error += (
-                            'Incorrect GPS data \'%s\' in row: %s\n' % 
-                            (row_lat[0], row))
-                        continue
-                
-                row_lon = str(single_row[8])
-                if row_lon != '':
-                    row_lon = row_lon.split('.')
-                    if len(str(abs(int(row_lon[0])))) != 5:
-                        self.parse_error += (
-                            'Incorrect GPS data \'%s\' in row: %s\n' %
-                            (row_lon[0], row))
-                        continue
-                    if len(row_lon[1]) != 4:
-                        self.parse_error += (
-                            'Incorrect GPS data \'%s\' in row: %s\n' %
-                            (row_lon[0], row))
-                        continue
-                    
-                if single_row[5] == '1':    # 1 or 2 = fix ok from GPS
-                    last_row = single_row
-                    data_shuffle_check = True
-                    continue                
-                
-                if single_row[5] == '2':    # 1 or 2 = fix ok from GPS
-                    last_row = single_row
-                    data_shuffle_check = True
-                    continue
-                
-                if data_shuffle_check == True:
-                    single_row[5:12] = last_row[5:12]
-                    return_row = ",".join(single_row)
-                    return_row = (str(row_number) + 
-                                     ',' + return_row + "\n")
-                    data_shuffle_check = False
-                    
-                else:
-                    return_row = str(row_number) + ',' + row + "\n"
-                    
-                io_file.write(return_row)
-                row_number += 1
-        
-        # Import the stringIO file to a Numpy array
-        np_array = np.genfromtxt(cStringIO.StringIO(io_file.getvalue()),
-                               dtype=self.dataType, delimiter=',',
-                               filling_values={0:0, 1:0, 2:0, 3:0, 4:0, 
-                                               5:0, 6:0, 7:0, 8:0, 9:0, 
-                                               10:0, 11:0, 12:0, })
-        
-        io_file.close()
-        return(np_array)
-    
-    def fix_mask(self, np_array):
-        """Read a Numpy array of KinetaMap data a return a Numpy Array
-        containing only GPS records with a valid fix.
-        
-        For the EM408, any number besides 0 is a type of valid fix.
-        """
-        fm = (np_array['fix'] != 0)
-        return np_array[fm]
-    
-    def decimal_degrees(self, coordinate):
-        """Take a decimal minutes angular measurement and return a decimal
-        degrees angular measurement.
-        
-        The EM408 outputs in latitude ddmm.mmmm and longitude dddmm.mmmm.
-        For calculations, need in latitude dd.dddddd or longitude ddd.dddddd.
-        Input: Latitude in ddmm.mmmm or longitude in dddmm.mmmm
-        Output: Latitude in dd.dddddd or longitude in ddd.dddddd
-        """
-        degree = np.int(coordinate / 100)
-        decimal = degree + ((coordinate - (degree * 100)) / 60)
-        return(decimal)
-    
-    def arc_length(self, lat1, long1, lat2, long2):
-        """Take two points on the globe and return the arc length distance
-        between them.
-        
-        Uses the Spherical Law of Cosines to calculate distances.
-        Input: Two locations in decimal degrees
-        Output: Distance in meters
-        """
-        R = 6371 * 1000    # Radius of Earth in meters
-        distance = R * np.arccos(np.sin(lat1 * (np.pi / 180)) *
-                                np.sin(lat2 * (np.pi / 180)) +
-                                np.cos(lat1 * (np.pi / 180)) *
-                                np.cos(lat2 * (np.pi / 180)) *
-                                np.cos((long2 - long1) * (np.pi / 180))
-                                )
-        return(distance)
-    
-    def clean(self, array):
-        """Read a Numpy array of KinetaMap data and a return a Numpy Array
-        with the location records properly formatted and error checked.
-        
-        Change the latitude and longitude values to decimal degrees, change
-        the notation from NS and EW to negative for S and W, remove GPS 
-        records that have dx/dt > 100 miles / hour = 44 m / s since they are
-        likely incorrect in most applications.
-        """
-        
-        # Vectorize the decimal degree conversion function onto the array
-        vfunc = np.vectorize(self.decimal_degrees)
-        array['latitude'] = vfunc(array['latitude'])
-        array['longitude'] = vfunc(array['longitude'])
-        
-        # If there are S or W entries, multiply by -1
-        for i in array:
-            if i['NS'] == 'S':
-                i['latitude'] = i['latitude'] * -1
-            if i['EW'] == 'W':
-                i['longitude'] = i['longitude'] * -1
-        
-        # Renumber records so array[0] is correct row number
-        for j in range(len(array)):
-            array[j]['record'] = j
-        
-        # dx/dt and dy/dt > 44 m/s, mask it out
-        
-        array_gps = self.fix_mask(array)
-        #array_gps = array
-        dt_r = []
-        
-        for k in range(1, len(array_gps)):
-            
-            kd = self.arc_length(array_gps[k - 1]['latitude'], \
-                                     array_gps[k - 1]['longitude'], \
-                                     array_gps[k]['latitude'], \
-                                     array_gps[k]['longitude'])
-
-            kt = array_gps[k]['time'] - array_gps[k - 1]['time']
-            if (kd != 0) & (kt != 0):
-                kdt = kd / kt
-            else: kdt = 0
-            
-            # If the over land speed is more that 44 m/s (100mph), 
-            # it's probably a data error
-            if kdt > 44:
-                dt_r.append((kd, array_gps[k]['record']))
-                self.parse_error += ('Speed of %s m/s is greater than ' +
-                                    '44m/s from row: %s\n' % 
-                                    (kdt, array_gps[k]))
-                
-            ka = array_gps[k]['altitude'] - array_gps[k - 1]['altitude']
-            if (ka != 0) & (kt != 0) & \
-                np.isfinite(ka) & np.isfinite(kt):
-                kat = ka / kt
-            else: kat = 0
-            
-            # If the rate of elevation change is more that 44 m/s (100mph),
-            # it's probably a data error
-            # Maybe use the GPS checksum?
-            if kat > 100:
-                dt_r.append((ka, array_gps[k - 1]['record']))
-                self.parse_error += ('Elevation change %s is greater than ' +
-                                    '44 m/s in row: %s\n' % 
-                                    (kat, array_gps[k]))
-                
-        dt_mask = np.ones(len(array))
-        for m in dt_r:
-            dt_mask[m[1]] = False
-        
-        dt_mask = np.ma.make_mask(dt_mask)
-        array = array[dt_mask]
-        
-        return(array)
-    
-    def track_stats(self, filepath, npArray):
-        """Read a formatted Numpy array of KinetaMap data and save to disk
-        an comma delimited file with elevation data, two plots of the 
-        elevation profile and a log file with track statistics and 
-        analysis comments.
-        """
-        new_filepath = (filepath[:-4] + ' - ' + self.record_time + 
-                            ' - altitude profile' + '.csv')
-        
-        a_gps = self.fix_mask(npArray)
-        record_time = [a_gps[0]['time'], ]
-        
-        # Save a plot of latitude(t) by longitude(t) at intervals t
-        plt.figure(figsize=(7, 7), dpi=80)
-        plt.clf()
-        plt.axis('equal')
-        plt.title('Latitude and Longitude of Track')
-        plt.xlabel('Longitude')
-        plt.ylabel('Latitude')
-        
-        plt.plot(a_gps['longitude'], a_gps['latitude'], 
-                'ro', label='data points')
-        plt.grid(True)
-        plt.legend(loc='lower right')
-        plt.savefig(filepath[:-4] + 
-                    ' - ' + self.record_time + 
-                    ' - lat lon.png')
-        plt.close()
-        
-        distance = [0, ]
-        distance_total = 0
-        altitude = [a_gps[0]['altitude'], ]
-        altitude_change_final = 0
-        altitude_change_cumulative = 0
-        
-        # Could use a filter to remove drift while stationary...
-        
-        for k in range(1, len(a_gps)):
-            
-            # Distance change
-            k_d = self.arc_length(a_gps[k - 1]['latitude'],
-                                     a_gps[k - 1]['longitude'],
-                                     a_gps[k]['latitude'],
-                                     a_gps[k]['longitude'])
-            
-            if np.isfinite(k_d):
-                distance.append(distance_total + k_d)
-                distance_total += k_d
-            else: 
-                distance.append(distance_total + 0)
-                
-            # Time step (dt)
-            k_t = a_gps[k]['time'] - a_gps[k - 1]['time']
-            
-            # Deal with roll over of 24 hour clock
-            if k_t < 0:
-                k_t = (a_gps[k]['time'] + 
-                           (235960 - a_gps[k - 1]['time']))
-            
-            record_time.append(a_gps[k]['time'])
-            
-            # Altitude change
-            k_a = a_gps[k]['altitude'] - a_gps[k - 1]['altitude']
-            altitude.append(a_gps[k]['altitude'])
-            
-            # Final elevation change
-            altitude_change_final += k_a
-            
-            # Cumulative elevation change
-            if k_a > 0:
-                altitude_change_cumulative += k_a
-            
-        # Save the elevation profile data
-        columns = np.column_stack((record_time, distance, altitude))
-        header = 'Time (UTC), Distance (m), Altitude (m)\n'
-        elevation_profile = open(new_filepath, 'a')
-        elevation_profile.write(header)
-        np.savetxt(elevation_profile, columns, delimiter=',', 
-                   fmt=('%06d', '%1.4f', '%1.1f'))
-        elevation_profile.close()
-        
-        # Save a plot of the elevation profile
-        if os.name == 'nt':
-            filepath_split = filepath.split('\\')
-        elif os.name == 'posix':
-            filepath_split = filepath.split('/')
-        else: filepath_split = filepath
-        
-        distance_array = np.array(distance)
-        altitude_array = np.array(altitude)
-        
-        # Metric plot
-        plt.clf()
-        distance_array_km = distance_array / 1000
-        plt.fill_between(distance_array_km, 
-                         altitude_array, 0, facecolor='green')
-        plt.title('Elevation Profile for:\n %s' % 
-                  filepath_split[-1][:-4])
-        plt.xlabel('Distance (km)')
-        plt.ylabel('Elevation (m)')
-        x_min, x_max = plt.xlim() #@UnusedVariable
-        plt.xlim(x_min, distance_array_km[-1])
-        plot_frame = plt.gcf()
-        plot_frame.set_size_inches(10, 5)
-        plt.savefig(filepath[:-4] + 
-                    ' - ' + self.record_time + 
-                    ' - elevation profile (m).png')
-        
-        # 'Standard' units plot
-        plt.clf()
-        distance_array_miles = distance_array * (3.28 / 5280)
-        plt.fill_between(distance_array_miles, altitude_array * 3.28, 0,
-                         facecolor='green')
-        plt.title('Elevation Profile for:\n %s' % 
-                  filepath_split[-1][:-4])
-        plt.xlabel('Distance (Miles)')
-        plt.ylabel('Elevation (ft)')
-        x_min, x_max = plt.xlim() #@UnusedVariable
-        plt.xlim(x_min, distance_array_miles[-1])
-        plot_frame = plt.gcf()
-        plot_frame.set_size_inches(10, 5)
-        plt.savefig(filepath[:-4] + 
-                    ' - ' + self.record_time + 
-                    ' - elevation profile (ft).png')
-        
-        # Save statistics and a log of the analysis        
-        self.statistics += ('Distance traveled: %s meters (%s feet)\n' %
-                            (distance_total, distance_total * 3.2808399))
-        self.statistics += ('    %s kilometers (%s miles)\n' %
-                            (distance_total / 1000, 
-                             (distance_total * 3.2808399) / 5280))
-        self.statistics += (
-            'Final Elevation Gain/Loss: %s meters (%s feet)\n' %
-            (altitude_change_final, altitude_change_final * 3.2808399))
-        self.statistics += (
-            'Cumulative Elevation Gain: %s meters (%s feet)\n' %
-            (altitude_change_cumulative, 
-             altitude_change_cumulative * 3.2808399))
-        
-        log = open(filepath[:-4] + ' - ' + self.record_time + 
-                      ' - log.txt', 'w')
-        log.write('Working on file: %s\n' % filepath)
-        log.write('Analyzed on %s.\n' % (self.record_time))
-        log.write('--- Statistics ---\n')
-        log.write(self.statistics)
-        if self.read_file_error != '':
-            log.write('--- Source Data Reading ---\n')
-            log.write(self.read_file_error)
-        if self.parse_error != '':
-            log.write('--- Parsing ---\n')
-            log.write(self.parse_error)
-        log.write('--- End of Log ---\n')
-        log.close()
-        
-    def location_save(self, filepath, array, interpolated):
-        """Read a formatted Numpy array of KinetaMap data and save to disk
-        a comma delimited file containing just the GPS location records.
-        """
-        if interpolated == True:
-            suffix = ' - interpolated locations.csv'
-        else: 
-            suffix = ' - location only.csv'
-            array = self.fix_mask(array)
-            
-        new_filepath = (filepath[:-4] + ' - ' + 
-                            self.record_time + suffix)
-        
-        j = np.column_stack((array['time'],
-                                  array['latitude'],
-                                  array['longitude'],
-                                  array['altitude']))
-        header = 'UTC Time,Latitude,Longitude,Altitude (m)\n'
-        f = open(new_filepath, 'a')
-        f.write(header)
-        np.savetxt(f, j, delimiter=',',
-                   fmt=('%6d', '%6.6f', '%6.6f', '%1.3f'))
-        f.close()
-        
-    def KML_points(self, filepath, np_array, type, mag, name_bool):
-        """Read a formatted Numpy array of KinetaMap data and save to disk
-        a KML file containing just the GPS location records.
-        
-        Altitude records are replaced by the magnitude of the vector sum of
-        of the three axis acceleration records.
-        """
-        if type == 'locations':
-            np_array = self.fix_mask(np_array)
-            rec_type = 'locations only'
-            style_color = 'ff00ff00'
-        elif type == 'interpolated':
-            rec_type = 'interpolated'
-            style_color = 'ffffff00'
-        else:
-            rec_type = ''
-            
-        alt_mode = 'relativeToGround'
-        
-        if mag == 'clamp':
-            mag_label = 'clamped'
-            alt_mode = 'clampToGround'
-        elif mag == 'gps':
-            mag_label = 'GPS altitude'
-        elif mag == 'x':
-            mag_label = 'x acceleration'
-        elif mag == 'y':
-            mag_label = 'y acceleration'
-        elif mag == 'z':
-            mag_label = 'z acceleration'
-        elif mag == 'xy':
-            mag_label = 'xy magnitude'
-        elif mag == 'xz':
-            mag_label = 'xz magnitude'
-        elif mag == 'yz':
-            mag_label = 'yz magnitude'
-        elif mag == 'xyz':
-            mag_label = 'xyz magnitude'
-        else:
-            mag_label = 'zero magnitude'
-        
-        if name_bool == True:
-            labels = '[labels ON]'
-        else:
-            labels = '[labels OFF]'
-        
-        new_filepath = (filepath[:-4] + ' - ' + 
-                            self.record_time + ' - ' + rec_type +
-                            ' - ' + mag_label +
-                            ' - ' + labels + '.kml')
-        
-        g = open(new_filepath, "w")
-        
-        # Start building the kml file with header information
-        kml_header = "\
-<?xml version='1.0' encoding='UTF-8'?>\n\
-<kml xmlns='http://www.opengis.net/kml/2.2'>\n\
-    <Document>\n\
-        <name>Path points from " + filepath[:-4] + ' - ' + mag_label + \
-        ' - ' + labels + "</name>\n\
-        <Style id='dot'>\n\
-            <IconStyle>\n\
-                <Icon>\n\
-                    " + \
-"<href>http://maps.google.com/mapfiles/kml/pal2/icon18.png</href>\n\
-                </Icon>\n\
-                <scale>0.25</scale>\n\
-                <color>" + style_color + "</color>\n\
-            </IconStyle>\n\
-        </Style>\n"
-        
-        kml_footer = "\
-    </Document>\n\
-</kml>"
-        # Start out the KML file with the header
-        g.write(kml_header)
-        
-        for element in np_array:
-            kml_place = "\
-        <Placemark>\n"
-        
-            if mag == 'gps':
-                KML_alt = element['altitude']
-            elif mag == 'x':
-                KML_alt = element['ax']
-            elif mag == 'y':
-                KML_alt = element['ay']
-            elif mag == 'z':
-                KML_alt = element['az']
-            elif mag == 'xy':
-                KML_alt = np.power(
-                            np.power(element['ax'], 2) +
-                            np.power(element['ay'], 2), 0.5)
-            elif mag == 'xz':
-                KML_alt = np.power(
-                            np.power(element['ax'], 2) +
-                            np.power(element['az'], 2), 0.5)
-            elif mag == 'yz':
-                KML_alt = np.power(
-                            np.power(element['ay'], 2) +
-                            np.power(element['az'], 2), 0.5)
-            elif mag == 'xyz':
-                KML_alt = np.power(
-                            np.power(element['ax'], 2) +
-                            np.power(element['ay'], 2) +
-                            np.power(element['az'], 2), 0.5)
-            else:
-                KML_alt = 0
-            
-            # Magnitude can only be plotted (and seen) positively in KML maps
-            KML_alt = abs(KML_alt)
-            
-            if name_bool == True:
-                kml_place = (kml_place + "\
-            <name>" + str(element['longitude']) +
-                                    "," + str(element['latitude']) +
-                                    "," + str(KML_alt) + "</name>\n")
-                
-            kml_place = kml_place + "\
-            <styleUrl>#dot</styleUrl>\n\
-            <Point>\n\
-                <altitudeMode>" + alt_mode + "</altitudeMode>\n\
-                <extrude>1</extrude>\n\
-                <coordinates>" + (str(element['longitude']) + ',' + 
-                                  str(element['latitude']) + ',' + 
-                                  str(KML_alt)) + "</coordinates>\n\
-            </Point>\n\
-        </Placemark>\n"
-            g.write(kml_place)
-        
-        # Done adding placemarks, append the KML footer
-        g.write(kml_footer)
-        
-        g.close()
-        return(new_filepath)
-         
-    def interpolate(self, np_array):
-        """Read a formatted Numpy array of KinetaMap data and return a Numpy
-        array with linearly interpolated location records for all 
-        acceleration records.
-        """
-        a_i = np_array
-        a_gps = self.fix_mask(np_array)
-        
-        a_gps_records = a_gps['record']
-
-        for i in range(0, len(a_gps_records)):
-            dr = a_gps_records[i] - a_gps_records[i - 1]
-            
-            lat_dt = (a_i[a_gps_records[i]]['latitude'] - 
-                          a_i[a_gps_records[i - 1]]['latitude'])
-
-            lon_dt = (a_i[a_gps_records[i]]['longitude'] - 
-                          a_i[a_gps_records[i - 1]]['longitude'])
-            alt_dt = (a_i[a_gps_records[i]]['altitude'] - 
-                          a_i[a_gps_records[i - 1]]['altitude'])
-            
-            lat_dt_dr = lat_dt / dr
-            lon_dt_dr = lon_dt / dr
-            alt_dt_dr = alt_dt / dr
-            
-            lat_increment = 0
-            lon_increment = 0
-            alt_increment = 0
-            
-            a_i_records = a_gps_records[i - 1]
-            for j in a_i[ a_gps_records[i - 1]:a_gps_records[i] + 1 ]:
-                if a_i_records == a_gps_records[i - 1]:
-                    lat_increment = a_i[a_i_records]['latitude']
-                    lon_increment = a_i[a_i_records]['longitude']
-                    alt_increment = a_i[a_i_records]['altitude']
-                    a_i_records += 1
-                else:
-                    lat_increment = lat_increment + lat_dt_dr
-                    lon_increment = lon_increment + lon_dt_dr
-                    alt_increment = alt_increment + alt_dt_dr
-                    a_i[a_i_records]['latitude'] = lat_increment
-                    a_i[a_i_records]['longitude'] = lon_increment
-                    a_i[a_i_records]['altitude'] = alt_increment
-                    a_i[a_i_records]['NS'] = \
-                                        a_i[a_gps_records[i - 1]]['NS']
-                    a_i[a_i_records]['EW'] = \
-                                        a_i[a_gps_records[i - 1]]['EW']
-                    a_i_records += 1    
-    
-        # Remove records not bound by GPS data
-        gps_mask = ((a_gps['record'].min() <= a_i['record']) &
-                        (a_i['record'] <= a_gps['record'].max()))
-        
-        a_i = a_i[gps_mask]
-        return(a_i)
-    
-    def interpolated_save(self, filepath, np_array):
-        """Read a formatted Numpy array of KinetaMap data and save to disk
-        a comma delimited file containing GPS records and acceleration 
-        records that include interpolated locations.
-        """
-        new_filepath = (filepath[:-4] + ' ' + self.record_time + 
-                            ' - Interpolated.csv')
-        j = np.column_stack((np_array['time'],
-                                  np_array['ax'],
-                                  np_array['ay'],
-                                  np_array['az'],
-                                  np_array['latitude'],
-                                  np_array['longitude'],
-                                  np_array['altitude']))
-        header = 'UTC Time,x,y,z,Latitude,Longitude,Altitude (m)\n'
-        f = open(new_filepath, 'a')
-        f.write(header)
-        np.savetxt(f, j, delimiter=',',
-                   fmt=('%1.2f', '%1.3f', '%1.3f', 
-                        '%1.3f', '%6.6f', '%6.6f', '%1.3f'))
-        f.close()
-    
-    def parse(self, filepath):
-        """Open a KinetaMap data file and save to disk cleaned data files,
-        KML records and elevation profiles images.
-        
-        This is the main analysis function.
-        """
-        array = self.read_file(filepath)
-        
-        # Check that there are gps locations in the file
-        check = self.fix_mask(array)
-        if len(check) == 0:
-            return(False)
-        
-        # Reformat the latitude and longitude
-        array = self.clean(array)
-        
-        # Calculate track stats, log errors
-        self.track_stats(filepath, array)
-        
-        # Write GPS fixed track points to a .csv file
-        self.location_save(filepath, array, False)
-        
-        # Write the GPS track data to KML files
-        self.KML_points(filepath, array, 'locations', 'gps', True)
-        self.KML_points(filepath, array, 'locations', 'gps', False)
-        self.KML_points(filepath, array, 'locations', 'clamp', False)
-        
-        # Interpolate accelerometer locations between GPS records
-        array_interp = self.interpolate(array)
-        
-        # Write interpolated track data to a .csv file
-        self.location_save(filepath, array_interp, True)
-        
-        # Write interpolated data to KML files ('interpolated' in file name)
-        self.KML_points(filepath, array_interp, 'interpolated', 'clamp', True)
-        self.KML_points(filepath, array_interp, 'interpolated', 'clamp', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'gps', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'x', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'y', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'z', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'xy', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'xz', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'yz', False)
-        self.KML_points(filepath, array_interp, 'interpolated', 'xyz', False)
-        return(True)
-    
-class MainWindow(Tkinter.Tk):
-    """GUI to select KinetaMap files, plot location records and save
-    analyzed data to disk.
-    """
-    
-    def __init__(self, parent):
-        
-        # Instance the KinetaMapParse class
-        self.kmp = KinetaMapParse()
-        
-        # Tkinter setup
-        Tkinter.Tk.__init__(self, parent)
-        
-        self.main_file_name = Tkinter.StringVar()        
-        self.main_status = Tkinter.StringVar()
-        
-        self.file_selected = False
-        self.file_name = ''
-        
-        self.protocol("WM_DELETE_WINDOW", self.quit)
-        
-        # Configuration information
-        self.default_log_dir = 'D:\logs'
-        self.main_status.set('Default directory: %s' % (self.default_log_dir))
-        
-        # Start the windows!
-        self.window()
-            
-    def ask_file(self):
-        try:
-            self.file_name = tkFileDialog.askopenfilename(
-                                title='Select file to parse...',
-                                initialdir=self.default_log_dir)
-            if self.file_name != '':
-                self.main_file_name.set('Working with: %s' % (self.file_name))
-                self.main_status.set('Data file opened successfully.')
-                self.file_selected = True
-        except:
-            self.main_status.set('Error opening file')
-        
-    def parse(self):
-        if self.file_selected:
-            self.main_status.set('Working on : %s' % (self.file_name))
-            parse_result = self.kmp.parse(self.file_name)
-            if parse_result == None:
-                self.main_status.set('Problem opening: %s' % (self.file_name))
-            elif parse_result == False:
-                self.main_status.set('No GPS fixes in: %s' % (self.file_name))
-            else:
-                self.main_status.set('Successfully parsed and saved!')
-        else:
-            self.main_status.set('No data file selected.')
-        
-    def quit(self):
-        self.destroy()
-
-    def window(self):
-        
-        # Window format options
-        font_1 = ('Arial', 8, 'bold')
-        self.title('KinetaMap Data Parser')
-        
-        # Window position and layout
-        w_width, w_height = 540, 110
-        s_width = self.winfo_screenwidth()
-        s_height = self.winfo_screenheight()
-        x = (s_width / 2) - (w_width / 2)
-        y = (s_height / 2) - (w_height / 2)
-        self.geometry('%dx%d+%d+%d' % (w_width, w_height, x, y))
-
-        # Window Grid Elements
-        tk_row = 0    # Header row labels
-        Tkinter.Label(self, text='Data File:', font=font_1).\
-            grid(row=tk_row, column=0, sticky='W')
-        
-        tk_row = 1
-        Tkinter.Label(self, textvariable=self.main_file_name).\
-            grid(row=tk_row, column=0, columnspan=3, sticky='W')
-        
-        tk_row = 2
-        Tkinter.Label(self, text='Status:', font=font_1).\
-            grid(row=tk_row, column=0, sticky='W')
-        
-        tk_row = 3
-        Tkinter.Label(self, textvariable=self.main_status).\
-            grid(row=tk_row, column=0, columnspan=3, sticky='W')
-             
-        tk_row = 4
-        Tkinter.Button(self, text="Select File", fg="blue", font=font_1,
-                       command=self.ask_file).grid(
-                                                  row=tk_row, 
-                                                  column=0, 
-                                                  padx='10', 
-                                                  sticky='W')
-        
-        Tkinter.Button(self, text="Parse", fg="green", font=font_1,
-                       command=self.parse).grid(
-                                                row=tk_row, 
-                                                column=1, 
-                                                padx='10')
-        
-        Tkinter.Button(self, text="Quit", fg="red", font=font_1,
-                       command=self.quit).grid(
-                                               row=tk_row, 
-                                               column=2, 
-                                               padx='10')
-        
-if __name__ == '__main__':
-    go = MainWindow(None)
-    go.mainloop()
-    #test = KinetaMapParse()
-    #test.parse('D:\\logs\\my_file')
-# eof