summaryrefslogtreecommitdiff
path: root/android-app/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'android-app/app/src/main')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TackDetector.kt4
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackColors.kt38
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt6
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt20
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt14
5 files changed, 67 insertions, 15 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TackDetector.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TackDetector.kt
index 9668fd1..b0d256b 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/track/TackDetector.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TackDetector.kt
@@ -14,11 +14,11 @@ object TackDetector {
if (points.size < 2 * HALF_WIN + 1) return emptyList()
val results = mutableListOf<TackEvent>()
- var lastTackMs = Long.MIN_VALUE
+ var lastTackMs: Long? = null
for (i in HALF_WIN until points.size - HALF_WIN) {
if (points[i].timestampMs - points.first().timestampMs < START_SKIP_MS) continue
- if (points[i].timestampMs - lastTackMs < MIN_GAP_MS) continue
+ if (lastTackMs != null && points[i].timestampMs - lastTackMs < MIN_GAP_MS) continue
val spreadBefore = abs(angleDiff(points[i - 2].cogDeg, points[i - 1].cogDeg))
val spreadAfter = abs(angleDiff(points[i + 1].cogDeg, points[i + 2].cogDeg))
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackColors.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackColors.kt
new file mode 100644
index 0000000..292240a
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackColors.kt
@@ -0,0 +1,38 @@
+package org.terst.nav.track
+
+import org.maplibre.android.style.expressions.Expression
+import org.maplibre.geojson.Feature
+import org.maplibre.geojson.LineString
+import org.maplibre.geojson.Point
+
+private fun speedToHex(speedKt: Double): String = when {
+ speedKt >= 13 -> "#F44336" // red
+ speedKt >= 10 -> "#FFC107" // amber
+ speedKt >= 7 -> "#4CAF50" // green
+ speedKt >= 4 -> "#00BCD4" // cyan
+ else -> "#2196F3" // blue
+}
+
+/**
+ * Breaks [points] into consecutive 2-point LineString Features.
+ * Each feature carries "speed" (knots) and "color" (CSS hex) properties.
+ */
+fun speedSegments(points: List<TrackPoint>): List<Feature> =
+ (0 until points.size - 1).map { i ->
+ val sog = (points[i].sogKnots + points[i + 1].sogKnots) / 2.0
+ Feature.fromGeometry(
+ LineString.fromLngLats(listOf(
+ Point.fromLngLat(points[i].lon, points[i].lat),
+ Point.fromLngLat(points[i + 1].lon, points[i + 1].lat)
+ ))
+ ).apply {
+ addNumberProperty("speed", sog)
+ addStringProperty("color", speedToHex(sog))
+ }
+ }
+
+/**
+ * Returns the lineColor expression for speed-colored segments.
+ * Reads the pre-computed "color" string property from each feature.
+ */
+fun speedColorExpression(): Expression = Expression.get("color")
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
index 0cc098b..0bf1dff 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
@@ -148,7 +148,11 @@ class TrackRepository(context: Context) {
suspend fun getSavedTracks(): List<SavedTrack> =
getPastTracks()
.filter { it.isNotEmpty() }
- .map { it.toSavedTrack() }
+ .mapNotNull { points ->
+ runCatching { points.toSavedTrack() }
+ .onFailure { e -> NavLogger.e("track", "toSavedTrack failed: ${e.javaClass.simpleName}: ${e.message}") }
+ .getOrNull()
+ }
fun safState(): SafState = storage.safState()
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt
index 49ce0f5..49de36f 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt
@@ -106,7 +106,9 @@ class MainViewModel(
runCatching { trackRepository.getPastTracks() }
.onSuccess {
_pastTracks.value = it
- _savedTracks.value = trackRepository.getSavedTracks()
+ runCatching { trackRepository.getSavedTracks() }
+ .onSuccess { saved -> _savedTracks.value = saved }
+ .onFailure { e -> NavLogger.e("track", "init: getSavedTracks failed: ${e.javaClass.simpleName}: ${e.message}") }
}
.onFailure { e -> NavLogger.e("track", "init: failed to load past tracks: ${e.javaClass.simpleName}: ${e.message}") }
}
@@ -170,7 +172,9 @@ class MainViewModel(
viewModelScope.launch {
val summary = trackRepository.stopTrack()
_pastTracks.value = trackRepository.getPastTracks()
- _savedTracks.value = trackRepository.getSavedTracks()
+ runCatching { trackRepository.getSavedTracks() }
+ .onSuccess { _savedTracks.value = it }
+ .onFailure { e -> NavLogger.e("track", "stopTrack: getSavedTracks failed: ${e.javaClass.simpleName}: ${e.message}") }
_trackPoints.value = emptyList()
_isRecording.value = false
_trackStats.value = null
@@ -187,7 +191,9 @@ class MainViewModel(
points.forEach { trackRepository.addPoint(it) }
val summary = trackRepository.stopTrack()
_pastTracks.value = trackRepository.getPastTracks()
- _savedTracks.value = trackRepository.getSavedTracks()
+ runCatching { trackRepository.getSavedTracks() }
+ .onSuccess { _savedTracks.value = it }
+ .onFailure { e -> NavLogger.e("track", "injectTestTrack: getSavedTracks failed: ${e.javaClass.simpleName}: ${e.message}") }
_trackPoints.value = emptyList()
_isRecording.value = false
_trackStats.value = null
@@ -430,7 +436,9 @@ class MainViewModel(
viewModelScope.launch {
trackRepository.importTrack(points)
_pastTracks.value = trackRepository.getPastTracks()
- _savedTracks.value = trackRepository.getSavedTracks()
+ runCatching { trackRepository.getSavedTracks() }
+ .onSuccess { _savedTracks.value = it }
+ .onFailure { e -> NavLogger.e("track", "addImportedTrack: getSavedTracks failed: ${e.javaClass.simpleName}: ${e.message}") }
}
}
@@ -444,7 +452,9 @@ class MainViewModel(
runCatching { trackRepository.reloadTracks() }
.onSuccess { tracks ->
_pastTracks.value = tracks
- _savedTracks.value = trackRepository.getSavedTracks()
+ runCatching { trackRepository.getSavedTracks() }
+ .onSuccess { saved -> _savedTracks.value = saved }
+ .onFailure { e -> NavLogger.e("track", "reloadPastTracks: getSavedTracks failed: ${e.javaClass.simpleName}: ${e.message}") }
NavLogger.i("track", "reloadPastTracks: ${tracks.size} tracks loaded")
}
.onFailure { e ->
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt
index 7614a49..a3c1eac 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt
@@ -26,6 +26,8 @@ import org.terst.nav.TidalCurrentState
import org.terst.nav.data.model.WindArrow
import org.terst.nav.track.TackEvent
import org.terst.nav.track.TrackPoint
+import org.terst.nav.track.speedColorExpression
+import org.terst.nav.track.speedSegments
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
@@ -328,11 +330,10 @@ class MapHandler(private val maplibreMap: MapLibreMap) {
style.addSource(trackPastFocusSource!!)
style.addLayer(LineLayer(TRACK_PAST_FOCUS_LAYER_ID, TRACK_PAST_FOCUS_SOURCE_ID).apply {
setProperties(
- PropertyFactory.lineColor("#2B8FC4"),
- PropertyFactory.lineOpacity(1.0f),
PropertyFactory.lineWidth(4f),
PropertyFactory.lineCap("round"),
- PropertyFactory.lineJoin("round")
+ PropertyFactory.lineJoin("round"),
+ PropertyFactory.lineColor(speedColorExpression())
)
})
}
@@ -363,12 +364,10 @@ class MapHandler(private val maplibreMap: MapLibreMap) {
}
}
- /** Highlights [points] in the focus layer (full-opacity bright blue over the dim layer). */
+ /** Highlights [points] in the focus layer, colored by boat speed. */
fun setFocusedTrackPoints(points: List<TrackPoint>?) {
if (points != null && points.size >= 2) {
- trackPastFocusSource?.setGeoJson(
- Feature.fromGeometry(LineString.fromLngLats(points.map { Point.fromLngLat(it.lon, it.lat) }))
- )
+ trackPastFocusSource?.setGeoJson(FeatureCollection.fromFeatures(speedSegments(points)))
} else {
trackPastFocusSource?.setGeoJson(FeatureCollection.fromFeatures(emptyList()))
}
@@ -443,3 +442,4 @@ class MapHandler(private val maplibreMap: MapLibreMap) {
return Polygon.fromLngLats(listOf(points))
}
}
+