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.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.single import kotlinx.coroutines.flow.single
import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.flow.timeout
import kotlinx.coroutines.time.debounce import kotlinx.coroutines.time.debounce
@ -167,24 +168,36 @@ fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow<Double> {
return getHeadingFlow() return getHeadingFlow()
.filter { it >= 0 } .filter { it >= 0 }
.combine(currentWeatherData) { cardinalDirection, data -> cardinalDirection to data } .combine(currentWeatherData) { bearing, data -> bearing to data }
.map { (cardinalDirection, data) -> .map { (bearing, data) ->
val bearing = cardinalDirection * 45.0
val windBearing = data.current.windDirection + 180 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") Log.d(KarooHeadwindExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff")
diff diff
} }
} }
fun KarooSystemService.getHeadingFlow(): Flow<Int> { fun Double.lerp(target: Double, alpha: Double): Double {
// return flowOf(2) return this + (target - this) * alpha
}
return streamDataFlow(DataType.Type.HEADING) fun KarooSystemService.getHeadingFlow(): Flow<Double> {
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue } //return flowOf(20.0)
.map { it.roundToInt() }
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() .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) @OptIn(FlowPreview::class)

View File

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

View File

@ -1,7 +1,15 @@
package de.timklge.karooheadwind.datatypes 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.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -9,6 +17,7 @@ import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier import androidx.glance.GlanceModifier
import androidx.glance.Image import androidx.glance.Image
import androidx.glance.ImageProvider import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.background import androidx.glance.background
import androidx.glance.color.ColorProvider import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment import androidx.glance.layout.Alignment
@ -20,27 +29,45 @@ import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview import androidx.glance.preview.Preview
import androidx.glance.text.Text import androidx.glance.text.Text
import androidx.glance.text.TextStyle import androidx.glance.text.TextStyle
import de.timklge.karooheadwind.R
import kotlin.math.roundToInt import kotlin.math.roundToInt
fun getArrowResourceByBearing(bearing: Int): Int {
val oclock = ((bearing % 360) / 30.0).roundToInt()
return when (oclock){ val bitmapsByBearing = mutableMapOf<Int, Bitmap>()
0 -> R.drawable.arrow_0
1 -> R.drawable.arrow_1 fun getArrowBitmapByBearing(bearing: Int): Bitmap {
2 -> R.drawable.arrow_2 synchronized(bitmapsByBearing) {
3 -> R.drawable.arrow_3 val bearingRounded = (((bearing + 360) / 5.0).roundToInt() * 5) % 360
4 -> R.drawable.arrow_4
5 -> R.drawable.arrow_5 val storedBitmap = bitmapsByBearing[bearingRounded]
6 -> R.drawable.arrow_6 if (storedBitmap != null) return storedBitmap
7 -> R.drawable.arrow_7
8 -> R.drawable.arrow_8 val bitmap = Bitmap.createBitmap(128, 128, Bitmap.Config.ARGB_8888)
9 -> R.drawable.arrow_9 val canvas = Canvas(bitmap)
10 -> R.drawable.arrow_10
11 -> R.drawable.arrow_11 val paint = Paint().apply {
12 -> R.drawable.arrow_0 color = android.graphics.Color.WHITE
else -> error("Bearing $bearing out of range") 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( Image(
modifier = GlanceModifier.fillMaxSize(), modifier = GlanceModifier.fillMaxSize(),
provider = ImageProvider(getArrowResourceByBearing(bearing)), provider = ImageProvider(getArrowBitmapByBearing(bearing)),
contentDescription = "Relative wind direction indicator", contentDescription = "Relative wind direction indicator",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) 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.KarooHeadwindExtension
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
import de.timklge.karooheadwind.WeatherInterpretation import de.timklge.karooheadwind.WeatherInterpretation
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.screens.HeadwindSettings
import de.timklge.karooheadwind.streamCurrentWeatherData import de.timklge.karooheadwind.streamCurrentWeatherData
import de.timklge.karooheadwind.streamSettings import de.timklge.karooheadwind.streamSettings
@ -25,7 +24,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt

View File

@ -9,6 +9,7 @@ import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier import androidx.glance.GlanceModifier
import androidx.glance.Image import androidx.glance.Image
import androidx.glance.ImageProvider import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.color.ColorProvider import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment import androidx.glance.layout.Alignment
import androidx.glance.layout.Column 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) { Row(horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically) {
Image( Image(
modifier = GlanceModifier.height(20.dp).width(12.dp), modifier = GlanceModifier.height(20.dp).width(12.dp),
provider = ImageProvider(getArrowResourceByBearing(windBearing)), provider = ImageProvider(getArrowBitmapByBearing(windBearing)),
contentDescription = "Current wind direction", contentDescription = "Current wind direction",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) 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