From c9e49c6cbd46564e0436db3ac240b278e82cd632 Mon Sep 17 00:00:00 2001 From: fiatcode Date: Sat, 2 May 2026 16:22:02 +0700 Subject: [PATCH] feat: implement two-location speed calculation when native speed unavailable --- .../service/LocationTrackingService.kt | 94 +++++++++++++++++-- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt index 2fa0f0e..6c064a6 100644 --- a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt +++ b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt @@ -13,7 +13,9 @@ import android.net.ConnectivityManager import android.location.Location import android.os.Binder import android.os.Build +import android.os.Handler import android.os.IBinder +import android.os.Looper import android.os.PowerManager import androidx.core.app.NotificationCompat import dev.fiatcode.tracpulse.BridgeModule @@ -57,6 +59,9 @@ class LocationTrackingService : Service() { private var bypassFilterOnce = false private var currentConfig: TrackingConfig? = null + private var pendingSpeedLocation: Location? = null + private var speedRequestPending = false + private val SPEED_CALC_DELAY_MS = 2500L inner class LocalBinder : Binder() { fun getService(): LocationTrackingService = this@LocationTrackingService @@ -143,6 +148,8 @@ class LocationTrackingService : Service() { isTracking = true distanceFilterProcessor.reset() + pendingSpeedLocation = null + speedRequestPending = false prefs.edit().putBoolean("tracking_active", true).apply() Log.d(TAG, "startTracking: set tracking_active=true") @@ -226,15 +233,22 @@ class LocationTrackingService : Service() { } bypassFilterOnce = false + val nativeSpeed = if (location.hasSpeed()) location.speed else null + + if (nativeSpeed == null || nativeSpeed <= 0) { + pendingSpeedLocation = location + requestLocationForSpeedCalculation() + } + val traccarLocation = TraccarLocation( timestamp = location.time, latitude = location.latitude, longitude = location.longitude, accuracy = if (location.hasAccuracy()) location.accuracy else null, - speed = if (location.hasSpeed()) location.speed else null, + speed = nativeSpeed, heading = if (location.hasBearing()) location.bearing else null, altitude = if (location.hasAltitude()) location.altitude else null, - isMoving = location.speed > 1.0f // Consider moving if speed > 1 m/s + isMoving = (nativeSpeed ?: 0f) > 1.0f ) logEvent("LOCATION", "Lat: ${location.latitude}, Lon: ${location.longitude}, Speed: ${location.speed}") @@ -262,10 +276,12 @@ class LocationTrackingService : Service() { apply() } - if (isNetworkAvailable) { - sendLocationToServer(traccarLocation) - } else { - bufferLocation(traccarLocation) + if (nativeSpeed != null && nativeSpeed > 0) { + if (isNetworkAvailable) { + sendLocationToServer(traccarLocation) + } else { + bufferLocation(traccarLocation) + } } } @@ -276,6 +292,58 @@ class LocationTrackingService : Service() { } } + private fun requestLocationForSpeedCalculation() { + if (speedRequestPending) return + + speedRequestPending = true + Handler(Looper.getMainLooper()).postDelayed({ + fusedLocationProvider.getLastLocation { location -> + location?.let { + val calculatedSpeed = distanceFilterProcessor.getCalculatedSpeed(it) + onSpeedLocationReceived(it, calculatedSpeed) + } + speedRequestPending = false + pendingSpeedLocation = null + } + }, SPEED_CALC_DELAY_MS) + } + + private fun onSpeedLocationReceived(location: Location, calculatedSpeed: Float?) { + val config = currentConfig ?: return + + val finalSpeed = if (location.hasSpeed() && location.speed > 0) { + location.speed + } else { + calculatedSpeed + } + + if (finalSpeed == null || finalSpeed <= 0) { + logEvent("SPEED_CALC", "Speed calculation failed - insufficient data") + return + } + + val traccarLocation = TraccarLocation( + timestamp = location.time, + latitude = location.latitude, + longitude = location.longitude, + accuracy = if (location.hasAccuracy()) location.accuracy else null, + speed = finalSpeed, + heading = if (location.hasBearing()) location.bearing else null, + altitude = if (location.hasAltitude()) location.altitude else null, + isMoving = finalSpeed > 1.0f + ) + + logEvent("LOCATION", "Lat: ${location.latitude}, Lon: ${location.longitude}, Calc Speed: ${finalSpeed}") + + updateNotificationSpeed(location, finalSpeed) + + if (isNetworkAvailable) { + sendLocationToServer(traccarLocation) + } else { + bufferLocation(traccarLocation) + } + } + private fun sendLocationToServer(location: TraccarLocation) { val config = currentConfig ?: return val traccarConfig = TraccarConfig( @@ -407,6 +475,20 @@ class LocationTrackingService : Service() { notificationManager.notify(NOTIFICATION_ID, notification) } + private fun updateNotificationSpeed(location: Location, speed: Float) { + val content = buildString { + append("Lat: %.4f Lon: %.4f".format(location.latitude, location.longitude)) + append("\nSpeed: %.0f km/h (calc)".format(speed * 3.6)) + val time = java.text.SimpleDateFormat("HH:mm:ss", java.util.Locale.getDefault()) + .format(java.util.Date(location.time)) + append("\nLast: $time") + } + + val notification = createNotification(content) + val notificationManager = getSystemService(NotificationManager::class.java) + notificationManager.notify(NOTIFICATION_ID, notification) + } + private fun acquireWakeLock() { val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager wakeLock = powerManager.newWakeLock(