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:
Thorsten
2026-04-28 11:52:27 +02:00
parent 8de7b4d886
commit 5019c83b80
3 changed files with 102 additions and 4 deletions
@@ -1,6 +1,10 @@
package de.tstratmann.gpstracker
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
@@ -18,6 +22,7 @@ class TrackDetailActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var binding: ActivityTrackDetailBinding
private lateinit var googleMap: GoogleMap
private lateinit var track: Track
private val waypointMarkers = mutableListOf<Marker>()
companion object {
const val EXTRA_TRACK_NAME = "track_name"
@@ -86,6 +91,93 @@ class TrackDetailActivity : AppCompatActivity(), OnMapReadyCallback {
drawTrackWithSpeedGradient()
addStartEndMarkers()
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() {