@@ -5,7 +5,6 @@ import android.annotation.SuppressLint
55import android.content.Intent
66import android.content.pm.PackageManager
77import android.content.res.Configuration
8- import android.graphics.Color
98import android.location.Location
109import android.os.Bundle
1110import android.util.Log
@@ -60,8 +59,6 @@ import org.maplibre.android.location.LocationComponentOptions
6059import org.maplibre.android.location.engine.LocationEngineRequest
6160import org.maplibre.android.maps.MapLibreMap
6261import org.maplibre.android.maps.Style
63- import org.maplibre.android.style.layers.CircleLayer
64- import org.maplibre.android.style.layers.PropertyFactory
6562import org.maplibre.android.style.sources.GeoJsonSource
6663import org.btcmap.area.AreaFragment
6764import org.btcmap.place.isMerchant
@@ -84,10 +81,9 @@ import org.btcmap.db
8481import org.btcmap.sync
8582import org.btcmap.db.table.place.Marker
8683import org.btcmap.db.table.event.Event
87- import org.maplibre.android.style.expressions.Expression
88- import org.maplibre.android.style.layers.Property.ICON_ANCHOR_CENTER
89- import org.maplibre.android.style.layers.SymbolLayer
90- import org.maplibre.android.style.sources.GeoJsonOptions
84+ import org.btcmap.map.layer.createEventLayers
85+ import org.btcmap.map.layer.createExchangeLayers
86+ import org.btcmap.map.layer.createMerchantLayers
9187import org.btcmap.search.SearchAdapterItem
9288import org.btcmap.settings.badgeBackgroundColor
9389import org.btcmap.settings.badgeTextColor
@@ -216,7 +212,7 @@ class MapFragment : Fragment() {
216212 }
217213
218214 override fun onViewCreated (view : View , savedInstanceState : Bundle ? ) {
219- val merchantLayers = createMerchantsSourceAndLayers (
215+ val merchantLayers = createMerchantLayers (
220216 markerBackgroundColor = prefs.markerBackgroundColor(requireContext()),
221217 markerBadgeBackgroundColor = prefs.badgeBackgroundColor(requireContext()),
222218 markerBadgeTextColor = prefs.badgeTextColor(requireContext()),
@@ -226,17 +222,25 @@ class MapFragment : Fragment() {
226222 markerBackgroundColor = prefs.markerBackgroundColor(requireContext()),
227223 usingOpenFreeMap = usingOpenFreeMap(),
228224 )
225+ val exchangeLayers = createExchangeLayers(
226+ markerBackgroundColor = prefs.markerBackgroundColor(requireContext()),
227+ markerBadgeBackgroundColor = prefs.badgeBackgroundColor(requireContext()),
228+ markerBadgeTextColor = prefs.badgeTextColor(requireContext()),
229+ usingOpenFreeMap = usingOpenFreeMap(),
230+ )
229231 merchantsSource = merchantLayers.first
230232 eventsSource = eventLayers.first
233+ exchangesSource = exchangeLayers.first
231234 binding.map.getMapAsync { map ->
232235 map.getStyle { style ->
233236 style.addSource(merchantLayers.first)
234237 merchantLayers.second.forEach { style.addLayer(it) }
235238 style.addSource(eventLayers.first)
236239 eventLayers.second.forEach { style.addLayer(it) }
240+ style.addSource(exchangeLayers.first)
241+ exchangeLayers.second.forEach { style.addLayer(it) }
237242 }
238243 }
239- initExchangesMap()
240244
241245 initSearchBar(binding)
242246
@@ -770,144 +774,6 @@ class MapFragment : Fragment() {
770774 private lateinit var eventsSource: GeoJsonSource
771775 private lateinit var exchangesSource: GeoJsonSource
772776
773- private fun initExchangesMap () {
774- val exchangesSource = GeoJsonSource (
775- " exchangesSource" ,
776- EMPTY_GEOJSON ,
777- GeoJsonOptions ()
778- .withCluster(true )
779- .withClusterMaxZoom(14 )
780- .withClusterRadius(50 )
781- )
782-
783- val exchangesClusterBackgroundLayer by lazy {
784- CircleLayer (" exchangesClusterBackground" , exchangesSource.id).apply {
785- setProperties(
786- PropertyFactory .circleColor(prefs.markerBackgroundColor(requireContext())),
787- PropertyFactory .circleRadius(23f ),
788- )
789- val pointCount = Expression .toNumber(Expression .get(" point_count" ))
790- setFilter(
791- Expression .all(
792- Expression .has(" point_count" ),
793- Expression .gte(
794- pointCount,
795- Expression .literal(1 )
796- )
797- )
798- )
799- }
800- }
801-
802- val exchangesClusterCountLayer =
803- SymbolLayer (" exchangesClusterCount" , exchangesSource.id).apply {
804- if (usingOpenFreeMap()) {
805- setProperties(PropertyFactory .textFont(arrayOf(" Noto Sans Regular" )))
806- }
807- setProperties(
808- PropertyFactory .textField(Expression .toString(Expression .get(" point_count" ))),
809- PropertyFactory .textSize(16f ),
810- PropertyFactory .textColor(Color .WHITE ),
811- )
812- }
813-
814- val exchangesLayer =
815- SymbolLayer (LAYER_EXCHANGES , exchangesSource.id).apply {
816- setProperties(
817- PropertyFactory .iconImage(" btcmap-marker" ),
818- PropertyFactory .iconAnchor(Expression .literal(" bottom" )),
819- PropertyFactory .iconAllowOverlap(true ),
820- PropertyFactory .iconIgnorePlacement(true )
821- )
822- setFilter(
823- Expression .neq(Expression .get(" cluster" ), true )
824- )
825- }
826-
827- val exchangesCategoryIconsLayer =
828- SymbolLayer (LAYER_EXCHANGES_CATEGORY_ICONS , exchangesSource.id).apply {
829- setProperties(
830- PropertyFactory .iconImage(
831- Expression .match(
832- Expression .get(" iconId" ),
833- * matcher().toTypedArray()
834- )
835- ),
836- PropertyFactory .iconAnchor(ICON_ANCHOR_CENTER ),
837- PropertyFactory .iconOffset(
838- arrayOf(
839- 0f ,
840- ICON_OFFSET_Y
841- )
842- ),
843- PropertyFactory .iconAllowOverlap(true ),
844- PropertyFactory .iconIgnorePlacement(true )
845- )
846- setFilter(
847- Expression .neq(Expression .get(" cluster" ), true )
848- )
849- }
850-
851- val exchangesCommentsLayer =
852- CircleLayer (" exchangesComments" , exchangesSource.id).apply {
853- setProperties(
854- PropertyFactory .circleColor(prefs.badgeBackgroundColor(requireContext())),
855- PropertyFactory .circleRadius(9f ),
856- PropertyFactory .circleOpacity(1f ),
857- PropertyFactory .circleTranslate(arrayOf(13f , - 43f )),
858- PropertyFactory .circleTranslateAnchor(" viewport" )
859- )
860- setFilter(
861- Expression .all(
862- Expression .neq(Expression .get(" cluster" ), true ),
863- Expression .gt(Expression .get(" comments" ), 0 )
864- )
865- )
866- }
867-
868- val exchangesCommentsCountLayer =
869- SymbolLayer (" exchangesCommentsCount" , exchangesSource.id).apply {
870- if (usingOpenFreeMap()) {
871- setProperties(PropertyFactory .textFont(arrayOf(" Noto Sans Bold" )))
872- }
873- setProperties(
874- PropertyFactory .textField(
875- Expression .switchCase(
876- Expression .gte(Expression .get(" comments" ), Expression .literal(10 )),
877- Expression .literal(" 9+" ),
878- Expression .toString(Expression .get(" comments" ))
879- )
880- ),
881- PropertyFactory .textSize(11f ),
882- PropertyFactory .textColor(prefs.badgeTextColor(requireContext())),
883- PropertyFactory .textTranslate(arrayOf(13f , - 43f )),
884- PropertyFactory .textTranslateAnchor(" viewport" ),
885- PropertyFactory .textAllowOverlap(true )
886- )
887- setFilter(
888- Expression .all(
889- Expression .neq(Expression .get(" cluster" ), true ),
890- Expression .gt(Expression .get(" comments" ), 0 )
891- )
892- )
893- }
894-
895- binding.map.getMapAsync { map ->
896- map.getStyle { style ->
897- style.addSource(exchangesSource)
898-
899- style.addLayer(exchangesClusterBackgroundLayer)
900- style.addLayer(exchangesClusterCountLayer)
901- style.addLayer(exchangesLayer)
902- style.addLayer(exchangesCategoryIconsLayer)
903- style.addLayer(exchangesCommentsLayer)
904- style.addLayer(exchangesCommentsCountLayer)
905- }
906- }
907-
908- this .exchangesSource = exchangesSource
909- }
910-
911777 private fun usingOpenFreeMap (): Boolean {
912778 val nightMode =
913779 requireContext().resources.configuration.uiMode and Configuration .UI_MODE_NIGHT_MASK == Configuration .UI_MODE_NIGHT_YES
@@ -924,7 +790,10 @@ class MapFragment : Fragment() {
924790 binding.map.getMapAsync { map ->
925791 val bounds = map.projection.visibleRegion.latLngBounds
926792 val expandedBounds = expandBounds(bounds, CLUSTERING_SCALE_FACTOR )
927- Log .d(" MapFragment" , " showMerchants: bounds=$bounds , expanded=$expandedBounds , cacheBounds=${merchantsCache.bounds} " )
793+ Log .d(
794+ " MapFragment" ,
795+ " showMerchants: bounds=$bounds , expanded=$expandedBounds , cacheBounds=${merchantsCache.bounds} "
796+ )
928797 viewLifecycleOwner.lifecycleScope.launch {
929798 if (! merchantsCache.contains(expandedBounds)) {
930799 Log .d(" MapFragment" , " showMerchants: cache miss, fetching" )
@@ -939,7 +808,10 @@ class MapFragment : Fragment() {
939808 )
940809 }
941810 lastDbCallTimeMs = System .currentTimeMillis() - startTime
942- Log .d(" MapFragment" , " showMerchants: fetched ${newMerchants.size} merchants in ${lastDbCallTimeMs} ms" )
811+ Log .d(
812+ " MapFragment" ,
813+ " showMerchants: fetched ${newMerchants.size} merchants in ${lastDbCallTimeMs} ms"
814+ )
943815 merchantsCache = merchantsCache.add(newMerchants, expandedBounds)
944816 } else {
945817 Log .d(" MapFragment" , " showMerchants: cache hit" )
@@ -1041,17 +913,19 @@ class MapFragment : Fragment() {
1041913 }
1042914 val viewportInfo = if (currentBounds != null ) {
1043915 val latSpanKm = currentBounds.latitudeSpan * 111
1044- val lonSpanKm = currentBounds.longitudeSpan * 111 * kotlin.math.cos(Math .toRadians(currentBounds.center.latitude))
916+ val lonSpanKm =
917+ currentBounds.longitudeSpan * 111 * kotlin.math.cos(Math .toRadians(currentBounds.center.latitude))
1045918 " %.1fkm x %.1fkm" .format(latSpanKm, lonSpanKm)
1046919 } else {
1047920 " no bounds"
1048921 }
1049922 Log .d(" MapFragment" , " updateDebugStats: filter=$filter , cacheSize=$cacheSize " )
1050923 if (prefs.showDebugInfo) {
1051924 binding.debugStats.apply {
1052- text = " memcache: %d items\n memcache bounds: %s\n db queries: %d\n last query: %dms" .format(
1053- cacheSize, viewportInfo, dbCallCount, lastDbCallTimeMs
1054- )
925+ text =
926+ " memcache: %d items\n memcache bounds: %s\n db queries: %d\n last query: %dms" .format(
927+ cacheSize, viewportInfo, dbCallCount, lastDbCallTimeMs
928+ )
1055929 isVisible = true
1056930 }
1057931 } else {
0 commit comments