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.
|
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.
|
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.
|
• 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.
|
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()
|
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)
|
fun insert_trkpt(trkpt: net.voussoir.trkpt.Trkpt)
|
||||||
{
|
{
|
||||||
Log.i("VOUSSOIR", "Database.insert_trkpt")
|
Log.i("VOUSSOIR", "Database.insert_trkpt")
|
||||||
|
@ -76,6 +84,56 @@ class Database(val trackbook: net.voussoir.trkpt.Trackbook)
|
||||||
connection.insert("trkpt", null, values)
|
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)
|
fun delete_homepoint(id: Long)
|
||||||
{
|
{
|
||||||
Log.i("VOUSSOIR", "Database.delete_homepoint")
|
Log.i("VOUSSOIR", "Database.delete_homepoint")
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
package net.voussoir.trkpt
|
package net.voussoir.trkpt
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
@ -34,23 +33,13 @@ import java.util.*
|
||||||
data class Track (
|
data class Track (
|
||||||
val database: net.voussoir.trkpt.Database,
|
val database: net.voussoir.trkpt.Database,
|
||||||
val device_id: String,
|
val device_id: String,
|
||||||
var start_time: Date,
|
|
||||||
var end_time: Date,
|
|
||||||
var name: String = "",
|
var name: String = "",
|
||||||
|
var _start_time: Long = 0L,
|
||||||
|
var _end_time: Long = 0L,
|
||||||
val trkpts: ArrayList<Trkpt> = ArrayList<Trkpt>(),
|
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,
|
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?
|
fun export_gpx(context: Context, fileuri: Uri): Uri?
|
||||||
{
|
{
|
||||||
if (! database.ready)
|
if (! database.ready)
|
||||||
|
@ -88,7 +77,7 @@ data class Track (
|
||||||
write("\t\t<trkseg>")
|
write("\t\t<trkseg>")
|
||||||
|
|
||||||
var previous: Trkpt? = null
|
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))
|
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<trkseg>")
|
||||||
}
|
}
|
||||||
write("\t\t\t<trkpt lat=\"${trkpt.latitude}\" lon=\"${trkpt.longitude}\">")
|
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<time>${iso8601(trkpt.time)}</time>")
|
||||||
write("\t\t\t\t<unix>${trkpt.time}</unix>")
|
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\t<sat>${trkpt.numberSatellites}</sat>")
|
||||||
write("\t\t\t</trkpt>")
|
write("\t\t\t</trkpt>")
|
||||||
previous = trkpt
|
previous = trkpt
|
||||||
|
@ -114,107 +104,65 @@ data class Track (
|
||||||
return fileuri
|
return fileuri
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load_trkpts()
|
fun load_trkpts(points: Iterator<Trkpt>)
|
||||||
{
|
{
|
||||||
this.trkpts.clear()
|
this.trkpts.clear()
|
||||||
trkpt_generator().forEach { trkpt -> this.trkpts.add(trkpt) }
|
points.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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TrackStatistics(
|
data class TrackStatistics(
|
||||||
|
val trkpts: ArrayList<Trkpt>,
|
||||||
var distance: Double = 0.0,
|
var distance: Double = 0.0,
|
||||||
var duration: Long = 0,
|
var duration: Long = 0,
|
||||||
var velocity: Double = 0.0,
|
var velocity: Double = 0.0,
|
||||||
var total_ascent: Double = 0.0,
|
var total_ascent: Double = 0.0,
|
||||||
var total_descent: Double = 0.0,
|
var total_descent: Double = 0.0,
|
||||||
var max_altitude: 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.PreferencesHelper
|
||||||
import net.voussoir.trkpt.helpers.UiHelper
|
import net.voussoir.trkpt.helpers.UiHelper
|
||||||
import net.voussoir.trkpt.helpers.create_start_end_markers
|
import net.voussoir.trkpt.helpers.create_start_end_markers
|
||||||
import net.voussoir.trkpt.helpers.iso8601
|
import net.voussoir.trkpt.helpers.iso8601_local
|
||||||
import net.voussoir.trkpt.helpers.iso8601_parse
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
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_date: DatePicker
|
||||||
lateinit var track_query_end_time: TimePicker
|
lateinit var track_query_end_time: TimePicker
|
||||||
lateinit var delete_selected_trkpt_button: ImageButton
|
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_start_time_previous: Int = 0
|
||||||
var track_query_end_time_previous: Int = 0
|
var track_query_end_time_previous: Int = 0
|
||||||
private lateinit var mapView: MapView
|
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
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
|
||||||
{
|
{
|
||||||
this.trackbook = (requireContext().applicationContext as Trackbook)
|
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(
|
track = Track(
|
||||||
database=database,
|
database=this.trackbook.database,
|
||||||
name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""),
|
|
||||||
device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""),
|
device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""),
|
||||||
start_time=iso8601_parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!),
|
name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""),
|
||||||
end_time=iso8601_parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!),
|
|
||||||
)
|
)
|
||||||
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)
|
rootView = inflater.inflate(R.layout.fragment_track, container, false)
|
||||||
mapView = rootView.findViewById(R.id.map)
|
mapView = rootView.findViewById(R.id.map)
|
||||||
save_track_button = rootView.findViewById(R.id.save_button)
|
save_track_button = rootView.findViewById(R.id.save_button)
|
||||||
|
@ -142,7 +145,11 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||||
mapView.isVerticalMapRepetitionEnabled = false
|
mapView.isVerticalMapRepetitionEnabled = false
|
||||||
mapView.setMultiTouchControls(true)
|
mapView.setMultiTouchControls(true)
|
||||||
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
|
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)
|
controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL)
|
||||||
|
|
||||||
// trkpt_infowindow = MarkerInfoWindow(R.layout.trkpt_infowindow, mapView)
|
// 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)
|
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_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()) track.end_time else Date(track.trkpts.last().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)
|
track_query_start_date = rootView.findViewById(R.id.track_query_start_date)
|
||||||
val start_cal = GregorianCalendar()
|
val start_cal = GregorianCalendar()
|
||||||
|
@ -263,6 +270,18 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||||
mapView.invalidate()
|
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 {
|
save_track_button.setOnClickListener {
|
||||||
openSaveGpxDialog()
|
openSaveGpxDialog()
|
||||||
|
@ -303,6 +322,7 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||||
fun render_track()
|
fun render_track()
|
||||||
{
|
{
|
||||||
Log.i("VOUSSOIR", "TrackFragment.render_track")
|
Log.i("VOUSSOIR", "TrackFragment.render_track")
|
||||||
|
mapView.invalidate()
|
||||||
mapView.overlays.clear()
|
mapView.overlays.clear()
|
||||||
track_segment_overlays.clear()
|
track_segment_overlays.clear()
|
||||||
delete_selected_trkpt_button.visibility = View.GONE
|
delete_selected_trkpt_button.visibility = View.GONE
|
||||||
|
@ -362,7 +382,7 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||||
}
|
}
|
||||||
val trkpt = (points[point]) as Trkpt
|
val trkpt = (points[point]) as Trkpt
|
||||||
Log.i("VOUSSOIR", "Clicked ${trkpt.device_id} ${trkpt.time}")
|
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
|
delete_selected_trkpt_button.visibility = View.VISIBLE
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -406,14 +426,17 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||||
|
|
||||||
private fun setupStatisticsViews()
|
private fun setupStatisticsViews()
|
||||||
{
|
{
|
||||||
val stats: TrackStatistics = track.statistics()
|
val stats: TrackStatistics = TrackStatistics(track.trkpts)
|
||||||
trackNameView.text = track.name
|
trackNameView.text = track.name
|
||||||
distanceView.text = LengthUnitHelper.convertDistanceToString(stats.distance, useImperialUnits)
|
distanceView.text = LengthUnitHelper.convertDistanceToString(stats.distance, useImperialUnits)
|
||||||
waypointsView.text = track.trkpts.size.toString()
|
waypointsView.text = track.trkpts.size.toString()
|
||||||
durationView.text = DateTimeHelper.convertToReadableTime(requireContext(), stats.duration)
|
durationView.text = DateTimeHelper.convertToReadableTime(requireContext(), stats.duration)
|
||||||
velocityView.text = LengthUnitHelper.convertToVelocityString(stats.velocity, useImperialUnits)
|
velocityView.text = LengthUnitHelper.convertToVelocityString(stats.velocity, useImperialUnits)
|
||||||
recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.start_time)
|
if (track.trkpts.isNotEmpty())
|
||||||
recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.end_time)
|
{
|
||||||
|
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)
|
maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.max_altitude, useImperialUnits)
|
||||||
minAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.min_altitude, useImperialUnits)
|
minAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.min_altitude, useImperialUnits)
|
||||||
positiveElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_ascent, useImperialUnits)
|
positiveElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_ascent, useImperialUnits)
|
||||||
|
@ -464,12 +487,13 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||||
override fun run()
|
override fun run()
|
||||||
{
|
{
|
||||||
Log.i("VOUSSOIR", "TrackFragment.requery_and_render")
|
Log.i("VOUSSOIR", "TrackFragment.requery_and_render")
|
||||||
track.start_time = get_datetime(track_query_start_date, track_query_start_time, seconds=0)
|
track.load_trkpts(trackbook.database.select_trkpt_start_end(
|
||||||
track.end_time = get_datetime(track_query_end_date, track_query_end_time, seconds=59)
|
track.device_id,
|
||||||
track.load_trkpts()
|
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.")
|
Log.i("VOUSSOIR", "TrackFragment.requery_and_render: Reloaded ${track.trkpts.size} trkpts.")
|
||||||
render_track()
|
render_track()
|
||||||
mapView.invalidate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,33 +523,22 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
|
||||||
/* Overrides onYesNoDialog from YesNoDialogListener */
|
/* Overrides onYesNoDialog from YesNoDialogListener */
|
||||||
override fun onYesNoDialog(type: Int, dialogResult: Boolean, payload: Int, payloadString: String)
|
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 -> {
|
trackbook.database.delete_trkpt_start_end(track.device_id, track.trkpts.first().time, track.trkpts.last().time)
|
||||||
when (dialogResult)
|
handler.removeCallbacks(requery_and_render)
|
||||||
{
|
handler.postDelayed(requery_and_render, RERENDER_DELAY)
|
||||||
// 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 ->
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opens up a file picker to select the save location */
|
/* Opens up a file picker to select the save location */
|
||||||
private fun openSaveGpxDialog()
|
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 {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
type = Keys.MIME_TYPE_GPX
|
type = Keys.MIME_TYPE_GPX
|
||||||
|
|
|
@ -20,30 +20,44 @@
|
||||||
|
|
||||||
package net.voussoir.trkpt
|
package net.voussoir.trkpt
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationListener
|
import android.location.LocationListener
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.Manifest
|
import android.media.AudioManager
|
||||||
import android.app.NotificationChannel
|
import android.media.ToneGenerator
|
||||||
import android.app.PendingIntent
|
import android.os.Binder
|
||||||
import android.app.TaskStackBuilder
|
import android.os.Build
|
||||||
import android.os.*
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
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 org.osmdroid.util.GeoPoint
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import net.voussoir.trkpt.helpers.*
|
|
||||||
|
|
||||||
class TrackerService: Service()
|
class TrackerService: Service()
|
||||||
{
|
{
|
||||||
|
@ -64,6 +78,7 @@ class TrackerService: Service()
|
||||||
|
|
||||||
private lateinit var notificationManager: NotificationManager
|
private lateinit var notificationManager: NotificationManager
|
||||||
private lateinit var notification_builder: NotificationCompat.Builder
|
private lateinit var notification_builder: NotificationCompat.Builder
|
||||||
|
val beeper = ToneGenerator(AudioManager.STREAM_MUSIC, 100)
|
||||||
|
|
||||||
private lateinit var locationManager: LocationManager
|
private lateinit var locationManager: LocationManager
|
||||||
private lateinit var gpsLocationListener: LocationListener
|
private lateinit var gpsLocationListener: LocationListener
|
||||||
|
@ -189,16 +204,18 @@ class TrackerService: Service()
|
||||||
{
|
{
|
||||||
Log.i("VOUSSOIR", "Processing point ${location.time} ${location.latitude}, ${location.longitude}.")
|
Log.i("VOUSSOIR", "Processing point ${location.time} ${location.latitude}, ${location.longitude}.")
|
||||||
|
|
||||||
|
// beeper.startTone(ToneGenerator.TONE_PROP_ACK, 150)
|
||||||
|
|
||||||
if (location.time == currentBestLocation.time)
|
if (location.time == currentBestLocation.time)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (! isBetterLocation(location, currentBestLocation))
|
if (! isBetterLocation(location, currentBestLocation))
|
||||||
// {
|
{
|
||||||
// Log.i("VOUSSOIR", "Not better than previous.")
|
Log.i("VOUSSOIR", "Not better than previous.")
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
|
|
||||||
currentBestLocation = location
|
currentBestLocation = location
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import net.voussoir.trkpt.helpers.iso8601
|
|
||||||
import net.voussoir.trkpt.tracklist.TracklistAdapter
|
import net.voussoir.trkpt.tracklist.TracklistAdapter
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -80,8 +79,8 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener,
|
||||||
val bundle: Bundle = bundleOf(
|
val bundle: Bundle = bundleOf(
|
||||||
Keys.ARG_TRACK_TITLE to track.name,
|
Keys.ARG_TRACK_TITLE to track.name,
|
||||||
Keys.ARG_TRACK_DEVICE_ID to track.device_id,
|
Keys.ARG_TRACK_DEVICE_ID to track.device_id,
|
||||||
Keys.ARG_TRACK_START_TIME to iso8601(track.start_time),
|
Keys.ARG_TRACK_START_TIME to track._start_time.toString(),
|
||||||
Keys.ARG_TRACK_STOP_TIME to iso8601(track.end_time),
|
Keys.ARG_TRACK_STOP_TIME to track._end_time.toString(),
|
||||||
)
|
)
|
||||||
findNavController().navigate(R.id.fragment_track, bundle)
|
findNavController().navigate(R.id.fragment_track, bundle)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,12 @@ fun iso8601(timestamp: Long): String
|
||||||
return iso8601_format.format(timestamp)
|
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
|
fun iso8601(datetime: Date): String
|
||||||
{
|
{
|
||||||
return iso8601(datetime.time)
|
return iso8601(datetime.time)
|
||||||
|
|
|
@ -23,9 +23,6 @@ import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/*
|
|
||||||
* DateTimeHelper object
|
|
||||||
*/
|
|
||||||
object DateTimeHelper {
|
object DateTimeHelper {
|
||||||
|
|
||||||
/* Converts milliseconds to mm:ss or hh:mm:ss */
|
/* 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 trackdate = cursor.getString(0)
|
||||||
val device_id = cursor.getString(1)
|
val device_id = cursor.getString(1)
|
||||||
val start_time: Date? = df.parse(trackdate + "T00:00:00.000")
|
val start_time: Long? = df.parse(trackdate + "T00:00:00.000").time
|
||||||
val stop_time: Date? = df.parse(trackdate + "T23:59:59.999")
|
val stop_time: Long? = df.parse(trackdate + "T23:59:59.999").time
|
||||||
Log.i("VOUSSOIR", "TracklistAdapter prep track ${trackdate}")
|
Log.i("VOUSSOIR", "TracklistAdapter prep track ${trackdate}")
|
||||||
if (start_time != null && stop_time != null)
|
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"
|
track.name = "$trackdate $device_id"
|
||||||
tracks.add(track)
|
tracks.add(track)
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ class TracklistAdapter(val fragment: Fragment, val database: net.voussoir.trkpt.
|
||||||
/* Get track name for given position */
|
/* Get track name for given position */
|
||||||
fun getTrackName(positionInRecyclerView: Int): String
|
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)
|
fun delete_track_at_position(context: Context, index: Int)
|
||||||
|
|
|
@ -21,22 +21,12 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="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
|
<ImageButton
|
||||||
android:id="@+id/delete_selected_trkpt_button"
|
android:id="@+id/delete_selected_trkpt_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="Delete selected trackpoint"
|
android:contentDescription="Delete selected trackpoint"
|
||||||
|
android:tooltipText="Delete selected trackpoint"
|
||||||
android:src="@drawable/ic_delete_24dp"
|
android:src="@drawable/ic_delete_24dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:backgroundTint="@color/default_transparent"
|
app:backgroundTint="@color/default_transparent"
|
||||||
|
@ -44,6 +34,18 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:srcCompat="@drawable/ic_delete_24dp" />
|
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
|
<DatePicker
|
||||||
android:id="@+id/track_query_start_date"
|
android:id="@+id/track_query_start_date"
|
||||||
|
@ -106,9 +108,18 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="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
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/zoom_out_button"
|
android:id="@+id/zoom_out_button"
|
||||||
|
|
Loading…
Reference in a new issue