/* * ShareNav - Copyright (c) 2008 Kai Krueger apmonkey at users dot sourceforge dot net * Copyright (c) 2009 Markus Baeurle mbaeurle at users dot sourceforge dot net * See COPYING */ package net.sharenav.sharenav.data; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.Integer; import java.lang.Math; import java.util.Calendar; import java.util.Date; import java.util.Vector; import javax.microedition.rms.InvalidRecordIDException; import javax.microedition.rms.RecordEnumeration; import javax.microedition.rms.RecordStore; import javax.microedition.rms.RecordStoreException; import javax.microedition.rms.RecordStoreFullException; import javax.microedition.rms.RecordStoreNotFoundException; import javax.microedition.rms.RecordStoreNotOpenException; import net.sharenav.gps.Node; import net.sharenav.sharenav.importexport.ExportSession; import net.sharenav.sharenav.importexport.GpxImportHandler; import net.sharenav.sharenav.importexport.GpxParser; import net.sharenav.sharenav.tile.GpxTile; import net.sharenav.sharenav.tile.Tile; import net.sharenav.sharenav.tile.WaypointsTile; import net.sharenav.sharenav.ui.GuiNameEnter; import net.sharenav.sharenav.ui.Trace; import net.sharenav.midlet.ui.InputListener; import net.sharenav.midlet.ui.UploadListener; import net.sharenav.util.DateTimeTools; import net.sharenav.util.HelperRoutines; import net.sharenav.util.Logger; import net.sharenav.util.MoreMath; import net.sharenav.util.ProjMath; import de.enough.polish.util.Locale; /** * Handles pretty much everything that has to do with tracks and waypoints: * <ul> * <li>Recording tracks,</li> * <li>Adding waypoints,</li> * <li>Deleting, Renaming,</li> * <li>Storing in RecordStores,</li> * <li>Exporting and Importing in GPX format.</li> * </ul> */ public class Gpx extends Tile implements Runnable, InputListener { // statics for user-defined rules for track recording private static long oldMsTime; private static float oldlat; private static float oldlon; private static float oldheight; private final static Logger logger = Logger.getInstance(Gpx.class, Logger.DEBUG); private RecordStore trackDatabase = null; private RecordStore wayptDatabase = null; private int trackDatabaseRecordId = -1; /** Counts how many track points were recorded so far. */ private int mTrkRecorded = 0; /** Counts how many tracke segments were recorded so far - subtracted from user-visible point count. */ private int mTrkSegments = 0; public int delay = 0; private float trkOdo; private float trkVertSpd; private float trkVmax; private int trkTimeTot; private Thread processorThread = null; private String url = null; private String waypointsSaveFileName = null; /** Value for mJobState: No job */ private static final int JOB_IDLE = 0; /** Value for mJobState: Reloading waypoints */ private static final int JOB_RELOAD_WPTS = 1; /** Value for mJobState: Exporting tracks to GPX */ private static final int JOB_EXPORT_TRKS = 2; /** Value for mJobState: Exporting waypoints to GPX */ private static final int JOB_EXPORT_WPTS = 3; /** Value for mJobState: Importing GPX data */ private static final int JOB_IMPORT_GPX = 4; /** Value for mJobState: Deleting tracks */ private static final int JOB_DELETE_TRKS = 5; /** Value for mJobState: Deleting waypoints */ private static final int JOB_DELETE_WPTS = 6; /** Value for mJobState: Saving track to RecordStore */ private static final int JOB_SAVE_TRK = 7; /** State of job processing in run(). */ private int mJobState; /** Flag whether user is currently being asked for name of track at start of recording. */ private boolean mEnteringGpxNameStart = false; /** Flag whether user is currently being asked for name of track at end of recording. */ private boolean mEnteringGpxNameStop = false; private boolean applyRecordingRules = true; private float maxDistance; /** Actual name of the track to be saved */ private String trackName; /** Original name of the track to be saved */ private String origTrackName; /** Vector of tracks to be exported */ private Vector exportTracks; /** Vector of tracks to be deleted */ private Vector mTrksToDelete; /** Track that is currently being exported */ private PersistEntity currentTrk; /** Vector of way point IDs to be deleted */ private Vector mWayPtIdsToDelete; private UploadListener feedbackListener; private String importExportMessage; /** Stream used for importing GPX data */ private InputStream mImportStream; /** Primary stream used to hold the track points that are recorded. */ private DataOutputStream mTrkOutStream; /** The stream dos forwards his data to this stream. */ private ByteArrayOutputStream mTrkByteOutStream; /** Flag whether track recording is currently suspended. */ private boolean trkRecordingSuspended; /** Holds the track that is currently recorded. */ private final GpxTile trackTile; /** Holds tracks that are loaded to be displayed but not altered by recording more trackpoints. */ private final GpxTile loadedTracksTile; /** Holds all waypoints to display them on the map. */ private final WaypointsTile wayPtTile; /** Default constructor of this class. * It creates new tiles for waypoints, the recording track and loaded tracks. * It also triggers loading of the waypoints from the RecordStore. */ public Gpx() { trackTile = new GpxTile(); loadedTracksTile = new GpxTile(true); wayPtTile = new WaypointsTile(); reloadWayPts(); } public void displayWaypoints(boolean displayWpt) { } /** * Loads one given track and displays it on the map-screen * @param trk The track to be displayed */ public void displayTrk(PersistEntity trk) { if (trk == null) { //TODO: } else { try { trackTile.dropTrk(); openTrackDatabase(); DataInputStream dis1 = new DataInputStream(new ByteArrayInputStream(trackDatabase.getRecord(trk.id))); trackName = dis1.readUTF(); mTrkRecorded = dis1.readInt(); mTrkSegments = 0; int trackSize = dis1.readInt(); byte[] trackArray = new byte[trackSize]; dis1.read(trackArray); DataInputStream trackIS = new DataInputStream(new ByteArrayInputStream(trackArray)); for (int i = 0; i < mTrkRecorded; i++) { float lat = trackIS.readFloat(); float lon = trackIS.readFloat(); if (i == 0) { Trace tr = Trace.getInstance(); tr.receivePosition(lat * MoreMath.FAC_DECTORAD, lon * MoreMath.FAC_DECTORAD, tr.scale); } trackIS.readShort(); //altitude long time = trackIS.readLong(); //Time trackIS.readByte(); //Speed if (time > Long.MIN_VALUE + 10) { // We use some special markers in the Time to indicate // data other than trackpoints, so ignore these. trackTile.addTrkPt(lat, lon, false); } } dis1.close(); dis1 = null; trackDatabase.closeRecordStore(); trackDatabase = null; } catch (IOException e) { logger.exception(Locale.get("gpx.IOExceptionDisplayingTrack")/*IOException displaying track*/, e); } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionDisplayingTrackDBNotOpen")/*Exception displaying track (database not open)*/, e); } catch (InvalidRecordIDException e) { logger.exception(Locale.get("gpx.ExceptionDisplayingTrackIDInvalid")/*Exception displaying track (ID invalid)*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionDisplayingTrack")/*Exception displaying track*/, e); } } } /** * Loads the given tracks to display them on the map-screen * @param trks Vector of tracks to be displayed */ public void displayTrk(Vector trks) { if (trks == null) { //TODO: } else { try { loadedTracksTile.dropTrk(); openTrackDatabase(); for (int j = 0; j< trks.size(); j++) { PersistEntity track = (PersistEntity)trks.elementAt(j); DataInputStream dis1 = new DataInputStream(new ByteArrayInputStream(trackDatabase.getRecord(track.id))); trackName = dis1.readUTF(); mTrkRecorded = dis1.readInt(); mTrkSegments = 0; int trackSize = dis1.readInt(); byte[] trackArray = new byte[trackSize]; dis1.read(trackArray); DataInputStream trackIS = new DataInputStream(new ByteArrayInputStream(trackArray)); for (int i = 0; i < mTrkRecorded; i++) { float lat = trackIS.readFloat(); float lon = trackIS.readFloat(); //center map on track start if (i == 0 && j == 0) { Trace tr = Trace.getInstance(); tr.receivePosition(lat * MoreMath.FAC_DECTORAD, lon * MoreMath.FAC_DECTORAD, tr.scale); } trackIS.readShort(); //altitude long time = trackIS.readLong(); //Time trackIS.readByte(); //Speed if (time > Long.MIN_VALUE + 10) { //We use some special markers in the Time to indicate //Data other than trackpoints, so ignore these. loadedTracksTile.addTrkPt(lat, lon, false); } } dis1.close(); dis1 = null; } trackDatabase.closeRecordStore(); trackDatabase = null; } catch (OutOfMemoryError oome) { loadedTracksTile.dropTrk(); try { trackDatabase.closeRecordStore(); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionClosingRecordstoreAfterOutOfMemoryErrorDisplayingTracks")/*Exception closing Recordstore after OutOfMemoryError displaying tracks*/, e); } trackDatabase = null; System.gc(); } catch (IOException e) { logger.exception(Locale.get("gpx.IOExceptionDisplayingTrack")/*IOException displaying track*/, e); } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionDisplayingTrackDBNotOpen")/*Exception displaying track (database not open)*/, e); } catch (InvalidRecordIDException e) { logger.exception(Locale.get("gpx.ExceptionDisplayingTrackIDInvalid")/*Exception displaying track (ID invalid)*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionDisplayingTrack")/*Exception displaying track*/, e); } } } /** * Loads the first given track to replay it on the map-screen * @param trks Vector of tracks to be replayed */ public void replayTrk(Vector trks) { if (trks == null) { //TODO: } else { try { openTrackDatabase(); PersistEntity track = (PersistEntity)trks.firstElement(); DataInputStream dis1 = new DataInputStream(new ByteArrayInputStream(trackDatabase.getRecord(track.id))); trackName = dis1.readUTF(); mTrkRecorded = dis1.readInt(); mTrkSegments = 0; //#debug debug logger.debug("Replay track with " + mTrkRecorded + " points"); int trackSize = dis1.readInt(); byte[] trackArray = new byte[trackSize]; dis1.read(trackArray); DataInputStream trackIS = new DataInputStream(new ByteArrayInputStream(trackArray)); float trkPtLat[] = new float[mTrkRecorded]; float trkPtLon[] = new float[mTrkRecorded]; long trkPtTime[] = new long[mTrkRecorded]; short trkPtSpeed[] = new short[mTrkRecorded]; for (int i = 0; i < mTrkRecorded; i++) { trkPtLat[i] = trackIS.readFloat(); trkPtLon[i] = trackIS.readFloat(); trackIS.readShort(); //altitude trkPtTime[i] = trackIS.readLong(); //Time trkPtSpeed[i] = trackIS.readByte(); //Speed } TrackPlayer.getInstance().playTrack(trkPtLat, trkPtLon, trkPtTime, trkPtSpeed); dis1.close(); dis1 = null; trackDatabase.closeRecordStore(); trackDatabase = null; } catch (OutOfMemoryError oome) { try { trackDatabase.closeRecordStore(); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionClosingRecordstoreOOM")/*Exception closing Recordstore after OutOfMemoryError replaying tracks*/, e); } trackDatabase = null; System.gc(); } catch (IOException e) { logger.exception(Locale.get("gpx.IOExceptionReplayingTrack")/*IOException replaying track*/, e); } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionReplayingTrackDBNotOpen")/*Exception replaying track (database not open)*/, e); } catch (InvalidRecordIDException e) { logger.exception(Locale.get("gpx.ExceptionReplayingTrackIDInvalid")/*Exception replaying track (ID invalid)*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionReplayingTrack")/*Exception replaying track*/, e); } } } /** * Removes all loaded tracks from the screen */ public void undispLoadedTracks() { loadedTracksTile.dropTrk(); } /** Adds a waypoint to the RecordStore. * Will also write a marker with the ID of this waypoint to the recording track * if this is enabled in the configuration (CFGBIT_WPTS_IN_TRACK). * @param waypt The waypoint to add */ public void addWayPt(PositionMark waypt) { byte[] buf = waypt.toByte(); try { openWayPtDatabase(); int id = wayptDatabase.addRecord(buf, 0, buf.length); waypt.id = id; wayptDatabase.closeRecordStore(); wayptDatabase = null; if ( isRecordingTrk() && Configuration.getCfgBitState(Configuration.CFGBIT_WPTS_IN_TRACK)) { // store waypoint in GPX track //#debug info logger.info("Adding waypoint in GPX track: " + waypt); /** * Add a marker to the recording for the waypoint */ mTrkOutStream.writeFloat(0.0f); mTrkOutStream.writeFloat(0.0f); mTrkOutStream.writeShort(id); mTrkOutStream.writeLong(Long.MIN_VALUE + 1); mTrkOutStream.writeByte(0); mTrkRecorded++; } } catch (IOException ioe) { logger.exception(Locale.get("gpx.FailedWritingWaypointIntoTrack")/*Failed to write waypoint into track*/, ioe); } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionStoringWaypointDBNotOpen")/*Exception storing waypoint (database not open)*/, e); } catch (RecordStoreFullException e) { logger.exception(Locale.get("gpx.RecordStoreFull")/*Record store is full, could not store waypoint*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionStoringWaypoint")/*Exception storing waypoint*/, e); } wayPtTile.addWayPt(waypt); } /** Updates a waypoint in the RecordStore. That is, the waypoint's ID is * searched and its data is replaced. * @param waypt New "version" of the waypoint */ public void updateWayPt(PositionMark waypt) { byte[] buf = waypt.toByte(); try { openWayPtDatabase(); wayptDatabase.setRecord(waypt.id, buf, 0, buf.length); wayptDatabase.closeRecordStore(); wayptDatabase = null; } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionUpdatingWaypointDBNotOpen")/*Exception updating waypoint (database not open)*/, e); } catch (RecordStoreFullException e) { logger.exception(Locale.get("gpx.RecordStoreFull")/*Record store is full, could not update waypoint*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionUpdatingWaypoint")/*Exception updating waypoint*/, e); } } /** Checks if a waypoint exists in the waypoint tile(s). * * @param wayPt Waypoint to search * @return True if exists, false if it doesn't */ public boolean existsWayPt(PositionMark wayPt) { if (wayPtTile != null) { return wayPtTile.existsWayPt(wayPt); } return false; } /** Adds a trackpoint to the track's output stream and the trackTile. * This is only done if the recording rules apply and recording is not suspended. * Will also update the odometer data (max speed, distance travelled etc etc) * * @param trkpt Trackpoint to be added */ public void addTrkPt(Position trkpt) { if (trkRecordingSuspended) { return; } //#debug debug logger.debug("Adding trackpoint: " + trkpt); long msTime = trkpt.timeMillis; float lat = trkpt.latitude * MoreMath.FAC_DECTORAD; float lon = trkpt.longitude * MoreMath.FAC_DECTORAD; float distance = 0.0f; boolean doRecord=false; try { // always record when i.e. receiving or loading tracklogs // or when starting to record if (!applyRecordingRules || mTrkRecorded==0) { doRecord=true; distance = ProjMath.getDistance(lat, lon, oldlat, oldlon); } // check which record rules to apply else if (Configuration.getGpxRecordRuleMode() == Configuration.GPX_RECORD_ADAPTIVE) { /** adaptive recording * * When saving tracklogs and adaptive recording is enabled, * we reduce the frequency of saved samples if the speed drops * to less than a certain amount. This should increase storage * efficiency if one doesn't need to repeatedly * store positions if the device is not moving * * Chose the following arbitrary sampling frequency: * Greater 8km/h (2.22 m/s): every sample * Greater 4km/h (1.11 m/s): every second sample * Greater 2km/h (0.55 m/s): every fourth sample * Below 2km/h (0.55 m/s): every tenth sample */ if ( (trkpt.speed > 2.222f) || ((trkpt.speed > 1.111f) && (delay > 0 )) || ((trkpt.speed > 0.556) && delay > 3 ) || (delay > 10)) { doRecord = true; distance = ProjMath.getDistance(lat, lon, oldlat, oldlon); delay = 0; } else { delay++; } } else { /* * User-specified recording rules */ distance = ProjMath.getDistance(lat, lon, oldlat, oldlon); if ( // is not always record distance not set // or always record distance reached ( Configuration.getGpxRecordAlwaysDistanceCentimeters() !=0 && 100 * distance >= Configuration.getGpxRecordAlwaysDistanceCentimeters() ) || ( ( // is minimum time interval not set // or interval at least minimum interval? Configuration.getGpxRecordMinMilliseconds() == 0 || Math.abs(msTime - oldMsTime) >= Configuration.getGpxRecordMinMilliseconds() ) && ( // is minimum distance not set // or distance at least minimum distance? Configuration.getGpxRecordMinDistanceCentimeters() == 0 || 100 * distance >= Configuration.getGpxRecordMinDistanceCentimeters() ) ) ) { doRecord = true; } } if (doRecord) { // Add point to the recording track's stream mTrkOutStream.writeFloat(trkpt.latitude); mTrkOutStream.writeFloat(trkpt.longitude); mTrkOutStream.writeShort((short)trkpt.altitude); mTrkOutStream.writeLong(trkpt.timeMillis); mTrkOutStream.writeByte((byte)(trkpt.speed * 3.6f)); //Convert to km/h mTrkRecorded++; // Add point to the recording track's tile trackTile.addTrkPt(trkpt.latitude, trkpt.longitude, false); // we need to redraw Trace.getInstance().newDataReady(); // Update odometer data (max speed, distance travelled etc etc) if ((oldlat != 0.0f) || (oldlon != 0.0f)) { trkOdo += distance; long timeDelta = msTime - oldMsTime; float deltaV = trkpt.altitude - oldheight; trkTimeTot += timeDelta; if (timeDelta > 300000) { trkVertSpd = deltaV / timeDelta * 1000.0f; } else { //TODO: This formula is not consistent and needs improvement!! float alpha = (300000 - timeDelta) / 300000.0f; // System.out.println("trkVertSpeed: " + trkVertSpd + // " timeDelta: " + timeDelta + " Alpha: " + alpha + // " deltaV " + deltaV + " instVertSpeed: " + // (deltaV / timeDelta * 1000.0f)); trkVertSpd = (trkVertSpd * alpha) + ((1 - alpha) * (deltaV / timeDelta * 1000.0f)); } if (trkVmax < trkpt.speed) { trkVmax = trkpt.speed; } } oldMsTime = msTime; oldlat = lat; oldlon = lon; oldheight = trkpt.altitude; // Write track to the RecordStore every 255 points to reduce the // data loss in case the application crashes. // TODO: This is done synchronously. Couldn't this be a problem if // this operation is very slow (device busy, many points to write)? if ((mTrkRecorded & 0xff) == 0xff) { storeTrk(); } } } catch (OutOfMemoryError oome) { try { Trace.getInstance().dropCache(); logger.info("Was out of memory, but we might have recovered"); }catch (OutOfMemoryError oome2) { logger.fatal(Locale.get("gpx.OOMCantAddTrackpoint")/*Out of memory, can not add trackpoint*/); } } catch (IOException e) { logger.exception(Locale.get("gpx.CouldNotAddTrackpoint")/*Could not add trackpoint*/, e); } } /** Triggers deletion of waypoints from the RecordStore. * * @param wayptIds Vector of waypoint IDs to be deleted * @param ul Listener for progress updates */ public void deleteWayPts(Vector wayptIds, UploadListener ul) { mWayPtIdsToDelete = wayptIds; feedbackListener = ul; startProcessorThread(JOB_DELETE_WPTS); } /** Actually deletes waypoints from the RecordStore. * Uses the IDs in mWayPtIdsToDelete for this */ public boolean doDeleteWayPts() { try { openWayPtDatabase(); for (int i = 0; i < mWayPtIdsToDelete.size(); i++) { wayptDatabase.deleteRecord(((Integer)mWayPtIdsToDelete.elementAt(i)).intValue()); } wayptDatabase.closeRecordStore(); wayptDatabase = null; mWayPtIdsToDelete = null; importExportMessage = Locale.get("gpx.Success")/*Success!*/; return true; } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionDeletingWaypoint")/*Exception deleting waypoint (database not open)*/, e); importExportMessage = Locale.get("gpx.ExceptionDeletingWaypoint")/*Exception deleting waypoint (database not open):*/ + " " + e.getMessage(); return false; } catch (InvalidRecordIDException e) { logger.exception(Locale.get("gpx.ExceptionDeletingWaypointIDInvalid")/*Exception deleting waypoint (ID invalid)*/, e); importExportMessage = Locale.get("gpx.ExceptionDeletingWaypointIDInvalid")/*Exception deleting waypoint (ID invalid): */ + e.getMessage(); return false; } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionDeletingWaypoint2")/*Exception deleting waypoint*/, e); importExportMessage = Locale.get("gpx.ExceptionDeletingWaypoint2")/*Exception deleting waypoint: */ + e.getMessage(); return false; } } /** Signals to reload the waypoints. */ public void reloadWayPts() { if (processorThread != null && processorThread.isAlive()) { logger.info("ProcessorThread busy, not triggering JOB_RELOAD_WPTS"); /* Already reloading, nothing to do */ /* TODO: This is not correct, the thread might be busy with another * operation and then the reload will not be done. */ return; } startProcessorThread(JOB_RELOAD_WPTS); } /** Starts the recording of a new track. */ public void doNewTrk() { mTrkByteOutStream = new ByteArrayOutputStream(); mTrkOutStream = new DataOutputStream(mTrkByteOutStream); trackDatabaseRecordId = -1; oldlat = 0.0f; oldlon = 0.0f; trkOdo = 0.0f; trkVmax = 0.0f; trkVertSpd = 0.0f; trkTimeTot = 0; mTrkRecorded = 0; mTrkSegments = 0; trkRecordingSuspended = false; origTrackName = new String(trackName); } /** Starts the recording of a new track. An intermediate step may be to ask * the user for the name of the track. * @param dontAskName If true, the user will not be asked for the track name, * no matter what the configuration says. */ public void newTrk(boolean dontAskName) { newTrk(null, dontAskName); } /** Starts the recording of a new track. An intermediate step may be to ask * the user for the name of the track. * @param newTrackName Name of track * @param dontAskName If true, the user will not be asked for the track name, * no matter what the configuration says. */ public void newTrk(String newTrackName, boolean dontAskName) { logger.debug("Starting a new track recording"); trackTile.dropTrk(); Calendar cal = Calendar.getInstance(); cal.setTime(new Date()); //Construct a track name from the current time if (newTrackName == null) { StringBuffer trkName = new StringBuffer(); // TODO: Should we change the format? trkName.append(cal.get(Calendar.YEAR)).append("-").append(DateTimeTools.formatInt2(cal.get(Calendar.MONTH) + 1)); trkName.append("-").append(DateTimeTools.formatInt2(cal.get(Calendar.DAY_OF_MONTH))).append("_"); trkName.append(DateTimeTools.formatInt2(cal.get(Calendar.HOUR_OF_DAY))).append("-").append(DateTimeTools.formatInt2(cal.get(Calendar.MINUTE))); trackName = trkName .toString(); } else { // TODO: what to do if track with this name exists? // TODO: Limit to Configuration.MAX_TRACKNAME_LENGTH trackName = new String(newTrackName); } origTrackName = new String(trackName); if ((!dontAskName) && Configuration.getCfgBitState(Configuration.CFGBIT_GPX_ASK_TRACKNAME_START)) { mEnteringGpxNameStart = true; GuiNameEnter gne = new GuiNameEnter(this, Locale.get("gpx.StartingRecording")/*Starting recording*/, trackName, Configuration.MAX_TRACKNAME_LENGTH); doNewTrk(); gne.show(); } else { doNewTrk(); } } /** Actually writes the track that was written into mTrkByteOutStream * to the RecordStore for tracks. */ private void storeTrk() { try { if (mTrkOutStream == null) { logger.debug("Not recording, so no track to save"); return; } mTrkOutStream.flush(); //#debug debug logger.debug("storing track with " + mTrkRecorded + " points"); ByteArrayOutputStream baosDb = new ByteArrayOutputStream(); DataOutputStream dosDb = new DataOutputStream(baosDb); dosDb.writeUTF(trackName); dosDb.writeInt(mTrkRecorded); dosDb.writeInt(mTrkByteOutStream.size()); // This is the actual writing of the points' data dosDb.write(mTrkByteOutStream.toByteArray()); dosDb.flush(); openTrackDatabase(); // The stream in which the points were written is now saved to the RecordStore if (trackDatabaseRecordId < 0) { trackDatabaseRecordId = trackDatabase.addRecord(baosDb.toByteArray(), 0, baosDb.size()); } else { trackDatabase.setRecord(trackDatabaseRecordId, baosDb.toByteArray(), 0, baosDb.size()); } trackDatabase.closeRecordStore(); trackDatabase = null; } catch (IOException e) { logger.exception(Locale.get("gpx.IOExceptionSavingTrack")/*IOException saving track: */, e); } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionSavingTrackDBNotOpen")/*Exception saving track (database not open): */, e); } catch (RecordStoreFullException e) { logger.exception(Locale.get("gpx.ExceptionSavingTrackDBFull")/*Exception saving track (database full): */, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionSavingTrack")/*Exception saving track: */, e); } catch (OutOfMemoryError oome) { logger.fatal(Locale.get("gpx.OOMCantSaveTracklog")/*Out of memory, can not save tracklog*/); } } /** Triggers the saving of the track to the RecordStore (storeTrk()). * Afterwards, no matter if the save was successful(!), clears the streams * where the track points were stored and the trackTile which displayed them. */ public boolean doSaveTrk() { storeTrk(); try { //#debug debug logger.debug("Closing track with " + mTrkRecorded + " points"); mTrkOutStream.close(); mTrkByteOutStream.close(); } catch (IOException e) { logger.exception(Locale.get("gpx.FailedClosingTrackrecording")/*Failed to close trackrecording*/, e); } mTrkOutStream = null; mTrkByteOutStream = null; trackTile.dropTrk(); return true; } /** Starts the saving of the track. An intermediate step may be to ask * the user for the name of the track. * @param dontAskName If true, the user will not be asked for the track name, * no matter what the configuration says. */ public void saveTrk(boolean dontAskName) { if (mTrkOutStream == null) { logger.debug("Not recording, so no track to save"); return; } if ((!dontAskName) && Configuration.getCfgBitState(Configuration.CFGBIT_GPX_ASK_TRACKNAME_STOP)) { mEnteringGpxNameStop = true; GuiNameEnter gne = new GuiNameEnter(this, Locale.get("gpx.StoppingRecording")/*Stopping recording*/, trackName, Configuration.MAX_TRACKNAME_LENGTH); gne.show(); } else { startProcessorThread(JOB_SAVE_TRK); } } /** Suspends track recording. No more points will be recorded until * resumeTrk() is called. The track will be written to the RecordStore * to reduce the track loss if the application should crash later. */ public void suspendTrk() { trkRecordingSuspended = true; try { if (mTrkOutStream != null) { /** * Add a marker to the recording to be able to break up * the GPX file into separate track segments after each suspend. */ mTrkOutStream.writeFloat(0.0f); mTrkOutStream.writeFloat(0.0f); mTrkOutStream.writeShort(0); mTrkOutStream.writeLong(Long.MIN_VALUE); mTrkOutStream.writeByte(0); mTrkRecorded++; mTrkSegments++; oldlat = 0.0f; oldlon = 0.0f; storeTrk(); } } catch (IOException ioe) { logger.exception(Locale.get("gpx.FailedWritingTrackSegmentationMarker")/*Failed to write track segmentation marker*/, ioe); } } /** Must be called after suspendTrk() to resume track recording. */ public void resumeTrk() { trkRecordingSuspended = false; } /** Triggers deleting of the specified tracks from the RecordStore. * * @param tracks Vector of tracks to delete */ public void deleteTracks(Vector tracks, UploadListener ul) { mTrksToDelete = tracks; feedbackListener = ul; startProcessorThread(JOB_DELETE_TRKS); } /** Actually deletes the tracks in mTrksToDelete from the RecordStore. */ private boolean doDeleteTracks() { try { openTrackDatabase(); for (int i = 0; i < mTrksToDelete.size(); i++) { trackDatabase.deleteRecord(((PersistEntity)mTrksToDelete.elementAt(i)).id); } trackDatabase.closeRecordStore(); trackDatabase = null; trackTile.dropTrk(); mTrksToDelete = null; importExportMessage = Locale.get("gpx.Finished")/*Finished!*/; return true; } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.ExceptionDeletingTrackDBNotOpen")/*Exception deleting track (database not open)*/, e); importExportMessage = Locale.get("gpx.ExceptionDeletingTrackDBNotOpen")/*Exception deleting track (database not open): */ + e.getMessage(); return false; } catch (InvalidRecordIDException e) { logger.exception(Locale.get("gpx.ExceptionDeletingTrackIDInvalid")/*Exception deleting track (ID invalid)*/, e); importExportMessage = Locale.get("gpx.ExceptionDeletingTrackIDInvalid")/*Exception deleting track (ID invalid): */ + e.getMessage(); return false; } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.ExceptionDeletingTrack")/*Exception deleting track*/, e); importExportMessage = Locale.get("gpx.ExceptionDeletingTrack")/*Exception deleting track: */ + e.getMessage(); return false; } } /** Changes the name of a track. * The track is searched in the RecordStore by its ID and is rewritten * with the new name. * * @param trk The track to rename */ public void updateTrackName(PersistEntity trk) { String action = " " + Locale.get("gpx.ReadingForUpdatingTrackname")/*reading for updating trackname*/; try { openTrackDatabase(); DataInputStream dis1 = new DataInputStream(new ByteArrayInputStream( trackDatabase.getRecord(trk.id))); String trackName = dis1.readUTF(); int recorded = dis1.readInt(); int trackSize = dis1.readInt(); byte[] trackArray = new byte[trackSize]; dis1.read(trackArray); action = " " + Locale.get("gpx.PreparingUpdateOf")/*preparing update of*/ + " "; ByteArrayOutputStream baosDb = new ByteArrayOutputStream(); DataOutputStream dosDb = new DataOutputStream(baosDb); // check if renaming the track being currently recorded if (isRecordingTrk() && trk.id == trackDatabaseRecordId) { this.trackName = trk.displayName; } dosDb.writeUTF(trk.displayName); dosDb.writeInt(recorded); dosDb.writeInt(trackSize); dosDb.write(trackArray); dosDb.flush(); action = " " + Locale.get("gpx.UpdatingOf")/*updating of*/ + " "; trackDatabase.setRecord(trk.id, baosDb.toByteArray(), 0, baosDb.size()); trackDatabase.closeRecordStore(); trackDatabase = null; } catch (IOException e) { logger.exception(Locale.get("gpx.IOException")/*IOException*/ + action, e); } catch (RecordStoreNotOpenException e) { logger.exception(Locale.get("gpx.Exception")/*Exception*/ + action + Locale.get("gpx.DatabaseNotOpen")/* (database not open)*/, e); } catch (RecordStoreFullException e) { logger.exception(Locale.get("gpx.Exception")/*Exception*/ + action + Locale.get("gpx.DatabaseFull")/* (database full)*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.Exception")/*Exception*/ + action, e); } catch (OutOfMemoryError oome) { logger.fatal(Locale.get("gpx.OOMCantDo")/*Out of memory, can not do*/ + action); } } /** Starts the receiving of GPX data. * * @param ins Stream from which to read the GPX data * @param ul Listener for progress information * @param maxDist Maximum distance (in kilometers) for filtering waypoints */ public void receiveGpx(InputStream ins, UploadListener ul, float maxDist) { if (ins == null) { logger.error(Locale.get("gpx.CouldNotOpenInputStreamToGpxFile")/*Could not open input stream to gpx file*/); } if ((processorThread != null) && (processorThread.isAlive())) { logger.error(Locale.get("gpx.StillProcessingAnotherGpxFile")/*Still processing another gpx file*/); } maxDistance = maxDist; mImportStream = ins; feedbackListener = ul; startProcessorThread(JOB_IMPORT_GPX); } /** Starts the export of tracks in GPX format. * * @param url URL to export the tracks to * @param ul Listener for progress information * @param tracks Vector of tracks to export */ public void exportTracks(String url, UploadListener ul, Vector tracks) { logger.debug("Exporting tracks to " + url); feedbackListener = ul; this.url = url; trackTile.dropTrk(); exportTracks = tracks; startProcessorThread(JOB_EXPORT_TRKS); } /** Starts the export of all waypoints in GPX format. * First, the user is asked for the name of the object which will contain * the waypoints. * * @param url URL to export the waypoints to * @param ul Listener for progress information */ public void exportWayPts(String url, String filename, UploadListener ul) { this.url = url; feedbackListener = ul; waypointsSaveFileName = filename; startProcessorThread(JOB_EXPORT_WPTS); } /** Returns a list of all way points, sorted by the current search criterion. * * @return Vector of waypoints */ public Vector listWayPoints() { return listWayPoints(false); } /** Returns a list of all way points, sorted by the current search criterion. * * @param favorites flag if only favorites wanted * @return Vector of waypoints */ public Vector listWayPoints(boolean favorites) { int sortmode = Configuration.getWaypointSortMode(); Node centerPos = Trace.getInstance().center; Vector source = wayPtTile.listWayPt(); Vector sorted = new Vector(source.size()); PositionMark insert; PositionMark compare; for (int i = 0; i < source.size(); i++) { if (favorites) { PositionMark waypt = (PositionMark)source.elementAt(i); if (! waypt.displayName.endsWith("*") ) { continue; } } // We want to insert source[i] at the right position into sorted insert = (PositionMark)source.elementAt(i); float distInsert = ProjMath.getDistance(centerPos.radlat, centerPos.radlon, insert.lat, insert.lon); int j = 0; while (j < sorted.size()) { compare = (PositionMark)sorted.elementAt(j); if (sortmode == Configuration.WAYPT_SORT_MODE_NEW_FIRST) { // Newest waypoints first if (insert.timeMillis > compare.timeMillis) { break; } } else if (sortmode == Configuration.WAYPT_SORT_MODE_OLD_FIRST) { // Oldest waypoints first if (insert.timeMillis <= compare.timeMillis) { break; } } else if (sortmode == Configuration.WAYPT_SORT_MODE_ALPHABET) { // Alphabetically if (insert.displayName.compareTo(compare.displayName) < 0) { break; } } else if (sortmode == Configuration.WAYPT_SORT_MODE_DISTANCE) { // By distance from map center float distCompare = ProjMath.getDistance( centerPos.radlat, centerPos.radlon, compare.lat, compare.lon); if (distInsert <= distCompare) { break; } } j++; } sorted.insertElementAt(insert, j); } return sorted; //return wayPts; } /** Returns the number of waypoints. * * @return Number of waypoints */ public int getNumberWaypoints() { int noWpt = wayPtTile.getNumberWaypoints(); //#debug debug logger.debug("WaypointsTile returns: No of WP = " + noWpt); return noWpt; } /** Returns whether waypoints are being loaded * @return True if still loading */ public boolean isLoadingWaypoints() { return (mJobState == JOB_RELOAD_WPTS); } /** * Read tracks from the RecordStore to display the names in the list on screen. */ public PersistEntity[] listTrks() { PersistEntity[] trks; byte [] record = new byte[16000]; DataInputStream dis = new DataInputStream(new ByteArrayInputStream(record)); try { openTrackDatabase(); logger.info("GPX database has " + trackDatabase.getNumRecords() + " entries and a size of " + trackDatabase.getSize()); trks = new PersistEntity[trackDatabase.getNumRecords()]; RecordEnumeration p = trackDatabase.enumerateRecords(null, null, false); logger.info("Enumerating tracks: " + p.numRecords()); int i = 0; boolean bExceptionOccurred = false; while (p.hasNextElement()) { int idx = p.nextRecordId(); PersistEntity trk = new PersistEntity(); try { while (trackDatabase.getRecordSize(idx) > record.length) { record = new byte[record.length + 16000]; dis = new DataInputStream(new ByteArrayInputStream(record)); } trackDatabase.getRecord(idx, record, 0); dis.reset(); String trackName = dis.readUTF(); int noTrackPoints = dis.readInt(); logger.debug("Found track " + trackName + " with " + noTrackPoints + " TrkPoints"); trk.displayName = trackName + " (" + noTrackPoints + ")"; trk.setTrackSize(noTrackPoints); } catch (RecordStoreFullException e) { trk.displayName = Locale.get("gpx.ErrorRecordStoreFullException")/*Error (RecordStoreFullException)*/; logger.error(Locale.get("gpx.RecordStoreFullList")/*Record Store is full, can not load list */ + i + Locale.get("gpx.WithIndex")/* with index */ + idx + ":" + e.getMessage()); } catch (RecordStoreNotFoundException e) { trk.displayName = Locale.get("gpx.ErrorRecordStoreNotFoundException")/*Error (RecordStoreNotFoundException)*/; logger.error(Locale.get("gpx.RecordStoreNotFoundList")/*Record Store not found, can not load list */ + i + Locale.get("gpx.WithIndex")/* with index */ + idx + ": " + e.getMessage()); } catch (RecordStoreException e) { trk.displayName = Locale.get("gpx.ErrorRecordStoreException")/*Error (RecordStoreException)*/; logger.error(Locale.get("gpx.RecordStoreExceptionTrack")/*Record Store exception, can not load track */ + i + Locale.get("gpx.WithIndex")/* with index */ + idx + ": " + e.getMessage()); logger.error( e.toString()); } catch (Exception e) { System.out.println("Exception: " + e.toString()); bExceptionOccurred = true; } trk.id = idx; trks[i++] = trk; } logger.info("Enumerated tracks"); trackDatabase.closeRecordStore(); trackDatabase = null; if (bExceptionOccurred) { logger.error(Locale.get("gpx.ExceptionReadingTracks")/*At least one of...*/); } return trks; } catch (RecordStoreFullException e) { logger.error(Locale.get("gpx.RecordStoreFullList2")/*Record Store is full, can not load list: */ + e.getMessage()); } catch (RecordStoreNotFoundException e) { logger.error(Locale.get("gpx.RecordStoreNotFoundList2")/*Record Store not found, can not load list: */ + e.getMessage()); } catch (RecordStoreException e) { logger.error(Locale.get("gpx.RecordStoreExceptionList2")/*Record Store exception, can not load list: */ + e.getMessage()); } return null; } /** Called when an out of memory condition is detected. * Will stop track recording and save the track in the RecordStore. */ public void dropCache() { trackTile.dropTrk(); // Can't drop waypoints. The only way to get them back is to restart ShareNav. // So if we do this we might as well quit the app altogether. // Plus it shocks the user as he will think they were deleted. //wayPtTile.dropWayPt(); System.gc(); if (isRecordingTrk()) { saveTrk(true); } } /** Currently without function. * TODO: Should it do anything? */ public boolean cleanup(int level) { return false; } /** Currently without function. * TODO: Should it do anything? */ public void walk(PaintContext pc, int opt) { } public int getNameIdx(float lat, float lon, short type) { // only interesting for SingleTile return -1; } /** * Renders the waypoints, the recording track and the loaded tracks on the screen. */ public void paint(PaintContext pc, byte layer) { // Rendering the tracks in reverse order... // Loaded tracks on the bottom, loadedTracksTile.paint(pc, layer); // then the other layers on top. trackTile.paint(pc, layer); wayPtTile.paint(pc, layer); } /** Returns how many actual track points have been recorded so far (segments are not counted for this user-visible string). * * @return Number of track points */ public int getTrkPointCount() { return mTrkRecorded - mTrkSegments; } /** Returns whether a track is currently recorded. * * @return True if recording is on, false if not. */ public boolean isRecordingTrk() { return (mTrkOutStream != null); } /** Returns whether track recording is currently suspended * * @return True if recording is suspended, false if not. */ public boolean isRecordingTrkSuspended() { return trkRecordingSuspended; } /** * @return current GPX track length in m */ public float currentTrkLength() { return trkOdo; } /** * @return current GPX track's average speed in m/s */ public float currentTrkAvgSpd() { return (1000.0f*trkOdo) / trkTimeTot; } /** * @return current GPX tracks time duration in ms */ public long currentTrkDuration() { return trkTimeTot; } /** * @return current GPX track's maximum speed in m/s */ public float maxTrkSpeed() { return trkVmax; } /** * @return current GPX track's exponentially averaged vertical speed in m/s */ public float deltaAltTrkSpeed() { return trkVertSpd; } /** Start processor thread with the specified job. * @param job Job to perform, use JOB_XX for this. */ private void startProcessorThread(int job) { if (mJobState == JOB_IDLE) { if (job > JOB_IDLE && job <= JOB_SAVE_TRK) { mJobState = job; processorThread = new Thread(this, "GpxProcessor"); processorThread.setPriority(Thread.MIN_PRIORITY); processorThread.start(); } else { logger.error(Locale.get("gpx.BadParameterJob")/*Bad parameter job*/ + "=" + job + Locale.get("gpx.OfGpxStartProcessorThread")/* of Gpx.startProcessorThread*/ + "()"); } } else { logger.error("Gpx.startProcessorThread(): " + Locale.get("gpx.NotIdleCantStart")/*Not idle, can not start processorThread!*/); // TODO: Should pass this on (return true/false) to let the user know. } } /** Processor thread which will perform one of several jobs. * Which job is determined by mJobState. */ public void run() { logger.info("GPX processing thread started"); try { boolean success = false; if (mJobState == JOB_RELOAD_WPTS) { loadWaypointsFromDatabase(); if (feedbackListener != null) { feedbackListener.uploadAborted(); feedbackListener = null; } } else if (mJobState == JOB_EXPORT_TRKS) { // Export GPX tracks for (int i = 0; i < exportTracks.size(); i++) { currentTrk = (PersistEntity)exportTracks.elementAt(i); if (feedbackListener != null) { feedbackListener.updateProgress(Locale.get("gpx.Exporting")/*Exporting */ + currentTrk.displayName + "\n"); } success = sendGpx(); if (success == false) { logger.error(Locale.get("gpx.FailedToExportTrack")/*Failed to export track */ + currentTrk); } } } else if (mJobState == JOB_EXPORT_WPTS) { // Export GPX waypoints success = sendGpx(); } else if (mJobState == JOB_IMPORT_GPX) { // Import GPX if (mImportStream != null) { success = doReceiveGpx(); mImportStream = null; } else { logger.error(Locale.get("gpx.GPXImportRequestedButInputStream")/*GPX import requested but InputStream was null*/); } } else if (mJobState == JOB_DELETE_TRKS) { success = doDeleteTracks(); } else if (mJobState == JOB_DELETE_WPTS) { success = doDeleteWayPts(); loadWaypointsFromDatabase(); } else if (mJobState == JOB_SAVE_TRK) { success = doSaveTrk(); } else { logger.error(Locale.get("gpx.DidNotKnowWhatToDoWithGpx")/*Did not know what to do in*/+ " Gpx.run()"); importExportMessage = Locale.get("gpx.DidNotKnowWhatToDo")/*Did not know what to do.*/; success = false; } // Must be called *before* changing the job state, else somebody might // trigger (in the context of this thread btw) another action = thread // before this one has completely finished. if (feedbackListener != null) { feedbackListener.completedUpload(success, importExportMessage); feedbackListener = null; } } catch (Exception e) { logger.exception(Locale.get("gpx.AnErrorOccuredDuringGPXJob")/*An error occured during GPX job*/, e); } catch (OutOfMemoryError oome) { Trace.getInstance().dropCache(); logger.error(Locale.get("gpx.OOMDuringGPXjobTryingToRecover")/*Out of memory during GPX job, trying to recover*/); } mJobState = JOB_IDLE; } /** Convenience method to open the waypoint RecordStore. */ private void openWayPtDatabase() { try { if (wayptDatabase == null) { wayptDatabase = RecordStore.openRecordStore("waypoints", true); } } catch (RecordStoreFullException e) { logger.exception(Locale.get("gpx.RecordstoreFullOpenWP")/*Recordstore full while trying to open waypoints*/, e); } catch (RecordStoreNotFoundException e) { logger.exception(Locale.get("gpx.WaypointRecordstoreNotFound")/*Waypoint recordstore not found*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.RecordStoreExceptionOpeningWaypoints")/*RecordStoreException opening waypoints*/, e); } catch (OutOfMemoryError oome) { logger.error(Locale.get("gpx.OOMOpeningWaypoints")/*Out of memory opening waypoints*/); } } /** * Read waypoints from the RecordStore and put them in the wayPtTile for displaying. */ private void loadWaypointsFromDatabase() { try { wayPtTile.dropWayPt(); RecordEnumeration renum; logger.info("Loading waypoints into tile"); openWayPtDatabase(); renum = wayptDatabase.enumerateRecords(null, null, false); while (renum.hasNextElement()) { int id; id = renum.nextRecordId(); PositionMark waypt = new PositionMark(id, wayptDatabase.getRecord(id)); wayPtTile.addWayPt(waypt); } wayptDatabase.closeRecordStore(); wayptDatabase = null; } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.RecordStoreExceptionLoadingWaypoints")/*RecordStoreException loading waypoints*/, e); } catch (OutOfMemoryError oome) { logger.error(Locale.get("gpx.OOMLoadingWaypoints")/*Out of memory loading waypoints*/); } } /** Convenience method to open the tracks RecordStore. */ private void openTrackDatabase() { try { if (trackDatabase == null) { logger.info("Opening track database"); trackDatabase = RecordStore.openRecordStore("tracks", true); } } catch (RecordStoreFullException e) { logger.exception(Locale.get("gpx.RecordstoreFullOpenTracks")/*Recordstore is full while trying to open tracks*/, e); } catch (RecordStoreNotFoundException e) { logger.exception(Locale.get("gpx.TracksRecordstoreNotFound")/*Tracks recordstore not found*/, e); } catch (RecordStoreException e) { logger.exception(Locale.get("gpx.RecordStoreExceptionOpeningTracks")/*RecordStoreException opening tracks*/, e); } catch (OutOfMemoryError oome) { logger.error(Locale.get("gpx.OOMOpeningTracks")/*Out of memory opening tracks*/); } } /** Actually writes the track points of the current track in GPX format. * * @param oS The stream to which the track points are written */ private void streamTracks(OutputStream oS) throws IOException, RecordStoreNotOpenException, InvalidRecordIDException, RecordStoreException { float lat, lon; short ele; long time; // Date date = new Date(); openTrackDatabase(); DataInputStream dis1 = new DataInputStream(new ByteArrayInputStream( trackDatabase.getRecord(currentTrk.id))); trackName = dis1.readUTF(); mTrkRecorded = dis1.readInt(); mTrkSegments = 0; int trackSize = dis1.readInt(); byte[] trackArray = new byte[trackSize]; dis1.read(trackArray); DataInputStream trackIS = new DataInputStream(new ByteArrayInputStream(trackArray)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); oS.write("<trk>\r\n<trkseg>\r\n".getBytes()); StringBuffer sb = new StringBuffer(128); // Calculate interval for progressbar update - progressbar is updated in 2% steps int progUpdtIntervall = 1; if (mTrkRecorded >= 50) { progUpdtIntervall = mTrkRecorded / 50; } for (int i = 1; i <= mTrkRecorded; i++) { lat = trackIS.readFloat(); lon = trackIS.readFloat(); ele = trackIS.readShort(); time = trackIS.readLong(); // Read extra bytes in the buffer, that are currently not written to the GPX file. // Will add these at a later time. trackIS.readByte(); //Speed if (time == Long.MIN_VALUE) { oS.write("</trkseg>\r\n".getBytes()); oS.write("<trkseg>\r\n".getBytes()); } else if (time == Long.MIN_VALUE + 1) { PositionMark waypt = null; try { int id; id = ele; openWayPtDatabase(); waypt = new PositionMark(id, wayptDatabase.getRecord(id)); wayptDatabase.closeRecordStore(); wayptDatabase = null; if (waypt != null) { // TODO: check if this copes with the case when // waypoint has been removed before converting // track to GPX // Stream waypoint to a separate bytearray to write it out // at the end of the track. streamWayPt(baos, waypt); } } catch (RecordStoreException e) { logger.info("RecordStoreException (" + e.getMessage() + ") loading track embeded waypoint. Has it been deleted?"); } catch (OutOfMemoryError oome) { logger.error(Locale.get("gpx.OOMLoadingWaypoints")/*Out of memory loading waypoints*/); } } else { sb.setLength(0); sb.append("<trkpt lat='").append(lat).append("' lon='").append(lon).append("'>\r\n"); sb.append("<ele>").append(ele).append("</ele>\r\n"); // date.setTime(time); // sb.append("<time>").append(formatUTC(date)).append("</time>\r\n"); sb.append("<time>").append(DateTimeTools.getUTCDateTime(time)).append("</time>\r\n"); //System.out.println(DateTimeTools.getUTCDateTime(time) + " / " + formatUTC(date)); sb.append("</trkpt>\r\n"); writeUTF(oS, sb); } /** * Increment the progress bar when progress has increased 2% * Don't update on every point as an optimisation. */ if (((i % progUpdtIntervall) == 0) && (feedbackListener != null)) { // Update the progress bar in GuiGpx feedbackListener.updateProgressValue(progUpdtIntervall); } } // for oS.write("</trkseg>\r\n</trk>\r\n".getBytes()); oS.write(baos.toByteArray()); trackDatabase.closeRecordStore(); trackDatabase = null; /** Update the progress bar by the remaining part */ feedbackListener.updateProgressValue(mTrkRecorded % progUpdtIntervall); } /** Writes a string in UTF-8 * * @param oS The stream to which to write the string * @param sb StringBuffer with the string */ private void writeUTF(OutputStream oS, StringBuffer sb) { try { oS.write(sb.toString().getBytes(Configuration.getUtf8Encoding())); } catch (IOException e) { logger.exception(Locale.get("gpx.IOExceptionInWriteUTF")/*IOException in writeUTF*/ + "()", e); } } /** Writes one waypoint in GPX format. * * @param oS The stream to which the waypoints are written * @param wayPt The waypoint to write */ private void streamWayPt (OutputStream oS, PositionMark wayPt) throws IOException { StringBuffer sb = new StringBuffer(128); //if (wayPt.lat > 90) wayPt.lat = wayPt.lat * MoreMath.FAC_DECTORAD * MoreMath.FAC_DECTORAD; //if (wayPt.lon > 180) wayPt.lon = wayPt.lon * MoreMath.FAC_DECTORAD * MoreMath.FAC_DECTORAD; sb.append("<wpt lat='").append(wayPt.lat * MoreMath.FAC_RADTODEC) .append("' lon='").append(wayPt.lon * MoreMath.FAC_RADTODEC).append("'>\r\n") .append("<name>").append(HelperRoutines.utf2xml(wayPt.displayName)).append("</name>\r\n"); if (wayPt.ele != PositionMark.INVALID_ELEVATION) { sb.append("<ele>").append(wayPt.ele).append("</ele>\r\n"); } // TODO: explain When will timeMillis of a wayPt be 0 ? if (wayPt.timeMillis != 0) { //dateStreamWayPt.setTime(wayPt.timeMillis); //sb.append("<time>").append(formatUTC(dateStreamWayPt)).append("</time>\r\n"); sb.append("<time>").append(DateTimeTools.getUTCDateTime(wayPt.timeMillis)).append("</time>\r\n"); } // fix and sats are not filled yet so we don't export them either. // sym and type are not exported yet but they could be mapped to strings. sb.append("</wpt>\r\n"); writeUTF(oS, sb); } /** Actually writes the waypoints from wayPtTile in GPX format. * * @param oS The stream to which the waypoints are written */ private void streamWayPts (OutputStream oS) throws IOException { Vector waypts = listWayPoints(); PositionMark wayPt = null; for (int i = 0; i < waypts.size(); i++) { wayPt = (PositionMark)waypts.elementAt(i); streamWayPt(oS, wayPt); } } /** This method exports a track or waypoints in GPX format. * * @return True if successful, false if not. Details can be found in the string importExportMessage. */ private boolean sendGpx() { try { String name = null; logger.trace("Starting to send a GPX file, about to open a connection to" + url); if (mJobState == JOB_EXPORT_TRKS) { name = Configuration.getValidFileName(currentTrk.displayName); } else if (mJobState == JOB_EXPORT_WPTS) { if (waypointsSaveFileName == null) { importExportMessage = Locale.get("gpx.NoWilenameWpSendingAborted")/*No filename, way points sending aborted*/; return false; } name = Configuration.getValidFileName(waypointsSaveFileName); } if (url == null) { importExportMessage = Locale.get("gpx.NoGPXreceiver")/*No GPX receiver specified. Please select a GPX receiver in the setup menu*/; return false; } OutputStream outStream = null; ExportSession session = null; try { /** * We jump through hoops here (Class.forName) in order to decouple * the implementation of JSRs. The problem is, that not all phones have all * of the JSRs, and if we simply called the Class directly, it would * cause the whole app to crash. With this method, we can hopefully catch * the missing JSRs and gracefully report an error to the user that the operation * is not available on this phone. */ /** * The Class.forName and the instantiation of the class must be separate * statements, as otherwise this confuses the proguard obfuscator when * rewriting the flattened renamed classes. */ Class tmp = null; if (url.startsWith("file:")) { tmp = Class.forName("net.sharenav.sharenav.importexport.FileExportSession"); } else if (url.startsWith("comm:")) { tmp = Class.forName("net.sharenav.sharenav.importexport.CommExportSession"); } else if (url.startsWith("btgoep:")) { tmp = Class.forName("net.sharenav.sharenav.importexport.ObexExportSession"); } else if (url.startsWith("http:")) { tmp = Class.forName("net.sharenav.midlet.sharenav.ui.GuiGpxOsmUpload"); } if (tmp != null) { logger.info("Got class: " + tmp); Object objTmp = tmp.newInstance(); if (objTmp instanceof ExportSession) { session = (ExportSession)(objTmp); } else { logger.info("objTmp: " + objTmp + "is not part of " + ExportSession.class.getName()); } } } catch (ClassNotFoundException cnfe) { importExportMessage = Locale.get("gpx.UnsuportedExport")/*Your phone does not support this form of exporting, please choose a different one*/; session = null; return false; } catch (ClassCastException cce) { logger.exception(Locale.get("gpx.CouldNotCastTheClass")/*Could not cast the class*/, cce); } if (session == null) { importExportMessage = Locale.get("gpx.UnsuportedExport")/*Your phone does not support this form of exporting, please choose a different one*/; return false; } outStream = session.openSession(url, name); if (outStream == null) { importExportMessage = Locale.get("gpx.CouldNotObtainValidConn")/*Could not obtain a valid connection to*/ + " " + url; return false; } outStream.write("<?xml version='1.0' encoding='UTF-8'?>\r\n".getBytes()); outStream.write("<gpx version='1.1' creator='GPSMID' xmlns='http://www.topografix.com/GPX/1/1'>\r\n".getBytes()); if (mJobState == JOB_EXPORT_WPTS) { streamWayPts(outStream); } else if (mJobState == JOB_EXPORT_TRKS) { streamTracks(outStream); } outStream.write("</gpx>\r\n".getBytes()); outStream.flush(); outStream.close(); session.closeSession(); importExportMessage = Locale.get("gpx.success")/*success*/; return true; } catch (IOException e) { logger.error(Locale.get("gpx.IOExceptionCantTransmitTracklog")/*IOException, can not transmit tracklog: */ + e); importExportMessage = e.getMessage(); } catch (OutOfMemoryError oome) { importExportMessage = Locale.get("gpx.OOMCantTransmitTracklog")/*Out of memory, can not transmit tracklog*/; logger.fatal(importExportMessage); } catch (Exception ee) { logger.error(Locale.get("gpx.ErrorWhileSendingTracklogs")/*Error while sending tracklogs: */ + ee); importExportMessage = ee.getMessage(); } return false; } /** Reads GPX data from mInputStream. * * @return True if reading was successful, false if not */ private boolean doReceiveGpx() { try { // Determine parser that can be used on this system boolean success; String jsr172Version = null; Class parserClass; Object parserObject; GpxParser parser; GpxImportHandler importHandler = new GpxImportHandler(maxDistance, this, feedbackListener); try { jsr172Version = System.getProperty("xml.jaxp.subset.version"); } catch (Exception e) { /** * Some phones throw exceptions if trying to access properties that don't * exist, so we have to catch these and just ignore them. */ } if ((jsr172Version != null) && (jsr172Version.length() > 0)) { logger.info("Using builtin jsr 172 XML parser"); parserClass = Class.forName("net.sharenav.sharenav.importexport.Jsr172GpxParser"); } else { logger.info("Using QDXMLParser"); parserClass = Class.forName("net.sharenav.sharenav.importexport.QDGpxParser"); } parserObject = parserClass.newInstance(); parser = (GpxParser) parserObject; // Trigger parsing of GPX data // As addTrkPt() is used for importing tracks, recording rules have // to be disabled temporarily. applyRecordingRules = false; success = parser.parse(mImportStream, importHandler); applyRecordingRules = true; mImportStream.close(); importExportMessage = importHandler.getMessage(); return success; } catch (ClassNotFoundException cnfe) { importExportMessage = Locale.get("gpx.NoXMLParseSupport")/*Your phone does not support XML parsing*/; } catch (Exception e) { importExportMessage = Locale.get("gpx.ImportGPXError")/*Something went wrong while importing GPX*/ + ": " + e; } return false; } // /** // * Formats an integer to 2 digits, as used for example in time. // * I.e. 3 gets printed as 03. // **/ // private static final String formatInt2(int n) { // if (n < 10) { // return "0" + n; // } else { // return Integer.toString(n); // } // } // // /** // * Date-Time formatter that corresponds to the standard UTC time as used in XML // * @param time Time to be formatted // * @return String containing the formatted time // */ // private static final String formatUTC(Date time) { // // TODO: This function needs optimising. It has a too high object churn. // Calendar c = null; // if (c == null) { // c = Calendar.getInstance(); // } // c.setTime(time); // return c.get(Calendar.YEAR) + "-" + formatInt2(c.get(Calendar.MONTH) + 1) + "-" + // formatInt2(c.get(Calendar.DAY_OF_MONTH)) + "T" + formatInt2(c.get(Calendar.HOUR_OF_DAY)) + ":" + // formatInt2(c.get(Calendar.MINUTE)) + ":" + formatInt2(c.get(Calendar.SECOND)) + "Z"; // // } /** Called when the user has entered a name. * Will continue with the actual operation, i.e. saving the track or the waypoints. * @param strResult The name entered */ public void inputCompleted(String strResult) { if (mEnteringGpxNameStart || mEnteringGpxNameStop) { trackName = strResult; if (mEnteringGpxNameStart) { mEnteringGpxNameStart = false; if (trackName != null) { origTrackName = new String(trackName); } else { // cancel start of recording // old behaviour: cancel rename: trackName = new String(origTrackName); try { Trace tr = Trace.getInstance(); //#debug debug logger.debug("Closing track with " + mTrkRecorded + " points due to user cancel"); mTrkOutStream.flush(); mTrkOutStream.close(); mTrkByteOutStream.close(); tr.alert(Locale.get("trace.GpsRecording")/*Gps track recording*/, Locale.get("trace.Cancelled")/*Cancelled*/, 1250); } catch (IOException e) { logger.exception(Locale.get("gpx.FailedClosingTrackrecording")/*Failed to close trackrecording*/, e); } mTrkOutStream = null; mTrkByteOutStream = null; trackTile.dropTrk(); } } if (mEnteringGpxNameStop) { mEnteringGpxNameStop = false; if (trackName == null) { // old behaviour: cancel rename: trackName = origTrackName; Trace tr = Trace.getInstance(); // don't stop recording tr.alert(Locale.get("trace.GpsRecording")/*Gps track recording*/, Locale.get("trace.Continuing")/*Continuing*/, 1250); trackName = new String(origTrackName); } else { startProcessorThread(JOB_SAVE_TRK); } } Trace.getInstance().show(); return; } } }