ref #8: Replace fixed arrow images, use gps bearing, moving average
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
|
Before Width: | Height: | Size: 980 B |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 943 B |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 758 B |