class TrackFileReader {

    @NonNull
    private List<MLocation> parse(@NonNull BufferedReader br) throws IOException {
        // Reset initial state
        // State used while scanning track file
        final Filter baroAltitudeFilter = new FilterKalman();
        final Filter gpsAltitudeFilter = new FilterKalman();
        long baroLastNano = -1L;
        long gpsLastMillis = -1L;
        boolean initAltGps = false;
        boolean initAltBaro = false;
        double baroOffset = 0; // gps altitude - pressure altitude

        final List<MLocation> data = new ArrayList<>();

        // Parse header column
        String line = br.readLine();
        final CSVHeader columns = new CSVHeader(line);
        // Add column aliases
        columns.addMapping("timeMillis", "millis");
        // Handle old files that were not FlySight compatible
        columns.addMapping("latitude", "lat");
        columns.addMapping("longitude", "lon");
        columns.addMapping("altitude_gps", "hMSL");

        // Parse data rows
        while ((line = br.readLine()) != null) {
            final String[] row = line.split(",");
            final Integer sensorIndex = columns.get("sensor");
            if (sensorIndex == null || sensorIndex >= row.length) {
                // FlySight
                final long millis = getColumnDate(row, columns, "time");
                if (millis > 0) {
                    final double lat = getColumnDouble(row, columns, "lat");
                    final double lon = getColumnDouble(row, columns, "lon");
                    final double alt_gps = getColumnDouble(row, columns, "hMSL");
                    final double climb = -getColumnDouble(row, columns, "velD");
                    final double vN = getColumnDouble(row, columns, "velN");
                    final double vE = getColumnDouble(row, columns, "velE");
                    if (!Double.isNaN(lat) && !Double.isNaN(lon)) {
                        final MLocation loc = new MLocation(millis, lat, lon, alt_gps, climb, vN, vE, Float.NaN, Float.NaN, Float.NaN, Float.NaN, 0, 0);
                        data.add(loc);
                    }
                }
            } else if (row[sensorIndex].equals("gps")) {
                // BASEline GPS measurement
                final long millis = getColumnLong(row, columns, "millis");
                final double lat = getColumnDouble(row, columns, "lat");
                final double lon = getColumnDouble(row, columns, "lon");
                final double alt_gps = getColumnDouble(row, columns, "hMSL");
                final double vN = getColumnDouble(row, columns, "velN");
                final double vE = getColumnDouble(row, columns, "velE");
                // Update gps altitude filter
                if (gpsLastMillis < 0) {
                    gpsAltitudeFilter.update(alt_gps, 0);
                } else {
                    final double dt = (millis - gpsLastMillis) * 0.001;
                    gpsAltitudeFilter.update(alt_gps, dt);
                }
                // Integrate baro altitude
                if (!Double.isNaN(alt_gps)) {
                    if (initAltBaro && !initAltGps) {
                        // Set the initial altitude offset
                        baroOffset = alt_gps - baroAltitudeFilter.x();
                    }
                }
                double integratedAlt;
                if (initAltBaro && initAltGps) {
                    integratedAlt = baroAltitudeFilter.x() + baroOffset;
                } else {
                    integratedAlt = alt_gps;
                }
                // Climb rate from baro or gps
                double climb = baroAltitudeFilter.v();
                if (baroLastNano < 0 || Double.isNaN(climb)) {
                    climb = gpsAltitudeFilter.v();
                }
                if (!Double.isNaN(lat) && !Double.isNaN(lon)) {
                    final MLocation loc = new MLocation(millis, lat, lon, integratedAlt, climb, vN, vE, Float.NaN, Float.NaN, Float.NaN, Float.NaN, 0, 0);
                    data.add(loc);
                }
                gpsLastMillis = millis;
                initAltGps = true;
            } else if (row[sensorIndex].equals("alt")) {
                // BASEline alti measurement
                final long nano = getColumnLong(row, columns, "nano");
                final double pressure = getColumnDouble(row, columns, "pressure");
                final double pressureAltitude = BaroAltimeter.pressureToAltitude(pressure);
                if (baroLastNano < 0) {
                    baroAltitudeFilter.update(pressureAltitude, 0);
                } else {
                    final double dt = (nano - baroLastNano) * 1E-9;
                    baroAltitudeFilter.update(pressureAltitude, dt);
                }
                // Integrate GPS altitude
                if (!initAltBaro && initAltGps) {
                    // Set the initial altitude offset
                    baroOffset = gpsAltitudeFilter.x() - baroAltitudeFilter.x();
                }
                baroLastNano = nano;
                initAltBaro = true;
            }
        }

        return data;
    }

      TrackFileReader(@NonNull File trackFile);

}