summaryrefslogtreecommitdiff
path: root/android-app
diff options
context:
space:
mode:
Diffstat (limited to 'android-app')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt37
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt35
-rw-r--r--android-app/app/src/test/kotlin/org/terst/nav/ui/aton/AtonDataTest.kt37
3 files changed, 109 insertions, 0 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt
new file mode 100644
index 0000000..2ff59cd
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt
@@ -0,0 +1,37 @@
+package org.terst.nav.ui.aton
+
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import org.maplibre.geojson.Feature
+import org.maplibre.geojson.Point
+
+object AtonData {
+ fun colorFor(type: String): String = when (type) {
+ "Light" -> "#FF0000"
+ "Float", "Buoy" -> "#00AA00"
+ "Daybeacon", "Range" -> "#FFDD00"
+ else -> "#FFFFFF"
+ }
+
+ fun parseStation(json: String): Feature? = try {
+ val obj = JsonParser.parseString(json).asJsonObject
+ val lat = obj.get("lat").asDouble
+ val lng = obj.get("lng").asDouble
+ val type = obj.get("type")?.asString ?: ""
+ val id = obj.get("id")?.asString ?: ""
+ val name = obj.get("name")?.asString ?: ""
+ val props = JsonObject().apply {
+ addProperty("id", id)
+ addProperty("name", name)
+ addProperty("type", type)
+ addProperty("color", colorFor(type))
+ }
+ Feature.fromGeometry(Point.fromLngLat(lng, lat), props)
+ } catch (_: Exception) { null }
+
+ fun parseResponse(json: String): List<Feature> = try {
+ val arr = JsonParser.parseString(json).asJsonObject
+ .getAsJsonArray("stations")
+ arr.mapNotNull { parseStation(it.toString()) }
+ } catch (_: Exception) { emptyList() }
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt
new file mode 100644
index 0000000..b5472b3
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt
@@ -0,0 +1,35 @@
+package org.terst.nav.ui.aton
+
+import android.content.Context
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.maplibre.geojson.Feature
+import java.io.File
+import java.net.URL
+
+class AtonRepository(private val context: Context) {
+
+ private val cacheFile = File(context.filesDir, "aton_cache.json")
+ private val maxAgeMs = 24 * 60 * 60 * 1000L
+
+ private val apiUrl =
+ "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations.json?type=aton&units=english"
+
+ suspend fun getFeatures(): List<Feature> = withContext(Dispatchers.IO) {
+ val cached = readCache()
+ if (cached != null) return@withContext AtonData.parseResponse(cached)
+ val fresh = fetchFromApi() ?: return@withContext emptyList()
+ cacheFile.writeText(fresh)
+ AtonData.parseResponse(fresh)
+ }
+
+ private fun readCache(): String? {
+ if (!cacheFile.exists()) return null
+ if (System.currentTimeMillis() - cacheFile.lastModified() > maxAgeMs) return null
+ return cacheFile.readText()
+ }
+
+ private fun fetchFromApi(): String? = try {
+ URL(apiUrl).openStream().bufferedReader().readText()
+ } catch (_: Exception) { null }
+}
diff --git a/android-app/app/src/test/kotlin/org/terst/nav/ui/aton/AtonDataTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/ui/aton/AtonDataTest.kt
new file mode 100644
index 0000000..dc0cb4f
--- /dev/null
+++ b/android-app/app/src/test/kotlin/org/terst/nav/ui/aton/AtonDataTest.kt
@@ -0,0 +1,37 @@
+package org.terst.nav.ui.aton
+
+import org.junit.Assert.*
+import org.junit.Test
+
+class AtonDataTest {
+ @Test fun `light type maps to red`() {
+ assertEquals("#FF0000", AtonData.colorFor("Light"))
+ }
+ @Test fun `float type maps to green`() {
+ assertEquals("#00AA00", AtonData.colorFor("Float"))
+ }
+ @Test fun `buoy type maps to green`() {
+ assertEquals("#00AA00", AtonData.colorFor("Buoy"))
+ }
+ @Test fun `daybeacon type maps to yellow`() {
+ assertEquals("#FFDD00", AtonData.colorFor("Daybeacon"))
+ }
+ @Test fun `unknown type maps to white`() {
+ assertEquals("#FFFFFF", AtonData.colorFor("WeirdThing"))
+ }
+ @Test fun `parse station json to feature`() {
+ val json = """{"id":"8443970","name":"Boston Light","lat":42.3298,"lng":-70.8930,"type":"Light"}"""
+ val f = AtonData.parseStation(json)
+ assertNotNull(f)
+ assertEquals("Boston Light", f!!.getStringProperty("name"))
+ assertEquals("#FF0000", f.getStringProperty("color"))
+ }
+ @Test fun `parse response returns list of features`() {
+ val json = """{"stations":[
+ {"id":"1","name":"A","lat":21.0,"lng":-158.0,"type":"Buoy"},
+ {"id":"2","name":"B","lat":21.1,"lng":-157.9,"type":"Light"}
+ ]}"""
+ val features = AtonData.parseResponse(json)
+ assertEquals(2, features.size)
+ }
+}