From ca4f8875370e7d76c770830138c0673325992f86 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 5 May 2026 12:13:19 -1000 Subject: fix: throttle wind/weather fetches to reduce 429s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Camera idle: debounce 1.5s so rapid panning/zooming collapses into one fetch loadWindGrid: skip if center moved < 10 nm AND last fetch < 15 min ago (was unbounded — fired on every camera-idle with no gate at all) loadConditions already had a 5 min + 2 nm gate; unchanged Co-Authored-By: Claude Sonnet 4.6 --- .../src/main/kotlin/org/terst/nav/MainActivity.kt | 37 ++++++++++++---------- .../main/kotlin/org/terst/nav/ui/MainViewModel.kt | 15 +++++++++ 2 files changed, 36 insertions(+), 16 deletions(-) (limited to 'android-app/app/src') diff --git a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt index 5e7ce82..1752fe8 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt @@ -60,6 +60,7 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener { private lateinit var layerManager: MapLayerManager private var particleWindView: ParticleWindView? = null private var weatherLoaded = false + private var cameraIdleJob: kotlinx.coroutines.Job? = null private lateinit var bottomSheetBehavior: BottomSheetBehavior private lateinit var fragmentContainer: FrameLayout @@ -492,23 +493,27 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener { } maplibreMap.addOnCameraIdleListener { - val corners = listOfNotNull( - maplibreMap.projection.visibleRegion.nearLeft, - maplibreMap.projection.visibleRegion.nearRight, - maplibreMap.projection.visibleRegion.farLeft, - maplibreMap.projection.visibleRegion.farRight - ) - if (corners.size == 4) { - viewModel.loadWindGrid( - corners.minOf { it.latitude }, corners.maxOf { it.latitude }, - corners.minOf { it.longitude }, corners.maxOf { it.longitude } + cameraIdleJob?.cancel() + cameraIdleJob = lifecycleScope.launch { + kotlinx.coroutines.delay(1_500) + val corners = listOfNotNull( + maplibreMap.projection.visibleRegion.nearLeft, + maplibreMap.projection.visibleRegion.nearRight, + maplibreMap.projection.visibleRegion.farLeft, + maplibreMap.projection.visibleRegion.farRight ) - } - maplibreMap.cameraPosition.target?.let { center -> - // Only refresh conditions when the user has manually panned — - // in following mode the GPS handler already triggers the load. - if (mapHandler?.isFollowing?.value == false) { - viewModel.loadConditions(center.latitude, center.longitude) + if (corners.size == 4) { + viewModel.loadWindGrid( + corners.minOf { it.latitude }, corners.maxOf { it.latitude }, + corners.minOf { it.longitude }, corners.maxOf { it.longitude } + ) + } + maplibreMap.cameraPosition.target?.let { center -> + // Only refresh conditions when the user has manually panned — + // in following mode the GPS handler already triggers the load. + if (mapHandler?.isFollowing?.value == false) { + viewModel.loadConditions(center.latitude, center.longitude) + } } } } diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt index 88689e0..530af39 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt @@ -236,6 +236,10 @@ class MainViewModel( private var lastConditionsLon = Double.NaN private var lastConditionsMs = 0L + private var lastWindGridLat = Double.NaN + private var lastWindGridLon = Double.NaN + private var lastWindGridMs = 0L + /** * Fetches current conditions snapshot for [lat]/[lon] and exposes it via [marineConditions]. * Throttled: skips if conditions loaded < 5 min ago and position < 2 nm from last load. @@ -312,9 +316,20 @@ class MainViewModel( /** * Fetch wind arrows for a 4×5 grid covering the visible map bounds. + * Throttled: skips if grid center < 10 nm from last fetch and < 15 min old. * Fires-and-forgets; silently ignores network failures (grid is decorative). */ fun loadWindGrid(latMin: Double, latMax: Double, lonMin: Double, lonMax: Double) { + val centerLat = (latMin + latMax) / 2.0 + val centerLon = (lonMin + lonMax) / 2.0 + val nowMs = System.currentTimeMillis() + val ageMs = nowMs - lastWindGridMs + val distNm = if (lastWindGridLat.isNaN()) Double.MAX_VALUE + else distanceNm(lastWindGridLat, lastWindGridLon, centerLat, centerLon) + if (ageMs < 15 * 60_000L && distNm < 10.0) return + lastWindGridLat = centerLat + lastWindGridLon = centerLon + lastWindGridMs = nowMs viewModelScope.launch { val points = buildGrid(latMin, latMax, lonMin, lonMax, cols = 4, rows = 5) repository.fetchWindGrid(points).getOrNull()?.let { _windGrid.value = it } -- cgit v1.2.3