Skip to content

Commit

Permalink
Smoothen speed values by using data from two consecutive locations
Browse files Browse the repository at this point in the history
  • Loading branch information
mjaakko committed Jan 17, 2025
1 parent 3df72ab commit 5587e80
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -99,4 +101,10 @@ fun <T> Flow<T>.maxAge(duration: Duration): Flow<T?> = transformLatest { value -
emit(value)
delay(duration)
emit(null)
}
}

fun <T> Flow<T>.pairwise(): Flow<Pair<T, T>> = scan(Pair<T?, T?>(null, null)) { pair, value ->
pair.second to value
}
.filter { it.first != null && it.second != null }
.map { it.first!! to it.second!! }
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import xyz.malkki.neostumbler.scanner.source.BluetoothBeaconSource
import xyz.malkki.neostumbler.scanner.source.CellInfoSource
import xyz.malkki.neostumbler.scanner.source.MultiSubscriptionCellInfoSource
import xyz.malkki.neostumbler.scanner.source.PressureSensorAirPressureSource
import xyz.malkki.neostumbler.scanner.source.SmoothenedGpsSpeedSource
import xyz.malkki.neostumbler.scanner.source.TelephonyManagerCellInfoSource
import xyz.malkki.neostumbler.scanner.source.WifiManagerWifiAccessPointSource
import xyz.malkki.neostumbler.utils.GpsStats
Expand Down Expand Up @@ -218,13 +219,7 @@ class ScannerService : Service() {
started = SharingStarted.WhileSubscribed()
)

val speedFlow = locationFlow.map {
if (it.location.hasSpeed()) {
it.location.speed
} else {
0.0f
}
}
val speedFlow = SmoothenedGpsSpeedSource(locationFlow.map { it.location }).getSpeedFlow()

val cellInfoSource = getCellInfoSource()

Expand Down Expand Up @@ -273,15 +268,17 @@ class ScannerService : Service() {
locationFlow
},
cellInfoSource = {
val scanFrequencyFlow = speedFlow.map { speed -> (cellScanDistance.toDouble() / speed).seconds }
val scanFrequencyFlow = speedFlow
.map { speed -> (cellScanDistance.toDouble() / speed).seconds }

cellInfoSource.getCellInfoFlow(scanFrequencyFlow)
},
bluetoothBeaconSource = {
bluetoothBeaconSource.getBluetoothBeaconFlow()
},
wifiAccessPointSource = {
val scanFrequencyFlow = speedFlow.map { speed -> (wifiScanDistance.toDouble() / speed).seconds }
val scanFrequencyFlow = speedFlow
.map { speed -> (wifiScanDistance.toDouble() / speed).seconds }

wifiAccessPointSource.getWifiAccessPointFlow(scanFrequencyFlow)
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package xyz.malkki.neostumbler.scanner.source

import android.location.Location
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import xyz.malkki.neostumbler.extensions.elapsedRealtimeMillisCompat
import xyz.malkki.neostumbler.extensions.pairwise
import xyz.malkki.neostumbler.scanner.speed.SpeedSource
import kotlin.math.abs
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

//Maximum age difference between two consecutive locations
private val LOCATION_MAX_AGE_DIFF = 5.seconds

//Smoothening factor, i.e. how much to weight previous speed vs. current speed
private const val A = 0.15
private const val B = 1 - A

/**
* Calculates smoothened speed by using data from two consecutive locations
*/
class SmoothenedGpsSpeedSource(private val locationFlow: Flow<Location>) : SpeedSource {
override fun getSpeedFlow(): Flow<Double> {
return locationFlow
.pairwise()
.map { (prevLocation, currentLocation) ->
if (currentLocation.hasSpeed()) {
//Smoothen the speed value by using data from two consecutive locations
if (prevLocation.hasSpeed() && abs(prevLocation.elapsedRealtimeMillisCompat - currentLocation.elapsedRealtimeMillisCompat).milliseconds <= LOCATION_MAX_AGE_DIFF) {
prevLocation.speed * A + currentLocation.speed * B
} else {
currentLocation.speed.toDouble()
}
} else {
0.0
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package xyz.malkki.neostumbler.scanner.speed

import kotlinx.coroutines.flow.Flow

fun interface SpeedSource {
/**
* @return Flow emitting the speed in meters per second
*/
fun getSpeedFlow(): Flow<Double>
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,13 @@ class FlowExtensionsTest {

assertEquals(listOf(1, null, 2, null), values)
}

@Test
fun `Test collecting flow values pairwise`() = runBlocking {
val a = flowOf(1, 2, 3)

val values = a.pairwise().toList()

assertEquals(listOf(1 to 2, 2 to 3), values)
}
}

0 comments on commit 5587e80

Please sign in to comment.