/** * ORcycle, Copyright 2014, 2015, PSU Transportation, Technology, and People Lab. * * ORcycle 2.2.0 has introduced new app features: safety focus with new buttons * to report safety issues and crashes (new questionnaires), expanded trip * questionnaire (adding questions besides trip purpose), app utilization * reminders, app tutorial, and updated font and color schemes. * * @author Bryan.Blanc <bryanpblanc@gmail.com> (code) * @author Miguel Figliozzi <figliozzi@pdx.edu> and ORcycle team (general app * design and features, report questionnaires and new ORcycle features) * * For more information on the project, go to * http://www.pdx.edu/transportation-lab/orcycle and http://www.pdx.edu/transportation-lab/app-development * * Updated/modified for Oregon pilot study and app deployment. * * ORcycle 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. * ORcycle 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 * ORcycle. If not, see <http://www.gnu.org/licenses/>. * ************************************************************************************* * * Cycle Altanta, Copyright 2012 Georgia Institute of Technology * Atlanta, GA. USA * * @author Christopher Le Dantec <ledantec@gatech.edu> * @author Anhong Guo <guoanhong15@gmail.com> * * Updated/Modified for Atlanta's app deployment. Based on the * CycleTracks codebase for SFCTA. * * CycleTracks, Copyright 2009,2010 San Francisco County Transportation Authority * San Francisco, CA, USA * * @author Billy Charlton <billy.charlton@sfcta.org> * * This file is part of CycleTracks. * * CycleTracks 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 * (at your option) any later version. * * CycleTracks 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 CycleTracks. If not, see <http://www.gnu.org/licenses/>. */ package edu.pdx.cecs.orcycle; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.TimeZone; import android.annotation.SuppressLint; import android.content.Context; import android.database.Cursor; import android.location.Location; import android.util.Log; public class TripData { private static final String MODULE_TAG = "TripData"; long tripid; private final static double RESET_START_TIME = 0.0; private double startTime = RESET_START_TIME; private double segmentStartTime = RESET_START_TIME; private double endTime = RESET_START_TIME; private double pauseStartedTime = RESET_START_TIME; private double totalTravelTime = 0.0f; private boolean isPaused = false; private boolean isFinished = false; private int numpoints = 0; private int lathigh, lgthigh, latlow, lgtlow, latestlat, latestlgt; private int status; private float distance; private String info; private String fancystart; private String purp; private String noteComment; private ArrayList<CyclePoint> gpspoints = new ArrayList<CyclePoint>(); CyclePoint startpoint, endpoint; private final DbAdapter mDb; public static int STATUS_INCOMPLETE = 0; public static int STATUS_COMPLETE = 1; public static int STATUS_SENT = 2; /** * Creates a new instance of TripData, and the database * entry. This instance is used for collecting trip data. * @param ctx * @return */ public static TripData createTrip(Context ctx) { TripData t = new TripData(ctx.getApplicationContext()); return t; } /** * Constructor for a new instance of TripData. This constructor * will create a new instance of the data in the database * @param ctx */ private TripData(Context ctx) { mDb = new DbAdapter(ctx.getApplicationContext()); tripid = createTripInDatabase(); initializeData(); } /** * Fetches an existing instance of TripData from the database * @param ctx * @param tripid * @return */ public static TripData fetchTrip(Context ctx, long tripid) { TripData t = new TripData(ctx.getApplicationContext(), tripid); return t; } /** * Constructor for a new instance of TripData. This constructor * will populate the instance with the data already existing in * the database * @param ctx */ private TripData(Context ctx, long tripid) { mDb = new DbAdapter(ctx.getApplicationContext()); this.tripid = tripid; populateDetails(); } /** * Creates an entry in the database for a new trip * @return Returns ID of new trip */ private long createTripInDatabase() { mDb.open(); tripid = mDb.createTrip(); mDb.close(); return tripid; } void initializeData() { endTime = (segmentStartTime = (startTime = System.currentTimeMillis())); pauseStartedTime = RESET_START_TIME; totalTravelTime = 0.0f; isPaused = false; numpoints = 0; latestlat = 800; latestlgt = 800; distance = 0; lathigh = (int) (-100 * 1E6); latlow = (int) (100 * 1E6); lgtlow = (int) (180 * 1E6); lgthigh = (int) (-180 * 1E6); noteComment = purp = fancystart = info = ""; // So that there are not nulls in the database for purpose, // fancyStart, fancyInfo, and notes fields, we set them to blank updateTripPurpose(""); updateTrip("", "", ""); } /** * Get lat/long extremes, etc, from trip record */ void populateDetails() { pauseStartedTime = RESET_START_TIME; isPaused = false; mDb.openReadOnly(); try { Cursor tripdetails = mDb.fetchTrip(tripid); if (tripdetails.getCount() > 0) { try { startTime = tripdetails.getDouble(tripdetails.getColumnIndex(DbAdapter.K_TRIP_START)); segmentStartTime = System.currentTimeMillis(); lathigh = tripdetails.getInt(tripdetails.getColumnIndex(DbAdapter.K_TRIP_LATHI)); latlow = tripdetails.getInt(tripdetails.getColumnIndex(DbAdapter.K_TRIP_LATLO)); lgthigh = tripdetails.getInt(tripdetails.getColumnIndex(DbAdapter.K_TRIP_LGTHI)); lgtlow = tripdetails.getInt(tripdetails.getColumnIndex(DbAdapter.K_TRIP_LGTLO)); status = tripdetails.getInt(tripdetails.getColumnIndex(DbAdapter.K_TRIP_STATUS)); endTime = tripdetails.getDouble(tripdetails.getColumnIndex(DbAdapter.K_TRIP_END)); distance = tripdetails.getFloat(tripdetails.getColumnIndex(DbAdapter.K_TRIP_DISTANCE)); purp = tripdetails.getString(tripdetails.getColumnIndex(DbAdapter.K_TRIP_PURP)); fancystart = tripdetails.getString(tripdetails.getColumnIndex(DbAdapter.K_TRIP_FANCYSTART)); info = tripdetails.getString(tripdetails.getColumnIndex(DbAdapter.K_TRIP_FANCYINFO)); noteComment = tripdetails.getString(tripdetails.getColumnIndex(DbAdapter.K_TRIP_NOTE)); } finally { tripdetails.close(); } } Cursor points = mDb.fetchAllCoordsForTrip(tripid); if (points != null) { numpoints = points.getCount(); points.close(); } } finally { mDb.close(); } } void dropTrip() { mDb.open(); mDb.deleteAllCoordsForTrip(tripid); mDb.deletePauses(tripid); mDb.deleteAnswers(tripid); mDb.deleteTrip(tripid); mDb.close(); } public ArrayList<CyclePoint> getPoints() { // If already built, don't build again! if (gpspoints != null && gpspoints.size() > 0) { return gpspoints; } // Otherwise, we need to query DB and build points from scratch. gpspoints = new ArrayList<CyclePoint>(); try { mDb.openReadOnly(); Cursor points = mDb.fetchAllCoordsForTrip(tripid); int COL_LAT = points.getColumnIndex(DbAdapter.K_POINT_LAT); int COL_LGT = points.getColumnIndex(DbAdapter.K_POINT_LGT); int COL_TIME = points.getColumnIndex(DbAdapter.K_POINT_TIME); int COL_ACC = points.getColumnIndex(DbAdapter.K_POINT_ACC); int COL_ALT = points.getColumnIndex(DbAdapter.K_POINT_ALT); numpoints = points.getCount(); points.moveToLast(); this.endpoint = new CyclePoint(points.getInt(COL_LAT), points.getInt(COL_LGT), points.getDouble(COL_TIME), points.getFloat(COL_ACC), points.getDouble(COL_ALT)); points.moveToFirst(); this.startpoint = new CyclePoint(points.getInt(COL_LAT), points.getInt(COL_LGT), points.getDouble(COL_TIME), points.getFloat(COL_ACC), points.getDouble(COL_ALT)); while (!points.isAfterLast()) { int lat = points.getInt(COL_LAT); int lgt = points.getInt(COL_LGT); double time = points.getDouble(COL_TIME); float acc = (float) points.getDouble(COL_ACC); double alt = points.getDouble(COL_ALT); CyclePoint pt = new CyclePoint(lat, lgt, time, acc, alt); gpspoints.add(pt); // addPointToSavedMap(lat, lgt, time, acc); points.moveToNext(); } points.close(); mDb.close(); } catch (Exception e) { e.printStackTrace(); } // gpspoints.repopulate(); return gpspoints; } public double getStartTime() { return startTime; } public double getEndTime() { return endTime; } public String getPurpose() { return purp; } public String getFormattedStartTime() { return fancystart; } /** * Returns average speed in meters per second * @return */ public float getAvgSpeedMps(boolean isRecording) { double durationSeconds = getDuration(isRecording) / 1000.0f; return (float) ((durationSeconds > 1.0f) ? (distance / durationSeconds): 0); } public void startPause() { if (!isPaused) { double currentTime = System.currentTimeMillis(); // record the beginning of pause time pauseStartedTime = currentTime; totalTravelTime += (currentTime - segmentStartTime); segmentStartTime = RESET_START_TIME; isPaused = true; } } public void finishPause() { double currentTime = System.currentTimeMillis(); // Insert pause data into database try { mDb.open(); mDb.addPauseToTrip(tripid, pauseStartedTime, currentTime); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } finally { mDb.close(); } // Re-initialize start time counters segmentStartTime = currentTime; isPaused = false; } /** * Returns trip duration in milliseconds * @param isRecording * @return trip duration in milliseconds */ public double getDuration (boolean isRecording) { if (isRecording) { if (isPaused) { return totalTravelTime; } else { return totalTravelTime + (System.currentTimeMillis() - segmentStartTime); } } else { return endTime - startTime; } } public float getDistance() { return distance; } boolean addPointNow(Location loc, double currentTime, float dst) { int lat = (int) (loc.getLatitude() * 1E6); int lgt = (int) (loc.getLongitude() * 1E6); // Skip duplicates if (latestlat == lat && latestlgt == lgt) return true; float accuracy = loc.getAccuracy(); double altitude = loc.getAltitude(); float speed = loc.getSpeed(); CyclePoint pt = new CyclePoint(lat, lgt, currentTime, accuracy, altitude, speed); numpoints++; if (isPaused ) { endTime = totalTravelTime; } else { endTime = totalTravelTime + currentTime - segmentStartTime; } distance = dst; latlow = Math.min(latlow, lat); lathigh = Math.max(lathigh, lat); lgtlow = Math.min(lgtlow, lgt); lgthigh = Math.max(lgthigh, lgt); latestlat = lat; latestlgt = lgt; mDb.open(); boolean rtn = mDb.addCoordToTrip(tripid, pt); rtn = rtn && mDb.updateTrip(tripid, lathigh, latlow, lgthigh, lgtlow, distance); mDb.close(); if (!rtn) { Log.e(MODULE_TAG, "Couldn't write trip point to database"); } return rtn; } public void addSensorReadings(double currentTime, String sensorName, int sensorType, int numSamples, float[] averageValues, float[] sumSquareDifferences) { Log.i(MODULE_TAG, "Sensor(time:" + currentTime + ", name: " + sensorName + ", type: " + sensorType + ", numSamples: " + numSamples + ")"); if ((null != averageValues) && (null != sumSquareDifferences)) { mDb.open(); try { mDb.addSensorReadings(currentTime, sensorName, sensorType, numSamples, averageValues, sumSquareDifferences); } catch (Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } finally { mDb.close(); } } } /** * Makes final calculation of endTime and * push trip data to the database */ public void finish() { if (!isFinished) { totalTravelTime += (System.currentTimeMillis() - segmentStartTime); endTime = totalTravelTime; isFinished = true; updateTripPurpose(""); updateTrip("", "", ""); } } public boolean updateTripStatus(int tripStatus) { boolean rtn; mDb.open(); rtn = mDb.updateTripStatus(tripid, tripStatus); mDb.close(); return rtn; } public void updateTripPurpose(String purpose) { // Save the trip details to the phone database. W00t! mDb.open(); mDb.updateTripPurpose(tripid, purpose); mDb.close(); } @SuppressLint("SimpleDateFormat") public void updateTrip(Double start, Double end, float distance, String noteComment) { SimpleDateFormat sdfStart = new SimpleDateFormat("MMMM d, y h:mm a"); String fancyStartTime = sdfStart.format(startTime); Log.v(MODULE_TAG, "Start: " + fancyStartTime); SimpleDateFormat sdfDuration = new SimpleDateFormat("HH:mm:ss"); sdfDuration.setTimeZone(TimeZone.getTimeZone("UTC")); String duration = sdfDuration.format(end - start); String fancyEndInfo = String.format("%1.1f miles, %s", (0.0006212f * distance), duration); // Save the trip details to the phone database. W00t! mDb.open(); mDb.updateTrip(tripid, fancyStartTime, fancyEndInfo, noteComment); mDb.close(); } public void updateTrip(String fancyStartTime, String fancyEndInfo, String noteComment) { // Save the trip details to the phone database. W00t! mDb.open(); mDb.updateTrip(tripid, fancyStartTime, fancyEndInfo, noteComment); mDb.close(); } public String getNoteComment() { return noteComment; } public int getNumPoints() { return numpoints; } public int getStatus() { return status; } }