11package org.btcmap.map
22
3- import android.util.Log
3+ import kotlinx.coroutines.CoroutineScope
4+ import kotlinx.coroutines.Dispatchers
5+ import kotlinx.coroutines.Job
6+ import kotlinx.coroutines.SupervisorJob
7+ import kotlinx.coroutines.cancel
48import kotlinx.coroutines.flow.MutableStateFlow
5- import kotlinx.coroutines.flow.update
9+ import kotlinx.coroutines.launch
10+ import kotlinx.coroutines.withContext
611import org.btcmap.db.Database
712import org.btcmap.db.table.place.Marker
8- import org.btcmap.db.table.place.MarkerProjection
913import org.maplibre.android.geometry.LatLngBounds
1014import org.maplibre.android.maps.MapLibreMap
15+ import java.util.concurrent.atomic.AtomicReference
1116
1217class MerchantsCache (
1318 private val map : MapLibreMap ,
1419 private val db : Database ,
1520) : MapLibreMap.OnCameraIdleListener {
16- private val merchants: MutableSet <Marker > =
17- mutableSetOf<MarkerProjection >().toHashSet()
21+ private val scope = CoroutineScope (SupervisorJob () + Dispatchers .Main .immediate)
22+ private val pendingQuery = AtomicReference <Job ?>(null )
23+ private val seenIds: MutableSet <Long > = mutableSetOf ()
24+ private val merchants: MutableSet <Marker > = mutableSetOf ()
1825 val geoJson = MutableStateFlow (merchants.toGeoJson())
1926
2027 init {
21- Log .d(" merchants_cache" , " init" )
2228 map.addOnCameraIdleListener(this )
2329 }
2430
2531 override fun onCameraIdle () {
26- Log .d(" merchants_cache" , " camera idle" )
2732 val bounds = map.projection.visibleRegion.latLngBounds
28- Log .d(" merchants_cache" , " real map bounds: $bounds " )
2933 val expandedBounds = expandBounds(bounds)
30- val merchantsInBounds = db.place.selectMerchantsByBounds(
31- expandedBounds.latitudeSouth,
32- expandedBounds.latitudeNorth,
33- expandedBounds.longitudeWest,
34- expandedBounds.longitudeEast,
35- minVerifiedAt = null ,
36- ).toHashSet()
37- Log .d(" merchants_cache" , " merchants in bounds: ${merchantsInBounds.size} " )
38- merchants.addAll(merchantsInBounds)
39- geoJson.update { merchants.toGeoJson() }
34+
35+ pendingQuery.getAndSet(
36+ scope.launch {
37+ val (lonRange1, lonRange2) = expandedBounds.toLonRanges()
38+ val merchantsInBounds = withContext(Dispatchers .IO ) {
39+ if (lonRange2 == null ) {
40+ db.place.selectMerchantsByBounds(
41+ expandedBounds.latitudeSouth,
42+ expandedBounds.latitudeNorth,
43+ lonRange1.first,
44+ lonRange1.second,
45+ minVerifiedAt = null ,
46+ ).toHashSet()
47+ } else {
48+ val first = db.place.selectMerchantsByBounds(
49+ expandedBounds.latitudeSouth,
50+ expandedBounds.latitudeNorth,
51+ lonRange1.first,
52+ lonRange1.second,
53+ minVerifiedAt = null ,
54+ )
55+ val second = db.place.selectMerchantsByBounds(
56+ expandedBounds.latitudeSouth,
57+ expandedBounds.latitudeNorth,
58+ lonRange2.first,
59+ lonRange2.second,
60+ minVerifiedAt = null ,
61+ )
62+ (first + second).toHashSet()
63+ }
64+ }
65+
66+ val newOnes = merchantsInBounds.filter { it.id !in seenIds }
67+ if (newOnes.isEmpty()) return @launch
68+
69+ seenIds.addAll(newOnes.map { it.id })
70+ merchants.addAll(newOnes)
71+
72+ val next = withContext(Dispatchers .Default ) { merchants.toGeoJson() }
73+ geoJson.value = next
74+ }
75+ )?.cancel()
4076 }
4177
4278 fun destroy () {
43- Log .d(" merchants_cache" , " destroy" )
4479 map.removeOnCameraIdleListener(this )
80+ pendingQuery.getAndSet(null )?.cancel()
81+ scope.cancel()
4582 }
4683
4784 private fun expandBounds (bounds : LatLngBounds , scaleFactor : Double = 2.0): LatLngBounds {
@@ -58,6 +95,22 @@ class MerchantsCache(
5895 )
5996 }
6097
98+ private fun LatLngBounds.toLonRanges (): Pair <Pair <Double , Double >, Pair<Double, Double>?> {
99+ var west = longitudeWest
100+ var east = longitudeEast
101+ if (west > 180.0 ) {
102+ west - = 360.0
103+ }
104+ if (east > 180.0 ) {
105+ east - = 360.0
106+ }
107+ return if (west <= east) {
108+ Pair (west to east, null )
109+ } else {
110+ Pair (- 180.0 to east, west to 180.0 )
111+ }
112+ }
113+
61114 private fun Set<Marker>.toGeoJson (): String {
62115 val sb = StringBuilder ()
63116 sb.append(
0 commit comments