Add "when was I here?" button.
This commit is contained in:
		
							parent
							
								
									d8d9bc23ff
								
							
						
					
					
						commit
						ebfbf05006
					
				
					 10 changed files with 238 additions and 181 deletions
				
			
		
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							|  | @ -7,7 +7,7 @@ The goal of this fork is to make 24/7 recording easier. I want to be able to run | |||
| 
 | ||||
| 1. trkpt stores points in an SQLite database instead of json files. | ||||
| 
 | ||||
|     • Instead of storing the database in the app's private area (`/Android/data/...`), you can put the database in a folder that you sync to your PC with [Syncthing](https://f-droid.org/en/packages/com.nutomic.syncthingandroid/). | ||||
|     • You can put the database in a folder that you sync to your PC with [Syncthing](https://f-droid.org/en/packages/com.nutomic.syncthingandroid/). | ||||
| 
 | ||||
| 2. trkpt does not store "tracks" as objects. Instead, tracks are rendered and exported on the fly by querying the database of trackpoints. | ||||
| 
 | ||||
|  | @ -16,3 +16,11 @@ The goal of this fork is to make 24/7 recording easier. I want to be able to run | |||
|     • Although Trackbook has a feature to omit points that are close together, natural GPS inaccuracy and drift is large enough to create points that are far apart, leading to clouds over time. | ||||
| 
 | ||||
| 4. trkpt removes the feature of "starring" waypoints. I recommend using [OsmAnd](https://f-droid.org/en/packages/net.osmand.plus/) to store your favorite places. | ||||
| 
 | ||||
| ## Mirrors | ||||
| 
 | ||||
| https://github.com/voussoir/trkpt | ||||
| 
 | ||||
| https://gitlab.com/voussoir/trkpt | ||||
| 
 | ||||
| https://codeberg.org/voussoir/trkpt | ||||
|  |  | |||
|  | @ -60,6 +60,14 @@ class Database(val trackbook: net.voussoir.trkpt.Trackbook) | |||
|         commit() | ||||
|     } | ||||
| 
 | ||||
|     fun delete_trkpt_start_end(device_id: String, start_time: Long, end_time: Long) | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Track.delete ${device_id} ${start_time} -- ${end_time}.") | ||||
|         this.begin_transaction() | ||||
|         this.connection.delete("trkpt", "device_id = ? AND time > ? AND time < ?", arrayOf(device_id, start_time.toString(), end_time.toString())) | ||||
|         this.commit() | ||||
|     } | ||||
| 
 | ||||
|     fun insert_trkpt(trkpt: net.voussoir.trkpt.Trkpt) | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Database.insert_trkpt") | ||||
|  | @ -76,6 +84,56 @@ class Database(val trackbook: net.voussoir.trkpt.Trackbook) | |||
|         connection.insert("trkpt", null, values) | ||||
|     } | ||||
| 
 | ||||
