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/ais/CpaAlert.kt23
-rw-r--r--test-runner/src/main/kotlin/org/terst/nav/ais/CpaCalculator.kt1
-rw-r--r--test-runner/src/main/kotlin/org/terst/nav/track/TrackRepository.kt30
-rw-r--r--test-runner/src/main/kotlin/org/terst/nav/track/TrackStats.kt15
4 files changed, 68 insertions, 1 deletions
diff --git a/test-runner/src/main/kotlin/org/terst/nav/ais/CpaAlert.kt b/test-runner/src/main/kotlin/org/terst/nav/ais/CpaAlert.kt
new file mode 100644
index 0000000..1d635c0
--- /dev/null
+++ b/test-runner/src/main/kotlin/org/terst/nav/ais/CpaAlert.kt
@@ -0,0 +1,23 @@
+package org.terst.nav.ais
+
+enum class CpaSeverity { CAUTION, WARNING, DANGER }
+
+data class CpaAlert(
+ val mmsi: Int,
+ val vesselName: String,
+ val cpaNm: Double,
+ val tcpaMin: Double,
+ val severity: CpaSeverity
+)
+
+object CpaThresholds {
+ const val DANGER_CPA_NM = 0.5
+ const val WARNING_CPA_NM = 1.0
+ const val CAUTION_CPA_NM = 2.0
+ const val MAX_TCPA_MIN = 30.0 // ignore contacts closing in > 30 min
+}
+
+fun CpaAlert.label(): String {
+ val name = if (vesselName.isNotBlank()) vesselName else "MMSI $mmsi"
+ return "%-12s CPA %.1fnm T+%.0fmin".format(name.take(12), cpaNm, tcpaMin)
+}
diff --git a/test-runner/src/main/kotlin/org/terst/nav/ais/CpaCalculator.kt b/test-runner/src/main/kotlin/org/terst/nav/ais/CpaCalculator.kt
index 7e98451..298af59 100644
--- a/test-runner/src/main/kotlin/org/terst/nav/ais/CpaCalculator.kt
+++ b/test-runner/src/main/kotlin/org/terst/nav/ais/CpaCalculator.kt
@@ -2,7 +2,6 @@ package org.terst.nav.ais
import kotlin.math.cos
import kotlin.math.sqrt
-import kotlin.math.PI
object CpaCalculator {
fun compute(
diff --git a/test-runner/src/main/kotlin/org/terst/nav/track/TrackRepository.kt b/test-runner/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
index 7953822..a68e315 100644
--- a/test-runner/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
+++ b/test-runner/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
@@ -1,5 +1,10 @@
package org.terst.nav.track
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.sqrt
+
class TrackRepository {
var isRecording: Boolean = false
@@ -23,4 +28,29 @@ class TrackRepository {
}
fun getPoints(): List<TrackPoint> = points.toList()
+
+ fun computeStats(): TrackStats? {
+ if (points.size < 2) return null
+ var distNm = 0.0
+ for (i in 1 until points.size) {
+ distNm += haversineNm(points[i - 1].lat, points[i - 1].lon, points[i].lat, points[i].lon)
+ }
+ val durationMs = points.last().timestampMs - points.first().timestampMs
+ val avgSog = if (durationMs > 0) {
+ points.map { it.sogKnots }.average()
+ } else 0.0
+ return TrackStats(distNm, durationMs, avgSog)
+ }
+
+ companion object {
+ fun haversineNm(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
+ val r = 3440.065 // Earth radius in nautical miles
+ 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)
+ return r * 2 * atan2(sqrt(a), sqrt(1 - a))
+ }
+ }
}
diff --git a/test-runner/src/main/kotlin/org/terst/nav/track/TrackStats.kt b/test-runner/src/main/kotlin/org/terst/nav/track/TrackStats.kt
new file mode 100644
index 0000000..39153c6
--- /dev/null
+++ b/test-runner/src/main/kotlin/org/terst/nav/track/TrackStats.kt
@@ -0,0 +1,15 @@
+package org.terst.nav.track
+
+data class TrackStats(
+ val distanceNm: Double,
+ val durationMs: Long,
+ val avgSogKnots: Double
+) {
+ val durationFormatted: String get() {
+ val totalSec = durationMs / 1000
+ val h = totalSec / 3600
+ val m = (totalSec % 3600) / 60
+ val s = totalSec % 60
+ return if (h > 0) "%d:%02d:%02d".format(h, m, s) else "%d:%02d".format(m, s)
+ }
+}