diff options
| author | Claude <noreply@anthropic.com> | 2026-05-20 22:24:57 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-05-20 22:24:57 +0000 |
| commit | 76f8707b777d7e25b5ba763ac84416d8b327c215 (patch) | |
| tree | 66e27ea7b923b00c6b318b8a3b4713fcc3d1917c | |
| parent | 62cc93086498d57ef7298eddb40cc382dfc10c6d (diff) | |
Color track lines by boat speed using MapLibre data-driven styling
Each focused track is rendered as per-segment LineStrings tagged with
average SOG, interpolated blue→cyan→green→amber→red for 0→13+ knots.
Applies to both the main map focus layer and the track detail sheet.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
3 files changed, 43 insertions, 10 deletions
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..3bcabb1 --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackColors.kt @@ -0,0 +1,35 @@ +package org.terst.nav.track + +import android.graphics.Color +import org.maplibre.android.style.expressions.Expression +import org.maplibre.android.style.layers.PropertyFactory +import org.maplibre.geojson.Feature +import org.maplibre.geojson.FeatureCollection +import org.maplibre.geojson.LineString +import org.maplibre.geojson.Point + +/** Breaks [points] into consecutive 2-point LineString Features, each tagged with "speed" (knots). */ +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) } + } + +/** + * MapLibre expression that interpolates line-color from the "speed" feature property. + * Scale: 0 kt → blue · 4 kt → cyan · 7 kt → green · 10 kt → amber · 13+ kt → red. + */ +fun speedColorExpression(): Expression = Expression.interpolate( + Expression.linear(), + Expression.get("speed"), + Expression.stop(0, Expression.color(Color.parseColor("#2196F3"))), // blue + Expression.stop(4, Expression.color(Color.parseColor("#00BCD4"))), // cyan + Expression.stop(7, Expression.color(Color.parseColor("#4CAF50"))), // green + Expression.stop(10, Expression.color(Color.parseColor("#FFC107"))), // amber + Expression.stop(13, Expression.color(Color.parseColor("#F44336"))) // red +) diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt index cedf4e1..6f3ed82 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt @@ -102,14 +102,14 @@ class TrackDetailSheet : Fragment() { private fun drawTrack(style: Style, points: List<TrackPoint>) { if (points.size < 2) return val source = GeoJsonSource("track-detail-source", - Feature.fromGeometry(LineString.fromLngLats(points.map { Point.fromLngLat(it.lon, it.lat) }))) + FeatureCollection.fromFeatures(speedSegments(points))) style.addSource(source) style.addLayer(LineLayer("track-detail-layer", "track-detail-source").apply { setProperties( - PropertyFactory.lineColor("#2B8FC4"), PropertyFactory.lineWidth(4f), PropertyFactory.lineCap("round"), - PropertyFactory.lineJoin("round") + PropertyFactory.lineJoin("round"), + PropertyFactory.lineColor(speedColorExpression()) ) }) } 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..17e94d0 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 @@ -328,11 +328,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 +362,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 +440,4 @@ class MapHandler(private val maplibreMap: MapLibreMap) { return Polygon.fromLngLats(listOf(points)) } } + |
