From c1bd51eb48c27b8d8cf357445fde0b22c5b4087f Mon Sep 17 00:00:00 2001 From: fiatcode Date: Thu, 30 Apr 2026 11:00:03 +0700 Subject: [PATCH] feat: add DistanceFilterProcessor with Haversine formula --- .../location/DistanceFilterProcessor.kt | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 android/app/src/main/kotlin/com/traccar/traccar_client/location/DistanceFilterProcessor.kt diff --git a/android/app/src/main/kotlin/com/traccar/traccar_client/location/DistanceFilterProcessor.kt b/android/app/src/main/kotlin/com/traccar/traccar_client/location/DistanceFilterProcessor.kt new file mode 100644 index 0000000..5e74019 --- /dev/null +++ b/android/app/src/main/kotlin/com/traccar/traccar_client/location/DistanceFilterProcessor.kt @@ -0,0 +1,97 @@ +package com.traccar.traccar_client.location + +import android.location.Location +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +data class FilterConfig( + val distanceFilter: Int = 75, + val intervalFilter: Int = 300, + val angleFilter: Int = 0 +) + +class DistanceFilterProcessor { + private var lastLatitude: Double? = null + private var lastLongitude: Double? = null + private var lastTimestamp: Long = 0 + private var lastHeading: Float? = null + + fun shouldAccept(location: Location, config: FilterConfig): Boolean { + val now = System.currentTimeMillis() + + // Distance filter + if (config.distanceFilter > 0 && lastLatitude != null && lastLongitude != null) { + val distance = haversineDistance(lastLatitude!!, lastLongitude!!, location.latitude, location.longitude) + if (distance < config.distanceFilter) { + // Check interval as fallback + if (config.intervalFilter > 0) { + val elapsed = (now - lastTimestamp) / 1000 + if (elapsed < config.intervalFilter) { + return false + } + } else { + return false + } + } + } + + // Interval filter (when distance filter is 0) + if (config.distanceFilter == 0 && config.intervalFilter > 0) { + val elapsed = (now - lastTimestamp) / 1000 + if (elapsed < config.intervalFilter && lastTimestamp > 0) { + return false + } + } + + // Angle filter + if (config.angleFilter > 0 && lastHeading != null && location.hasBearing()) { + val headingDelta = abs(location.bearing - lastHeading!!) + // Handle wrap-around at 360 degrees + val normalizedDelta = if (headingDelta > 180) 360 - headingDelta else headingDelta + if (normalizedDelta < config.angleFilter && lastTimestamp > 0) { + return false + } + } + + // Update last known values + lastLatitude = location.latitude + lastLongitude = location.longitude + lastTimestamp = now + if (location.hasBearing()) { + lastHeading = location.bearing + } + + return true + } + + fun reset() { + lastLatitude = null + lastLongitude = null + lastTimestamp = 0 + lastHeading = null + } + + companion object { + /** + * Calculate distance between two coordinates using Haversine formula + * @return distance in meters + */ + fun haversineDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double { + val earthRadius = 6371000.0 // meters + + val dLat = Math.toRadians(lat2 - lat1) + val dLon = Math.toRadians(lon2 - lon1) + + val a = sin(dLat / 2) * sin(dLat / 2) + + cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) * + sin(dLon / 2) * sin(dLon / 2) + + val c = 2 * atan2(sqrt(a), sqrt(1 - a)) + + return earthRadius * c + } + } +} \ No newline at end of file