summaryrefslogtreecommitdiff
path: root/test-runner/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'test-runner/src/main')
-rw-r--r--test-runner/src/main/kotlin/org/terst/nav/track/TackDetector.kt50
-rw-r--r--test-runner/src/main/kotlin/org/terst/nav/track/TackEvent.kt9
2 files changed, 59 insertions, 0 deletions
diff --git a/test-runner/src/main/kotlin/org/terst/nav/track/TackDetector.kt b/test-runner/src/main/kotlin/org/terst/nav/track/TackDetector.kt
new file mode 100644
index 0000000..b392f67
--- /dev/null
+++ b/test-runner/src/main/kotlin/org/terst/nav/track/TackDetector.kt
@@ -0,0 +1,50 @@
+package org.terst.nav.track
+
+import kotlin.math.abs
+
+object TackDetector {
+ private const val HALF_WIN = 2 // look 2 points before and after
+ private const val MIN_DELTA = 60.0
+ private const val MAX_DELTA = 140.0
+ private const val SKIP_AFTER = 5 // suppress re-detection for this many indices
+ private const val STABILITY_MAX = 30.0 // max internal spread of a before/after pair
+
+ fun detectTacks(points: List<TrackPoint>): List<TackEvent> {
+ if (points.size < 2 * HALF_WIN + 1) return emptyList()
+
+ val results = mutableListOf<TackEvent>()
+ var nextCandidate = 0
+
+ for (i in HALF_WIN until points.size - HALF_WIN) {
+ if (i < nextCandidate) continue
+
+ // Require both "before" and "after" pairs to be internally stable;
+ // if one spans the transition itself, that window is noise.
+ val spreadBefore = abs(angleDiff(points[i - 2].cogDeg, points[i - 1].cogDeg))
+ val spreadAfter = abs(angleDiff(points[i + 1].cogDeg, points[i + 2].cogDeg))
+ if (spreadBefore > STABILITY_MAX || spreadAfter > STABILITY_MAX) continue
+
+ val cogBefore = circularMean(points[i - 2].cogDeg, points[i - 1].cogDeg)
+ val cogAfter = circularMean(points[i + 1].cogDeg, points[i + 2].cogDeg)
+ val delta = abs(angleDiff(cogBefore, cogAfter))
+
+ if (delta in MIN_DELTA..MAX_DELTA) {
+ results += TackEvent(i, points[i].lat, points[i].lon, cogBefore, cogAfter)
+ nextCandidate = i + SKIP_AFTER
+ }
+ }
+ return results
+ }
+
+ internal fun angleDiff(from: Double, to: Double): Double {
+ var diff = to - from
+ while (diff > 180) diff -= 360
+ while (diff < -180) diff += 360
+ return diff
+ }
+
+ private fun circularMean(a: Double, b: Double): Double {
+ val half = angleDiff(a, b) / 2.0
+ return ((a + half) % 360.0 + 360.0) % 360.0
+ }
+}
diff --git a/test-runner/src/main/kotlin/org/terst/nav/track/TackEvent.kt b/test-runner/src/main/kotlin/org/terst/nav/track/TackEvent.kt
new file mode 100644
index 0000000..7c0bf85
--- /dev/null
+++ b/test-runner/src/main/kotlin/org/terst/nav/track/TackEvent.kt
@@ -0,0 +1,9 @@
+package org.terst.nav.track
+
+data class TackEvent(
+ val index: Int,
+ val lat: Double,
+ val lon: Double,
+ val cogBefore: Double,
+ val cogAfter: Double
+)