|     fun select_trkpt_start_end(device_id: String, start_time: Long, end_time: Long): Iterator<Trkpt> | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Track.trkpt_generator: Querying points between ${start_time} -- ${end_time}.") | ||||
|         return _trkpt_generator(this.connection.rawQuery( | ||||
|             "SELECT device_id, lat, lon, time, ele, accuracy, sat FROM trkpt WHERE device_id = ? AND time > ? AND time < ? ORDER BY time ASC", | ||||
|             arrayOf(device_id, start_time.toString(), end_time.toString()) | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     fun select_trkpt_bounding_box(device_id: String, north: Double, south: Double, east: Double, west: Double): Iterator<Trkpt> | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Track.trkpt_generator: Querying points between $north, $south, $east, $west.") | ||||
|         return _trkpt_generator(this.connection.rawQuery( | ||||
|             "SELECT device_id, lat, lon, time, ele, accuracy, sat FROM trkpt WHERE device_id = ? AND lat >= ? AND lat <= ? AND lon >= ? AND lon <= ? ORDER BY time ASC", | ||||
|             arrayOf(device_id, south.toString(), north.toString(), west.toString(), east.toString()) | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     fun _trkpt_generator(cursor: Cursor) = iterator<Trkpt> | ||||
|     { | ||||
|         val COLUMN_DEVICE = cursor.getColumnIndex("device_id") | ||||
|         val COLUMN_LAT = cursor.getColumnIndex("lat") | ||||
|         val COLUMN_LON = cursor.getColumnIndex("lon") | ||||
|         val COLUMN_ELE = cursor.getColumnIndex("ele") | ||||
|         val COLUMN_SAT = cursor.getColumnIndex("sat") | ||||
|         val COLUMN_ACCURACY = cursor.getColumnIndex("accuracy") | ||||
|         val COLUMN_TIME = cursor.getColumnIndex("time") | ||||
|         try | ||||
|         { | ||||
|             while (cursor.moveToNext()) | ||||
|             { | ||||
|                 val trkpt = Trkpt( | ||||
|                     device_id=cursor.getString(COLUMN_DEVICE), | ||||
|                     provider="", | ||||
|                     latitude=cursor.getDouble(COLUMN_LAT), | ||||
|                     longitude=cursor.getDouble(COLUMN_LON), | ||||
|                     altitude=cursor.getDouble(COLUMN_ELE), | ||||
|                     accuracy=cursor.getFloat(COLUMN_ACCURACY), | ||||
|                     time=cursor.getLong(COLUMN_TIME), | ||||
|                     numberSatellites=cursor.getInt(COLUMN_SAT), | ||||
|                 ) | ||||
|                 yield(trkpt) | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             cursor.close() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun delete_homepoint(id: Long) | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Database.delete_homepoint") | ||||
|  |  | |||
|  | @ -21,7 +21,6 @@ | |||
| package net.voussoir.trkpt | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.database.Cursor | ||||
| import android.net.Uri | ||||
| import android.os.Handler | ||||
| import android.os.Looper | ||||
|  | @ -34,23 +33,13 @@ import java.util.* | |||
| data class Track ( | ||||
|     val database: net.voussoir.trkpt.Database, | ||||
|     val device_id: String, | ||||
|     var start_time: Date, | ||||
|     var end_time: Date, | ||||
|     var name: String = "", | ||||
|     var _start_time: Long = 0L, | ||||
|     var _end_time: Long = 0L, | ||||
|     val trkpts: ArrayList<Trkpt> = ArrayList<Trkpt>(), | ||||
|     var view_latitude: Double = Keys.DEFAULT_LATITUDE, | ||||
|     var view_longitude: Double = Keys.DEFAULT_LONGITUDE, | ||||
|     var trackFormatVersion: Int = Keys.CURRENT_TRACK_FORMAT_VERSION, | ||||
| ) | ||||
| { | ||||
|     fun delete() | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Track.delete ${device_id} ${start_time} -- ${end_time}.") | ||||
|         database.begin_transaction() | ||||
|         database.connection.delete("trkpt", "device_id = ? AND time > ? AND time < ?", arrayOf(device_id, start_time.time.toString(), end_time.time.toString())) | ||||
|         database.commit() | ||||
|     } | ||||
| 
 | ||||
|     fun export_gpx(context: Context, fileuri: Uri): Uri? | ||||
|     { | ||||
|         if (! database.ready) | ||||
|  | @ -88,7 +77,7 @@ data class Track ( | |||
|         write("\t\t<trkseg>") | ||||
| 
 | ||||
|         var previous: Trkpt? = null | ||||
|         for (trkpt in trkpt_generator()) | ||||
|         for (trkpt in this.trkpts) | ||||
|         { | ||||
|             if (previous != null && (trkpt.time - previous.time) > (Keys.STOP_OVER_THRESHOLD)) | ||||
|             { | ||||
|  | @ -96,9 +85,10 @@ data class Track ( | |||
|                 write("\t\t<trkseg>") | ||||
|             } | ||||
|             write("\t\t\t<trkpt lat=\"${trkpt.latitude}\" lon=\"${trkpt.longitude}\">") | ||||
|             write("\t\t\t\t<ele>${trkpt.altitude}</ele>") | ||||
|             write("\t\t\t\t<time>${iso8601(trkpt.time)}</time>") | ||||
|             write("\t\t\t\t<unix>${trkpt.time}</unix>") | ||||
|             write("\t\t\t\t<ele>${trkpt.altitude}</ele>") | ||||
|             write("\t\t\t\t<accuracy>${trkpt.accuracy}</accuracy>") | ||||
|             write("\t\t\t\t<sat>${trkpt.numberSatellites}</sat>") | ||||
|             write("\t\t\t</trkpt>") | ||||
|             previous = trkpt | ||||
|  | @ -114,107 +104,65 @@ data class Track ( | |||
|         return fileuri | ||||
|     } | ||||
| 
 | ||||
|     fun load_trkpts() | ||||
|     fun load_trkpts(points: Iterator<Trkpt>) | ||||
|     { | ||||
|         this.trkpts.clear() | ||||
|         trkpt_generator().forEach { trkpt -> this.trkpts.add(trkpt) } | ||||
|         if (this.trkpts.size > 0) | ||||
|         { | ||||
|             this.view_latitude = this.trkpts.first().latitude | ||||
|             this.view_longitude = this.trkpts.first().longitude | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun statistics(): TrackStatistics | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Track.statistics") | ||||
|         var first: Trkpt? = null | ||||
|         var last: Trkpt? = null | ||||
|         var previous: Trkpt? = null | ||||
|         val stats = TrackStatistics() | ||||
|         for (trkpt in trkpt_generator()) | ||||
|         { | ||||
|             if (previous == null) | ||||
|             { | ||||
|                 first = trkpt | ||||
|                 previous = trkpt | ||||
|                 stats.max_altitude = trkpt.altitude | ||||
|                 stats.min_altitude = trkpt.altitude | ||||
|                 continue | ||||
|             } | ||||
|             stats.distance += previous.toLocation().distanceTo(trkpt.toLocation()) | ||||
|             val ascentdiff = trkpt.altitude - previous.altitude | ||||
|             if (ascentdiff > 0) | ||||
|             { | ||||
|                 stats.total_ascent += ascentdiff | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 stats.total_descent += ascentdiff | ||||
|             } | ||||
|             if (trkpt.altitude > stats.max_altitude) | ||||
|             { | ||||
|                 stats.max_altitude = trkpt.altitude | ||||
|             } | ||||
|             if (trkpt.altitude < stats.min_altitude) | ||||
|             { | ||||
|                 stats.min_altitude = trkpt.altitude | ||||
|             } | ||||
|             previous = trkpt | ||||
|             last = trkpt | ||||
|         } | ||||
|         if (first == null || last == null) | ||||
|         { | ||||
|             return stats | ||||
|         } | ||||
|         stats.duration = last.time - first.time | ||||
|         stats.velocity = stats.distance / (stats.duration / 1000) | ||||
|         return stats | ||||
|     } | ||||
| 
 | ||||
|     fun trkpt_generator() = iterator<Trkpt> | ||||
|     { | ||||
|         var cursor: Cursor = database.connection.rawQuery( | ||||
|             "SELECT lat, lon, time, ele, accuracy, sat FROM trkpt WHERE device_id = ? AND time > ? AND time < ? ORDER BY time ASC", | ||||
|             arrayOf(device_id, start_time.time.toString(), end_time.time.toString()) | ||||
|         ) | ||||
|         Log.i("VOUSSOIR", "Track.trkpt_generator: Querying points between ${start_time} -- ${end_time}, ${cursor.count} results") | ||||
|         val COLUMN_LAT = cursor.getColumnIndex("lat") | ||||
|         val COLUMN_LON = cursor.getColumnIndex("lon") | ||||
|         val COLUMN_ELE = cursor.getColumnIndex("ele") | ||||
|         val COLUMN_SAT = cursor.getColumnIndex("sat") | ||||
|         val COLUMN_ACCURACY = cursor.getColumnIndex("accuracy") | ||||
|         val COLUMN_TIME = cursor.getColumnIndex("time") | ||||
|         try | ||||
|         { | ||||
|             while (cursor.moveToNext()) | ||||
|             { | ||||
|                 val trkpt = Trkpt( | ||||
|                     device_id=device_id, | ||||
|                     provider="", | ||||
|                     latitude=cursor.getDouble(COLUMN_LAT), | ||||
|                     longitude=cursor.getDouble(COLUMN_LON), | ||||
|                     altitude=cursor.getDouble(COLUMN_ELE), | ||||
|                     accuracy=cursor.getFloat(COLUMN_ACCURACY), | ||||
|                     time=cursor.getLong(COLUMN_TIME), | ||||
|                     numberSatellites=cursor.getInt(COLUMN_SAT), | ||||
|                 ) | ||||
|                 yield(trkpt) | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             cursor.close() | ||||
|         } | ||||
|         points.forEach { trkpt -> this.trkpts.add(trkpt) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| data class TrackStatistics( | ||||
|     val trkpts: ArrayList<Trkpt>, | ||||
|     var distance: Double = 0.0, | ||||
|     var duration: Long = 0, | ||||
|     var velocity: Double = 0.0, | ||||
|     var total_ascent: Double = 0.0, | ||||
|     var total_descent: Double = 0.0, | ||||
|     var max_altitude: Double = 0.0, | ||||
|     var min_altitude: Double = 0.0, | ||||
|     var min_altitude: Double = 0.0 | ||||
| ) | ||||
| { | ||||
|     init | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "Track.statistics") | ||||
|         var first: Trkpt? = null | ||||
|         var last: Trkpt? = null | ||||
|         var previous: Trkpt? = null | ||||
|         for (trkpt in trkpts) | ||||
|         { | ||||
|             if (previous == null) | ||||
|             { | ||||
|                 first = trkpt | ||||
|                 previous = trkpt | ||||
|                 max_altitude = trkpt.altitude | ||||
|                 min_altitude = trkpt.altitude | ||||
|                 continue | ||||
|             } | ||||
|             distance += previous.toLocation().distanceTo(trkpt.toLocation()) | ||||
|             val ascentdiff = trkpt.altitude - previous.altitude | ||||
|             if (ascentdiff > 0) | ||||
|             { | ||||
|                 total_ascent += ascentdiff | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 total_descent += ascentdiff | ||||
|             } | ||||
|             if (trkpt.altitude > max_altitude) | ||||
|             { | ||||
|                 max_altitude = trkpt.altitude | ||||
|             } | ||||
|             if (trkpt.altitude < min_altitude) | ||||
|             { | ||||
|                 min_altitude = trkpt.altitude | ||||
|             } | ||||
|             previous = trkpt | ||||
|             last = trkpt | ||||
|         } | ||||
|         if (first != null && last != null) | ||||
|         { | ||||
|             duration = last.time - first.time | ||||
|             velocity = distance / (duration / 1000) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -64,8 +64,7 @@ import net.voussoir.trkpt.helpers.LengthUnitHelper | |||
| import net.voussoir.trkpt.helpers.PreferencesHelper | ||||
| import net.voussoir.trkpt.helpers.UiHelper | ||||
| import net.voussoir.trkpt.helpers.create_start_end_markers | ||||
| import net.voussoir.trkpt.helpers.iso8601 | ||||
| import net.voussoir.trkpt.helpers.iso8601_parse | ||||
| import net.voussoir.trkpt.helpers.iso8601_local | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| 
 | ||||
|  | @ -85,6 +84,7 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|     lateinit var track_query_end_date: DatePicker | ||||
|     lateinit var track_query_end_time: TimePicker | ||||
|     lateinit var delete_selected_trkpt_button: ImageButton | ||||
|     lateinit var when_was_i_here_button: ImageButton | ||||
|     var track_query_start_time_previous: Int = 0 | ||||
|     var track_query_end_time_previous: Int = 0 | ||||
|     private lateinit var mapView: MapView | ||||
|  | @ -118,15 +118,18 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View | ||||
|     { | ||||
|         this.trackbook = (requireContext().applicationContext as Trackbook) | ||||
|         val database: net.voussoir.trkpt.Database = (requireActivity().applicationContext as Trackbook).database | ||||
|         val requested_start_time = this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!.toLong() | ||||
|         val requested_end_time = this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!.toLong() | ||||
|         track = Track( | ||||
|             database=database, | ||||
|             name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""), | ||||
|             database=this.trackbook.database, | ||||
|             device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""), | ||||
|             start_time=iso8601_parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!), | ||||
|             end_time=iso8601_parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!), | ||||
|             name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""), | ||||
|         ) | ||||
|         track.load_trkpts() | ||||
|         track.load_trkpts(this.trackbook.database.select_trkpt_start_end( | ||||
|             device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""), | ||||
|             start_time=requested_start_time, | ||||
|             end_time=requested_end_time, | ||||
|         )) | ||||
|         rootView = inflater.inflate(R.layout.fragment_track, container, false) | ||||
|         mapView = rootView.findViewById(R.id.map) | ||||
|         save_track_button = rootView.findViewById(R.id.save_button) | ||||
|  | @ -142,7 +145,11 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|         mapView.isVerticalMapRepetitionEnabled = false | ||||
|         mapView.setMultiTouchControls(true) | ||||
|         mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER) | ||||
|         controller.setCenter(GeoPoint(track.view_latitude, track.view_longitude)) | ||||
|         if (track.trkpts.size > 0) | ||||
|         { | ||||
|             val first = track.trkpts.first() | ||||
|             controller.setCenter(GeoPoint(first.latitude, first.longitude)) | ||||
|         } | ||||
|         controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL) | ||||
| 
 | ||||
|         // trkpt_infowindow = MarkerInfoWindow(R.layout.trkpt_infowindow, mapView) | ||||
|  | @ -170,8 +177,8 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|             mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS) | ||||
|         } | ||||
| 
 | ||||
|         val actual_start_time: Date = if (track.trkpts.isEmpty()) track.start_time else Date(track.trkpts.first().time) | ||||
|         val actual_end_time: Date = if (track.trkpts.isEmpty()) track.end_time else Date(track.trkpts.last().time) | ||||
|         val actual_start_time: Date = if (track.trkpts.isEmpty()) Date(requested_start_time) else Date(track.trkpts.first().time) | ||||
|         val actual_end_time: Date = if (track.trkpts.isEmpty()) Date(requested_end_time) else Date(track.trkpts.last().time) | ||||
| 
 | ||||
|         track_query_start_date = rootView.findViewById(R.id.track_query_start_date) | ||||
|         val start_cal = GregorianCalendar() | ||||
|  | @ -263,6 +270,18 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|                 mapView.invalidate() | ||||
|             } | ||||
|         } | ||||
|         when_was_i_here_button = rootView.findViewById(R.id.when_was_i_here_button) | ||||
|         when_was_i_here_button.setOnClickListener { | ||||
|             Log.i("VOUSSOIR", "when_was_i_here_button.") | ||||
|             track.load_trkpts(trackbook.database.select_trkpt_bounding_box( | ||||
|                 device_id=track.device_id, | ||||
|                 north=mapView.boundingBox.actualNorth, | ||||
|                 south=mapView.boundingBox.actualSouth, | ||||
|                 east=mapView.boundingBox.lonEast, | ||||
|                 west=mapView.boundingBox.lonWest, | ||||
|             )) | ||||
|             render_track() | ||||
|         } | ||||
| 
 | ||||
|         save_track_button.setOnClickListener { | ||||
|             openSaveGpxDialog() | ||||
|  | @ -303,6 +322,7 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|     fun render_track() | ||||
|     { | ||||
|         Log.i("VOUSSOIR", "TrackFragment.render_track") | ||||
|         mapView.invalidate() | ||||
|         mapView.overlays.clear() | ||||
|         track_segment_overlays.clear() | ||||
|         delete_selected_trkpt_button.visibility = View.GONE | ||||
|  | @ -362,7 +382,7 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|                 } | ||||
|                 val trkpt = (points[point]) as Trkpt | ||||
|                 Log.i("VOUSSOIR", "Clicked ${trkpt.device_id} ${trkpt.time}") | ||||
|                 selected_trkpt_info.text = "${trkpt.time}\n${iso8601(trkpt.time)}\n${trkpt.latitude}\n${trkpt.longitude}" | ||||
|                 selected_trkpt_info.text = "${trkpt.time}\n${iso8601_local(trkpt.time)}\n${trkpt.latitude}\n${trkpt.longitude}" | ||||
|                 delete_selected_trkpt_button.visibility = View.VISIBLE | ||||
|                 return | ||||
|             } | ||||
|  | @ -406,14 +426,17 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
| 
 | ||||
|     private fun setupStatisticsViews() | ||||
|     { | ||||
|         val stats: TrackStatistics = track.statistics() | ||||
|         val stats: TrackStatistics = TrackStatistics(track.trkpts) | ||||
|         trackNameView.text = track.name | ||||
|         distanceView.text = LengthUnitHelper.convertDistanceToString(stats.distance, useImperialUnits) | ||||
|         waypointsView.text = track.trkpts.size.toString() | ||||
|         durationView.text = DateTimeHelper.convertToReadableTime(requireContext(), stats.duration) | ||||
|         velocityView.text = LengthUnitHelper.convertToVelocityString(stats.velocity, useImperialUnits) | ||||
|         recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.start_time) | ||||
|         recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.end_time) | ||||
|         if (track.trkpts.isNotEmpty()) | ||||
|         { | ||||
|             recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(Date(track.trkpts.first().time)) | ||||
|             recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(Date(track.trkpts.last().time)) | ||||
|         } | ||||
|         maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.max_altitude, useImperialUnits) | ||||
|         minAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.min_altitude, useImperialUnits) | ||||
|         positiveElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_ascent, useImperialUnits) | ||||
|  | @ -464,12 +487,13 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|         override fun run() | ||||
|         { | ||||
|             Log.i("VOUSSOIR", "TrackFragment.requery_and_render") | ||||
|             track.start_time = get_datetime(track_query_start_date, track_query_start_time, seconds=0) | ||||
|             track.end_time = get_datetime(track_query_end_date, track_query_end_time, seconds=59) | ||||
|             track.load_trkpts() | ||||
|             track.load_trkpts(trackbook.database.select_trkpt_start_end( | ||||
|                 track.device_id, | ||||
|                 start_time=get_datetime(track_query_start_date, track_query_start_time, seconds=0).time, | ||||
|                 end_time=get_datetime(track_query_end_date, track_query_end_time, seconds=59).time, | ||||
|             )) | ||||
|             Log.i("VOUSSOIR", "TrackFragment.requery_and_render: Reloaded ${track.trkpts.size} trkpts.") | ||||
|             render_track() | ||||
|             mapView.invalidate() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -499,33 +523,22 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener | |||
|     /* Overrides onYesNoDialog from YesNoDialogListener */ | ||||
|     override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String) | ||||
|     { | ||||
|         when (type) | ||||
|         if (type == Keys.DIALOG_DELETE_TRACK && dialogResult && track.trkpts.isNotEmpty()) | ||||
|         { | ||||
|             Keys.DIALOG_DELETE_TRACK -> { | ||||
|                 when (dialogResult) | ||||
|                 { | ||||
|                     // user tapped remove track | ||||
|                     true -> { | ||||
|                         track.delete() | ||||
|                         handler.removeCallbacks(requery_and_render) | ||||
|                         handler.postDelayed(requery_and_render, RERENDER_DELAY) | ||||
|                         // switch to TracklistFragment and remove track there | ||||
|                         // val bundle: Bundle = bundleOf(Keys.ARG_TRACK_ID to layout.track.id) | ||||
|                         // findNavController().navigate(R.id.tracklist_fragment, bundle) | ||||
|                     } | ||||
|                     else -> | ||||
|                     { | ||||
|                         ; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             trackbook.database.delete_trkpt_start_end(track.device_id, track.trkpts.first().time, track.trkpts.last().time) | ||||
|             handler.removeCallbacks(requery_and_render) | ||||
|             handler.postDelayed(requery_and_render, RERENDER_DELAY) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* Opens up a file picker to select the save location */ | ||||
|     private fun openSaveGpxDialog() | ||||
|     { | ||||
|         val export_name: String = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(track.start_time) + " " + track.device_id + Keys.GPX_FILE_EXTENSION | ||||
|         if (track.trkpts.isEmpty()) | ||||
|         { | ||||
|             return | ||||
|         } | ||||
|         val export_name: String = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(track.trkpts.first().time) + " " + track.device_id + Keys.GPX_FILE_EXTENSION | ||||
|         val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { | ||||
|             addCategory(Intent.CATEGORY_OPENABLE) | ||||
|             type = Keys.MIME_TYPE_GPX | ||||
|  |  | |||
|  | @ -20,30 +20,44 @@ | |||
| 
 | ||||
| package net.voussoir.trkpt | ||||
| 
 | ||||
| import android.Manifest | ||||
| import android.app.Notification | ||||
| import android.app.NotificationChannel | ||||
| import android.app.NotificationManager | ||||
| import android.app.PendingIntent | ||||
| import android.app.Service | ||||
| import android.app.TaskStackBuilder | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.content.pm.PackageManager | ||||
| import android.content.SharedPreferences | ||||
| import android.content.pm.PackageManager | ||||
| import android.location.Location | ||||
| import android.location.LocationListener | ||||
| import android.location.LocationManager | ||||
| import android.Manifest | ||||
| import android.app.NotificationChannel | ||||
| import android.app.PendingIntent | ||||
| import android.app.TaskStackBuilder | ||||
| import android.os.* | ||||
| import android.media.AudioManager | ||||
| import android.media.ToneGenerator | ||||
| import android.os.Binder | ||||
| import android.os.Build | ||||
| import android.os.IBinder | ||||
| import android.util.Log | ||||
| import androidx.annotation.RequiresApi | ||||
| import androidx.appcompat.content.res.AppCompatResources | ||||
| import androidx.core.app.NotificationCompat | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.core.graphics.drawable.toBitmap | ||||
| import net.voussoir.trkpt.helpers.PreferencesHelper | ||||
| import net.voussoir.trkpt.helpers.getDefaultLocation | ||||
| import net.voussoir.trkpt.helpers.getLastKnownLocation | ||||
| import net.voussoir.trkpt.helpers.isAccurateEnough | ||||
| import net.voussoir.trkpt.helpers.isBetterLocation | ||||
| import net.voussoir.trkpt.helpers.isDifferentEnough | ||||
| import net.voussoir.trkpt.helpers.isGpsEnabled | ||||
| import net.voussoir.trkpt.helpers.isNetworkEnabled | ||||
| import net.voussoir.trkpt.helpers.isRecentEnough | ||||
| import net.voussoir.trkpt.helpers.iso8601 | ||||
| import net.voussoir.trkpt.helpers.random_device_id | ||||
| import org.osmdroid.util.GeoPoint | ||||
| import java.util.* | ||||
| import net.voussoir.trkpt.helpers.* | ||||
| 
 | ||||
| class TrackerService: Service() | ||||
| { | ||||
|  | @ -64,6 +78,7 @@ class TrackerService: Service() | |||
| 
 | ||||
|     private lateinit var notificationManager: NotificationManager | ||||
|     private lateinit var notification_builder: NotificationCompat.Builder | ||||
|     val beeper = ToneGenerator(AudioManager.STREAM_MUSIC, 100) | ||||
| 
 | ||||
|     private lateinit var locationManager: LocationManager | ||||
|     private lateinit var gpsLocationListener: LocationListener | ||||
|  | @ -189,16 +204,18 @@ class TrackerService: Service() | |||
|             { | ||||
|                 Log.i("VOUSSOIR", "Processing point ${location.time} ${location.latitude}, ${location.longitude}.") | ||||
| 
 | ||||
|                 // beeper.startTone(ToneGenerator.TONE_PROP_ACK, 150) | ||||
| 
 | ||||
|                 if (location.time == currentBestLocation.time) | ||||
|                 { | ||||
|                     return | ||||
|                 } | ||||
| 
 | ||||
|                 // if (! isBetterLocation(location, currentBestLocation)) | ||||
|                 // { | ||||
|                 //     Log.i("VOUSSOIR", "Not better than previous.") | ||||
|                 //     return | ||||
|                 // } | ||||
|                 if (! isBetterLocation(location, currentBestLocation)) | ||||
|                 { | ||||
|                     Log.i("VOUSSOIR", "Not better than previous.") | ||||
|                     return | ||||
|                 } | ||||
| 
 | ||||
|                 currentBestLocation = location | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,7 +35,6 @@ import androidx.recyclerview.widget.LinearLayoutManager | |||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.Dispatchers.Main | ||||
| import net.voussoir.trkpt.helpers.iso8601 | ||||
| import net.voussoir.trkpt.tracklist.TracklistAdapter | ||||
| 
 | ||||
| /* | ||||
|  | @ -80,8 +79,8 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|         val bundle: Bundle = bundleOf( | ||||
|             Keys.ARG_TRACK_TITLE to track.name, | ||||
|             Keys.ARG_TRACK_DEVICE_ID to track.device_id, | ||||
|             Keys.ARG_TRACK_START_TIME to iso8601(track.start_time), | ||||
|             Keys.ARG_TRACK_STOP_TIME to iso8601(track.end_time), | ||||
|             Keys.ARG_TRACK_START_TIME to track._start_time.toString(), | ||||
|             Keys.ARG_TRACK_STOP_TIME to track._end_time.toString(), | ||||
|         ) | ||||
|         findNavController().navigate(R.id.fragment_track, bundle) | ||||
|     } | ||||
|  |  | |||
|  | @ -14,6 +14,12 @@ fun iso8601(timestamp: Long): String | |||
|     return iso8601_format.format(timestamp) | ||||
| } | ||||
| 
 | ||||
| fun iso8601_local(timestamp: Long): String | ||||
| { | ||||
|     val iso8601_format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS") | ||||
|     return iso8601_format.format(timestamp) | ||||
| } | ||||
| 
 | ||||
| fun iso8601(datetime: Date): String | ||||
| { | ||||
|     return iso8601(datetime.time) | ||||
|  |  | |||
|  | @ -23,9 +23,6 @@ import java.text.DateFormat | |||
| import java.util.* | ||||
| import java.util.concurrent.TimeUnit | ||||
| 
 | ||||
| /* | ||||
|  * DateTimeHelper object | ||||
|  */ | ||||
| object DateTimeHelper { | ||||
| 
 | ||||
|     /* Converts milliseconds to mm:ss or hh:mm:ss */ | ||||
|  |  | |||
|  | @ -64,12 +64,12 @@ class TracklistAdapter(val fragment: Fragment, val database: net.voussoir.trkpt. | |||
|             { | ||||
|                 val trackdate = cursor.getString(0) | ||||
|                 val device_id = cursor.getString(1) | ||||
|                 val start_time: Date? = df.parse(trackdate + "T00:00:00.000") | ||||
|                 val stop_time: Date? = df.parse(trackdate + "T23:59:59.999") | ||||
|                 val start_time: Long? = df.parse(trackdate + "T00:00:00.000").time | ||||
|                 val stop_time: Long? = df.parse(trackdate + "T23:59:59.999").time | ||||
|                 Log.i("VOUSSOIR", "TracklistAdapter prep track ${trackdate}") | ||||
|                 if (start_time != null && stop_time != null) | ||||
|                 { | ||||
|                     val track = Track(database=database, device_id=device_id, start_time=start_time, end_time=stop_time) | ||||
|                     val track = Track(database=database, device_id=device_id, _start_time=start_time, _end_time=stop_time) | ||||
|                     track.name = "$trackdate $device_id" | ||||
|                     tracks.add(track) | ||||
|                 } | ||||
|  | @ -115,7 +115,7 @@ class TracklistAdapter(val fragment: Fragment, val database: net.voussoir.trkpt. | |||
|     /* Get track name for given position */ | ||||
|     fun getTrackName(positionInRecyclerView: Int): String | ||||
|     { | ||||
|         return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(tracks[positionInRecyclerView].start_time) | ||||
|         return SimpleDateFormat("yyyy-MM-dd", Locale.US).format(tracks[positionInRecyclerView]._start_time) | ||||
|     } | ||||
| 
 | ||||
|     fun delete_track_at_position(context: Context, index: Int) | ||||
|  |  | |||
|  | @ -21,22 +21,12 @@ | |||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             app:layout_constraintTop_toTopOf="parent"> | ||||
| 
 | ||||
|             <TextView | ||||
|                 android:id="@+id/selected_trkpt_info" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginEnd="8dp" | ||||
|                 android:gravity="right" | ||||
|                 android:text="time\nlat\nlong" | ||||
|                 app:fontFamily="monospace" | ||||
|                 app:layout_constraintEnd_toEndOf="parent" | ||||
|                 app:layout_constraintTop_toTopOf="parent" /> | ||||
| 
 | ||||
|             <ImageButton | ||||
|                 android:id="@+id/delete_selected_trkpt_button" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:contentDescription="Delete selected trackpoint" | ||||
|                 android:tooltipText="Delete selected trackpoint" | ||||
|                 android:src="@drawable/ic_delete_24dp" | ||||
|                 android:visibility="gone" | ||||
|                 app:backgroundTint="@color/default_transparent" | ||||
|  | @ -44,6 +34,18 @@ | |||
|                 app:layout_constraintEnd_toEndOf="parent" | ||||
|                 app:srcCompat="@drawable/ic_delete_24dp" /> | ||||
| 
 | ||||
|             <ImageButton | ||||
|                 android:id="@+id/when_was_i_here_button" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:contentDescription="When was I here?" | ||||
|                 android:tooltipText="When was I here?" | ||||
|                 android:src="@drawable/ic_gps_24dp" | ||||
|                 app:backgroundTint="@color/default_transparent" | ||||
|                 app:layout_constraintTop_toTopOf="parent" | ||||
|                 app:layout_constraintEnd_toEndOf="parent" | ||||
|                 app:srcCompat="@drawable/ic_gps_24dp" /> | ||||
| 
 | ||||
| 
 | ||||
|             <DatePicker | ||||
|                 android:id="@+id/track_query_start_date" | ||||
|  | @ -106,9 +108,18 @@ | |||
|             app:layout_constraintBottom_toBottomOf="parent" | ||||
|             app:layout_constraintEnd_toEndOf="parent" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/trackfragment_tools_constraint_layout"> | ||||
|             app:layout_constraintTop_toBottomOf="@+id/trackfragment_tools_constraint_layout"/> | ||||
| 
 | ||||
|         </org.osmdroid.views.MapView> | ||||
|         <TextView | ||||
|             android:id="@+id/selected_trkpt_info" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginEnd="8dp" | ||||
|             android:gravity="right" | ||||
|             android:text="time\nlat\nlong" | ||||
|             app:fontFamily="monospace" | ||||
|             app:layout_constraintEnd_toEndOf="parent" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/trackfragment_tools_constraint_layout"/> | ||||
| 
 | ||||
|         <com.google.android.material.floatingactionbutton.FloatingActionButton | ||||
|             android:id="@+id/zoom_out_button" | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue