Wegpunkt-Pfeile mit Richtung und Info-Fenster; GPX-Schreib-Performance fix
- TrackDetailActivity: Zeigt zoom-adaptive Richtungspfeile (Bearing) als Marker auf der Karte; Klick öffnet Info-Fenster mit Geschwindigkeit und Kurs - GPXManager: appendLocation schreibt nur noch per appendText statt Datei komplett neu zu schreiben (O(1) statt O(n)) - TrackingService: finalizeSession() beim Stoppen aufrufen, um GPX-Footer korrekt abzuschließen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
package de.tstratmann.gpstracker
|
package de.tstratmann.gpstracker
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Path
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@@ -18,6 +22,7 @@ class TrackDetailActivity : AppCompatActivity(), OnMapReadyCallback {
|
|||||||
private lateinit var binding: ActivityTrackDetailBinding
|
private lateinit var binding: ActivityTrackDetailBinding
|
||||||
private lateinit var googleMap: GoogleMap
|
private lateinit var googleMap: GoogleMap
|
||||||
private lateinit var track: Track
|
private lateinit var track: Track
|
||||||
|
private val waypointMarkers = mutableListOf<Marker>()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_TRACK_NAME = "track_name"
|
const val EXTRA_TRACK_NAME = "track_name"
|
||||||
@@ -86,6 +91,93 @@ class TrackDetailActivity : AppCompatActivity(), OnMapReadyCallback {
|
|||||||
drawTrackWithSpeedGradient()
|
drawTrackWithSpeedGradient()
|
||||||
addStartEndMarkers()
|
addStartEndMarkers()
|
||||||
fitMapToTrack()
|
fitMapToTrack()
|
||||||
|
|
||||||
|
googleMap.setOnCameraIdleListener {
|
||||||
|
displayWaypoints(googleMap.cameraPosition.zoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
googleMap.setOnMarkerClickListener { marker ->
|
||||||
|
val point = marker.tag as? TrackPoint ?: return@setOnMarkerClickListener false
|
||||||
|
marker.title = formatWaypointSpeed(point.speed)
|
||||||
|
marker.snippet = "Kurs: ${point.bearing.toInt()}°"
|
||||||
|
marker.showInfoWindow()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getWaypointStride(zoom: Float): Int = when {
|
||||||
|
zoom < 12f -> Int.MAX_VALUE
|
||||||
|
zoom < 13f -> 100
|
||||||
|
zoom < 14f -> 50
|
||||||
|
zoom < 15f -> 20
|
||||||
|
zoom < 16f -> 10
|
||||||
|
zoom < 17f -> 5
|
||||||
|
zoom < 18f -> 3
|
||||||
|
else -> 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createArrowBitmap(bearing: Float): BitmapDescriptor {
|
||||||
|
val size = 48
|
||||||
|
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
val cx = size / 2f
|
||||||
|
val cy = size / 2f
|
||||||
|
|
||||||
|
val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = Color.WHITE
|
||||||
|
style = Paint.Style.FILL
|
||||||
|
}
|
||||||
|
val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = Color.argb(220, 30, 100, 200)
|
||||||
|
style = Paint.Style.STROKE
|
||||||
|
strokeWidth = 3f
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = Path().apply {
|
||||||
|
moveTo(cx, cy - size * 0.38f)
|
||||||
|
lineTo(cx + size * 0.22f, cy + size * 0.30f)
|
||||||
|
lineTo(cx, cy + size * 0.12f)
|
||||||
|
lineTo(cx - size * 0.22f, cy + size * 0.30f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.rotate(bearing, cx, cy)
|
||||||
|
canvas.drawPath(path, fillPaint)
|
||||||
|
canvas.drawPath(path, strokePaint)
|
||||||
|
|
||||||
|
return BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displayWaypoints(zoom: Float) {
|
||||||
|
waypointMarkers.forEach { it.remove() }
|
||||||
|
waypointMarkers.clear()
|
||||||
|
|
||||||
|
val stride = getWaypointStride(zoom)
|
||||||
|
if (stride == Int.MAX_VALUE) return
|
||||||
|
|
||||||
|
track.points.forEachIndexed { index, point ->
|
||||||
|
if (index % stride == 0) {
|
||||||
|
val marker = googleMap.addMarker(
|
||||||
|
MarkerOptions()
|
||||||
|
.position(LatLng(point.latitude, point.longitude))
|
||||||
|
.icon(createArrowBitmap(point.bearing))
|
||||||
|
.anchor(0.5f, 0.5f)
|
||||||
|
.flat(true)
|
||||||
|
) ?: return@forEachIndexed
|
||||||
|
marker.tag = point
|
||||||
|
waypointMarkers.add(marker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatWaypointSpeed(speedMs: Float): String {
|
||||||
|
val useKnots = track.activityType?.useKnots == true
|
||||||
|
return if (useKnots) {
|
||||||
|
val knots = speedMs * 1.94384f
|
||||||
|
"%.1f kn".format(knots)
|
||||||
|
} else {
|
||||||
|
"${(speedMs * 3.6f).toInt()} km/h"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawTrackWithSpeedGradient() {
|
private fun drawTrackWithSpeedGradient() {
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ class GPXManager(private val context: Context) {
|
|||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun finalizeSession() {
|
||||||
|
currentFile?.appendText(getGpxFooter())
|
||||||
|
currentFile = null
|
||||||
|
}
|
||||||
|
|
||||||
fun appendLocation(location: Location) {
|
fun appendLocation(location: Location) {
|
||||||
currentFile?.let { file ->
|
currentFile?.let { file ->
|
||||||
val time = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
val time = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
||||||
@@ -37,8 +42,7 @@ class GPXManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
sb.append("</trkpt>")
|
sb.append("</trkpt>")
|
||||||
|
|
||||||
val content = file.readText().removeSuffix(getGpxFooter())
|
file.appendText(sb.toString())
|
||||||
file.writeText(content + sb.toString() + getGpxFooter())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,9 @@ class TrackingService : Service() {
|
|||||||
speedCount = 0
|
speedCount = 0
|
||||||
_trackingState.value = TrackingData()
|
_trackingState.value = TrackingData()
|
||||||
|
|
||||||
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000)
|
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
|
||||||
.setMinUpdateIntervalMillis(1000)
|
.setMinUpdateIntervalMillis(5000)
|
||||||
|
.setMinUpdateDistanceMeters(10f)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -122,6 +123,7 @@ class TrackingService : Service() {
|
|||||||
fun stopTracking() {
|
fun stopTracking() {
|
||||||
fusedLocationClient.removeLocationUpdates(locationCallback)
|
fusedLocationClient.removeLocationUpdates(locationCallback)
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
|
gpxManager.finalizeSession()
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user