summaryrefslogtreecommitdiff
path: root/android-app/app/src/main/kotlin
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-01 06:37:01 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-01 06:37:01 +0000
commit9ec663b4aa79c48829cc3796be90dc130fa5f097 (patch)
tree1f858244fbd87f3885691912e55d6f93d7dcfe0e /android-app/app/src/main/kotlin
parentbee75a1083455726c5fc2d179d564571da5096cb (diff)
feat(layers): ATON CircleLayer + TSS FillLayer in MapHandler
Diffstat (limited to 'android-app/app/src/main/kotlin')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt103
1 files changed, 103 insertions, 0 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt
index 6d408ed..02c9fca 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt
@@ -1,5 +1,6 @@
package org.terst.nav.ui
+import android.content.Context
import android.graphics.Bitmap
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
@@ -9,6 +10,7 @@ import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.Style
import org.maplibre.android.style.expressions.Expression
import org.maplibre.android.style.layers.CircleLayer
+import org.maplibre.android.style.layers.FillLayer
import org.maplibre.android.style.layers.LineLayer
import org.maplibre.android.style.layers.PropertyFactory
import org.maplibre.android.style.layers.RasterLayer
@@ -21,6 +23,7 @@ import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.LineString
import org.maplibre.geojson.Point
import org.maplibre.geojson.Polygon
+import org.terst.nav.R
import org.terst.nav.safety.AnchorWatchState
import org.terst.nav.TidalCurrentState
import org.terst.nav.data.model.WindArrow
@@ -96,6 +99,11 @@ class MapHandler(private val maplibreMap: MapLibreMap) {
private var fadSource: GeoJsonSource? = null
+ private val ATON_SOURCE_ID = "aton-source"
+ private val ATON_LAYER_ID = "aton-layer"
+ private val ATON_LABELS_ID = "aton-labels"
+ private var atonSource: GeoJsonSource? = null
+
private val FISHING_LINE_SOURCE_ID = "fishing-line-source"
private val FISHING_LINE_LAYER_ID = "fishing-line-layer"
private var fishingLineSource: GeoJsonSource? = null
@@ -489,6 +497,101 @@ class MapHandler(private val maplibreMap: MapLibreMap) {
fadSource?.setGeoJson(FeatureCollection.fromFeatures(features))
}
+ /**
+ * Initialises the ATON (Aid to Navigation) layer.
+ * Renders ATONs as colored circles with name labels at zoom 12+.
+ * Call once after style loads, then call [updateAtonLayer] to populate.
+ */
+ fun setupAtonLayer(style: Style, visible: Boolean) {
+ val src = GeoJsonSource(ATON_SOURCE_ID)
+ atonSource = src
+ style.addSource(src)
+ val v = if (visible) "visible" else "none"
+ style.addLayer(CircleLayer(ATON_LAYER_ID, ATON_SOURCE_ID).apply {
+ setProperties(
+ PropertyFactory.circleColor(Expression.get("color")),
+ PropertyFactory.circleRadius(4f),
+ PropertyFactory.circleStrokeColor("rgba(0,0,0,0.6)"),
+ PropertyFactory.circleStrokeWidth(1f),
+ PropertyFactory.visibility(v)
+ )
+ })
+ style.addLayer(SymbolLayer(ATON_LABELS_ID, ATON_SOURCE_ID).apply {
+ setProperties(
+ PropertyFactory.textField(Expression.get("name")),
+ PropertyFactory.textSize(9f),
+ PropertyFactory.textOffset(arrayOf(0f, -1.2f)),
+ PropertyFactory.textColor("rgba(255,255,255,1)"),
+ PropertyFactory.textHaloColor("rgba(0,0,0,0.7)"),
+ PropertyFactory.textHaloWidth(1f),
+ PropertyFactory.textAllowOverlap(false),
+ PropertyFactory.visibility(v),
+ PropertyFactory.textOpacity(
+ Expression.step(Expression.zoom(), Expression.literal(0f),
+ Expression.stop(12.0, 1f))
+ )
+ )
+ })
+ }
+
+ /** Replaces ATON markers from a list of [Feature] points. */
+ fun updateAtonLayer(features: List<Feature>) {
+ atonSource?.setGeoJson(FeatureCollection.fromFeatures(features))
+ }
+
+ /** Shows or hides the ATON layer (and its label layer) on a live style. */
+ fun setAtonLayerVisible(style: Style, visible: Boolean) {
+ val v = if (visible) "visible" else "none"
+ style.getLayer(ATON_LAYER_ID)?.setProperties(PropertyFactory.visibility(v))
+ style.getLayer(ATON_LABELS_ID)?.setProperties(PropertyFactory.visibility(v))
+ }
+
+ /**
+ * Initialises the TSS (Traffic Separation Scheme) layer from the bundled GeoJSON asset.
+ * Renders traffic lanes as green (inbound) / red (outbound) fills and the separation
+ * zone as a yellow fill. Call once after style loads.
+ */
+ fun setupTssLayer(style: Style, context: Context, visible: Boolean) {
+ val json = context.resources.openRawResource(R.raw.pacific_tss)
+ .bufferedReader().readText()
+ val src = GeoJsonSource("tss-source", FeatureCollection.fromJson(json))
+ style.addSource(src)
+ val v = if (visible) "visible" else "none"
+ style.addLayer(FillLayer("tss-lanes-layer", "tss-source").apply {
+ setFilter(Expression.neq(
+ Expression.get("type"), Expression.literal("separation_zone")))
+ setProperties(
+ PropertyFactory.fillColor(Expression.switchCase(
+ Expression.eq(Expression.get("type"), Expression.literal("lane_inbound")),
+ Expression.literal("rgba(0,180,0,0.12)"),
+ Expression.literal("rgba(200,0,0,0.12)")
+ )),
+ PropertyFactory.fillOutlineColor(Expression.switchCase(
+ Expression.eq(Expression.get("type"), Expression.literal("lane_inbound")),
+ Expression.literal("rgba(0,180,0,0.5)"),
+ Expression.literal("rgba(200,0,0,0.5)")
+ )),
+ PropertyFactory.visibility(v)
+ )
+ })
+ style.addLayer(FillLayer("tss-sep-layer", "tss-source").apply {
+ setFilter(Expression.eq(
+ Expression.get("type"), Expression.literal("separation_zone")))
+ setProperties(
+ PropertyFactory.fillColor("rgba(255,200,0,0.25)"),
+ PropertyFactory.fillOutlineColor("rgba(255,200,0,0.7)"),
+ PropertyFactory.visibility(v)
+ )
+ })
+ }
+
+ /** Shows or hides the TSS lanes and separation zone layers on a live style. */
+ fun setTssLayerVisible(style: Style, visible: Boolean) {
+ val v = if (visible) "visible" else "none"
+ style.getLayer("tss-lanes-layer")?.setProperties(PropertyFactory.visibility(v))
+ style.getLayer("tss-sep-layer")?.setProperties(PropertyFactory.visibility(v))
+ }
+
/** Clears all tack markers. */
fun clearTackLayer() {
tackSource?.setGeoJson(FeatureCollection.fromFeatures(emptyList()))