ref #8: Replace fixed arrow images, use gps bearing, moving average

This commit is contained in:
Tim Kluge 2024-12-09 21:43:15 +01:00
parent 9eb16bf7af
commit 4f943ebda7
17 changed files with 70 additions and 32 deletions

View File

@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.flow.timeout
import kotlinx.coroutines.time.debounce
@ -167,24 +168,36 @@ fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow<Double> {
return getHeadingFlow()
.filter { it >= 0 }
.combine(currentWeatherData) { cardinalDirection, data -> cardinalDirection to data }
.map { (cardinalDirection, data) ->
val bearing = cardinalDirection * 45.0
.combine(currentWeatherData) { bearing, data -> bearing to data }
.map { (bearing, data) ->
val windBearing = data.current.windDirection + 180
val diff = (signedAngleDifference(bearing, windBearing) + 360) % 360
val diff = signedAngleDifference(bearing, windBearing)
Log.d(KarooHeadwindExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff")
diff
}
}
fun KarooSystemService.getHeadingFlow(): Flow<Int> {
// return flowOf(2)
fun Double.lerp(target: Double, alpha: Double): Double {
return this + (target - this) * alpha
}
return streamDataFlow(DataType.Type.HEADING)
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
.map { it.roundToInt() }
fun KarooSystemService.getHeadingFlow(): Flow<Double> {
//return flowOf(20.0)
return streamDataFlow("TYPE_LOCATION_ID")
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.values }
.map { values ->
val heading = values[DataType.Field.LOC_BEARING]
heading ?: 0.0
}
.distinctUntilChanged()
.scan(emptyList<Double>()) { acc, value ->
val newAcc = acc + value
if (newAcc.size > 3) newAcc.drop(1) else newAcc
}
.map { it.average() }
}
@OptIn(FlowPreview::class)

View File

@ -7,7 +7,6 @@ import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi
import androidx.glance.appwidget.GlanceRemoteViews
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.getRelativeHeadingFlow
import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.streamCurrentWeatherData

View File

@ -1,7 +1,15 @@
package de.timklge.karooheadwind.datatypes
import android.R
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
@ -9,6 +17,7 @@ import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.background
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
@ -20,27 +29,45 @@ import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import de.timklge.karooheadwind.R
import kotlin.math.roundToInt
fun getArrowResourceByBearing(bearing: Int): Int {
val oclock = ((bearing % 360) / 30.0).roundToInt()
return when (oclock){
0 -> R.drawable.arrow_0
1 -> R.drawable.arrow_1
2 -> R.drawable.arrow_2
3 -> R.drawable.arrow_3
4 -> R.drawable.arrow_4
5 -> R.drawable.arrow_5
6 -> R.drawable.arrow_6
7 -> R.drawable.arrow_7
8 -> R.drawable.arrow_8
9 -> R.drawable.arrow_9
10 -> R.drawable.arrow_10
11 -> R.drawable.arrow_11
12 -> R.drawable.arrow_0
else -> error("Bearing $bearing out of range")
val bitmapsByBearing = mutableMapOf<Int, Bitmap>()
fun getArrowBitmapByBearing(bearing: Int): Bitmap {
synchronized(bitmapsByBearing) {
val bearingRounded = (((bearing + 360) / 5.0).roundToInt() * 5) % 360
val storedBitmap = bitmapsByBearing[bearingRounded]
if (storedBitmap != null) return storedBitmap
val bitmap = Bitmap.createBitmap(128, 128, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val paint = Paint().apply {
color = android.graphics.Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 15f
isAntiAlias = true
}
val path = Path().apply {
moveTo(64f, 0f) // Top point of the arrow
lineTo(128f, 128f) // Bottom right point of the arrow
lineTo(64f, 96f) // Middle bottom point of the arrow
lineTo(0f, 128f) // Bottom left point of the arrow
close() // Close the path to form the arrow shape
}
canvas.save()
canvas.rotate(bearing.toFloat(), 64f, 64f) // Rotate the canvas based on the bearing
canvas.scale(0.75f, 0.75f, 64f, 64f) // Scale the arrow down to fit the canvas
canvas.drawPath(path, paint)
canvas.restore()
bitmapsByBearing[bearingRounded] = bitmap
return bitmap
}
}
@ -57,7 +84,7 @@ fun HeadwindDirection(bearing: Int, fontSize: Int, overlayText: String? = null)
) {
Image(
modifier = GlanceModifier.fillMaxSize(),
provider = ImageProvider(getArrowResourceByBearing(bearing)),
provider = ImageProvider(getArrowBitmapByBearing(bearing)),
contentDescription = "Relative wind direction indicator",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))

View File

@ -8,7 +8,6 @@ import androidx.glance.appwidget.GlanceRemoteViews
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.WeatherInterpretation
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamSettings
@ -25,7 +24,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import kotlin.math.roundToInt

View File

@ -9,6 +9,7 @@ import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
@ -57,7 +58,7 @@ fun Weather(current: WeatherInterpretation, windBearing: Int, windSpeed: Int, wi
Row(horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = GlanceModifier.height(20.dp).width(12.dp),
provider = ImageProvider(getArrowResourceByBearing(windBearing)),
provider = ImageProvider(getArrowBitmapByBearing(windBearing)),
contentDescription = "Current wind direction",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 B