Advertisement
Guest User

Kotlin Implementation

a guest
Oct 25th, 2024
518
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 19.44 KB | None | 0 0
  1. package com.pkg.membermap
  2. import android.app.Activity
  3. import android.content.Context
  4. import android.os.Build
  5. import android.os.Handler
  6. import android.os.Looper
  7. import android.view.LayoutInflater
  8. import androidx.appcompat.content.res.AppCompatResources
  9.  
  10. import com.google.android.gms.maps.MapsInitializer
  11. import com.google.maps.android.clustering.Cluster
  12. import com.google.maps.android.clustering.ClusterManager
  13. import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm
  14. import com.google.maps.android.clustering.view.DefaultClusterRenderer
  15.  
  16. import com.facebook.react.bridge.Arguments
  17. import com.facebook.react.bridge.ReactContext
  18. import com.facebook.react.bridge.ReactMethod
  19. import com.facebook.react.bridge.ReadableArray
  20. import com.facebook.react.bridge.ReadableMap
  21. import com.facebook.react.bridge.WritableMap
  22. import com.facebook.react.uimanager.annotations.ReactProp
  23. import com.facebook.react.uimanager.events.RCTEventEmitter
  24. import com.google.android.gms.maps.MapView
  25. import com.google.android.gms.maps.CameraUpdateFactory
  26. import com.google.android.gms.maps.GoogleMap
  27. import com.facebook.react.uimanager.SimpleViewManager
  28. import com.facebook.react.uimanager.ThemedReactContext
  29. import com.facebook.react.bridge.ReactApplicationContext
  30. import com.google.android.gms.maps.OnMapReadyCallback
  31. import com.google.android.gms.maps.model.BitmapDescriptorFactory
  32. import com.google.android.gms.maps.model.LatLng
  33. import com.google.android.gms.maps.model.LatLngBounds
  34. import com.google.android.gms.maps.model.Marker
  35. import com.google.android.gms.maps.model.MarkerOptions
  36. import com.google.maps.android.ui.IconGenerator
  37. import com.pkg.R
  38.  
  39. import java.util.ArrayList
  40. import java.util.Collections
  41. import java.util.HashSet
  42. import java.util.concurrent.ExecutorService
  43. import java.util.concurrent.Executors
  44. import java.util.concurrent.ThreadPoolExecutor
  45. import java.util.concurrent.TimeUnit
  46. import kotlin.math.ceil
  47. import kotlin.math.floor
  48.  
  49. internal class MemberMap(private val reactContext: ReactApplicationContext) :
  50.     SimpleViewManager<MapView>(),
  51.     OnMapReadyCallback,
  52.     GoogleMap.OnMapClickListener,
  53.     GoogleMap.OnCameraIdleListener,
  54.     GoogleMap.OnMapLoadedCallback,
  55.     ClusterManager.OnClusterItemClickListener<ProfileClusterItem> {
  56.  
  57.     companion object {
  58.         const val REACT_CLASS = "RCTMemberMap"
  59.         const val ANIMATE_MAP_TO_LOCATION = "ANIMATE_MAP_TO_LOCATION"
  60.     }
  61.  
  62.     private lateinit var context: ThemedReactContext
  63.     private var mapView: MapView? = null
  64.     private var boundsToMove: LatLngBounds? = null
  65.     private var map: GoogleMap? = null
  66.     private var initialRegion: ReadableMap? = null
  67.     private var pins: ReadableArray? = null
  68.     private var mapReady = false
  69.     private var renderPinsOnLoad = false
  70.     private var executor: ThreadPoolExecutor? = null
  71.  
  72.     private var renderExecutor: ExecutorService = Executors.newSingleThreadExecutor()
  73.     private val handler = Handler(Looper.getMainLooper())
  74.     private var renderCount = 0
  75.     private val coreCount = ParallelProcessing.getNumberOfCores()
  76.     private var activity: Activity? = null
  77.     private var clusterManager: ClusterManager<ProfileClusterItem>? = null
  78.     private var clusterRender: DefaultClusterRenderer<ProfileClusterItem>? = null
  79.  
  80.     private var selectedId: String? = null
  81.     private var selectedItem: ProfileClusterItem? = null
  82.     private var oldSelectedZIndex = Float.MIN_VALUE
  83.     private val visitedIds = HashSet<String>()
  84.  
  85.     override fun getName(): String = REACT_CLASS
  86.  
  87.     override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Map<String, Map<String, String>>> {
  88.         return mapOf(
  89.             "pinClick" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onPinClickEvent")),
  90.             "mapClick" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onMapClickEvent")),
  91.             "cameraIdle" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onCameraIdleEvent"))
  92.         )
  93.     }
  94.  
  95.     override fun createViewInstance(context: ThemedReactContext): MapView {
  96.         MapsInitializer.initialize(reactContext.applicationContext)
  97.         mapView = MapView(context)
  98.         this.context = context
  99.         mapView?.onCreate(null)
  100.         mapView?.getMapAsync(this)
  101.         mapView?.onResume()
  102.         return mapView!!
  103.     }
  104.  
  105.     override fun onMapReady(googleMap: GoogleMap) {
  106.         map = googleMap
  107.         googleMap.clear()
  108.  
  109.         initialRegion?.let { region ->
  110.             val lat = region.getDouble("latitude")
  111.             val lng = region.getDouble("longitude")
  112.             val latDelta = region.getDouble("latitudeDelta")
  113.             val lngDelta = region.getDouble("longitudeDelta")
  114.             val bounds = LatLngBounds(
  115.                 LatLng(lat - latDelta, lng - lngDelta),
  116.                 LatLng(lat + latDelta, lng + lngDelta)
  117.             )
  118.  
  119.             if ((mapView?.height ?: 0) <= 0 || (mapView?.width ?: 0) <= 0) {
  120.                 map?.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(lat, lng), 10f))
  121.                 boundsToMove = bounds
  122.             } else {
  123.                 map?.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0))
  124.                 boundsToMove = null
  125.             }
  126.         }
  127.  
  128.         googleMap.setOnMapLoadedCallback(this)
  129.         googleMap.uiSettings.isCompassEnabled = false
  130.         googleMap.setMinZoomPreference(5.5f)
  131.         googleMap.setOnMapClickListener(this)
  132.         googleMap.setOnCameraIdleListener(this)
  133.     }
  134.  
  135.     @ReactProp(name = "initialMapRegion")
  136.     fun setInitialMapRegion(view: MapView, geo: ReadableMap?) {
  137.         initialRegion = geo
  138.     }
  139.  
  140.     @ReactProp(name = "pins")
  141.     fun setPins(view: MapView, pins: ReadableArray?) {
  142.         this.pins = pins
  143.         if (mapReady) renderPins() else renderPinsOnLoad = true
  144.     }
  145.  
  146.     override fun receiveCommand(root: MapView, commandId: String, args: ReadableArray?) {
  147.         if (commandId == ANIMATE_MAP_TO_LOCATION) {
  148.             args?.getMap(0)?.let { animateMapToLocation(it) }
  149.         }
  150.     }
  151.  
  152.     @ReactMethod
  153.     fun animateMapToLocation(geo: ReadableMap) {
  154.         val loc = geo.getMap("viewport")
  155.         val southwest = loc?.getMap("southwest")
  156.         val northeast = loc?.getMap("northeast")
  157.         boundsToMove = null
  158.         val bounds = LatLngBounds(
  159.             LatLng(southwest?.getDouble("lat") ?: 0.0, southwest?.getDouble("lng") ?: 0.0),
  160.             LatLng(northeast?.getDouble("lat") ?: 0.0, northeast?.getDouble("lng") ?: 0.0)
  161.         )
  162.         map?.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0))
  163.     }
  164.  
  165.     override fun onMapClick(latLng: LatLng) {
  166.         val event = Arguments.createMap().apply {
  167.             putDouble("latitude", latLng.latitude)
  168.             putDouble("longitude", latLng.longitude)
  169.         }
  170.         val reactContext = mapView?.context as ReactContext
  171.         reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
  172.             mapView!!.id, "mapClick", event
  173.         )
  174.  
  175.         selectedId = null
  176.         selectedItem?.let { item ->
  177.             clusterRender?.getMarker(item)?.let { marker ->
  178.                 marker.setIcon(
  179.                     HaveIcon.getIcon(
  180.                         mapView!!, item.price, item.country!!, HaveIcon.HaveIconState.VISITED
  181.                     )
  182.                 )
  183.                 if (oldSelectedZIndex != Float.MIN_VALUE) marker.zIndex = oldSelectedZIndex
  184.             }
  185.             oldSelectedZIndex = Float.MIN_VALUE
  186.         }
  187.     }
  188.  
  189.     override fun onCameraIdle() {
  190.         val bounds = map?.projection?.visibleRegion?.latLngBounds
  191.         val event = Arguments.createMap().apply {
  192.             putMap("northeast", createLatLngMap(bounds?.northeast))
  193.             putMap("southwest", createLatLngMap(bounds?.southwest))
  194.         }
  195.         val reactContext = mapView?.context as ReactContext
  196.         reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
  197.             mapView!!.id, "cameraIdle", event
  198.         )
  199.  
  200.         clusterManager?.onCameraIdle()
  201.     }
  202.  
  203.     private fun createLatLngMap(latLng: LatLng?): WritableMap {
  204.         return Arguments.createMap().apply {
  205.             putDouble("latitude", latLng?.latitude ?: 0.0)
  206.             putDouble("longitude", latLng?.longitude ?: 0.0)
  207.         }
  208.     }
  209.  
  210.     override fun onMapLoaded() {
  211.         boundsToMove?.let { bounds ->
  212.             map?.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0))
  213.             boundsToMove = null
  214.         }
  215.  
  216.         clusterManager = ClusterManager(mapView?.context, map)
  217.  
  218.         val gen = IconGenerator(mapView?.context).apply {
  219.             setBackground(mapView?.context?.let {
  220.                 AppCompatResources.getDrawable(it, R.drawable.map_cluster)
  221.             })
  222.         }
  223.  
  224.         val inflater = mapView?.context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
  225.         val activityView = inflater.inflate(R.layout.cluster_view, mapView, false)
  226.         gen.setContentView(activityView)
  227.  
  228.         clusterRender = object : DefaultClusterRenderer<ProfileClusterItem>(
  229.             mapView?.context, map, clusterManager
  230.         ) {
  231.             override fun onBeforeClusterRendered(
  232.                 cluster: Cluster<ProfileClusterItem>,
  233.                 markerOptions: MarkerOptions
  234.             ) {
  235.                 val clusterSize = cluster.size
  236.                 markerOptions.icon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(clusterSize.toString())))
  237.             }
  238.  
  239.             override fun onClusterUpdated(
  240.                 cluster: Cluster<ProfileClusterItem>,
  241.                 marker: Marker
  242.             ) {
  243.                 val clusterSize = cluster.size
  244.                 marker.setIcon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(clusterSize.toString())))
  245.             }
  246.  
  247.             override fun onBeforeClusterItemRendered(
  248.                 item: ProfileClusterItem,
  249.                 markerOptions: MarkerOptions
  250.             ) {
  251.                 markerOptions.apply {
  252.                     icon(item.getIcon())
  253.                     snippet(item.snippet)
  254.                     title(item.title)
  255.                 }
  256.             }
  257.         }
  258.  
  259.         clusterRender?.minClusterSize = 5
  260.  
  261.         val displayMetrics = mapView?.context?.resources?.displayMetrics
  262.  
  263.         clusterManager?.apply {
  264.             setAlgorithm(
  265.                 NonHierarchicalViewBasedAlgorithm(
  266.                     displayMetrics?.widthPixels ?: 0,
  267.                     displayMetrics?.heightPixels ?: 0
  268.                 )
  269.             )
  270.             renderer = clusterRender
  271.             setOnClusterItemClickListener(this@MemberMap)
  272.         }
  273.  
  274.         mapReady = true
  275.  
  276.         if (renderPinsOnLoad) {
  277.             renderPins()
  278.         }
  279.     }
  280.  
  281.  
  282.     override fun onClusterItemClick(item: ProfileClusterItem?): Boolean {
  283.         if (item == null) return true
  284.  
  285.         map?.animateCamera(CameraUpdateFactory.newLatLng(item.position), 300, null)
  286.  
  287.         val profileId = item.profileId
  288.         val event = Arguments.createMap().apply {
  289.             putString("profileId", profileId)
  290.         }
  291.  
  292.         selectedItem?.takeIf { it != item }?.let { previousItem ->
  293.             clusterRender?.getMarker(previousItem)?.let { marker ->
  294.                 marker.setIcon(
  295.                     HaveIcon.getIcon(
  296.                         mapView!!, previousItem.price, previousItem.country!!, HaveIcon.HaveIconState.VISITED
  297.                     )
  298.                 )
  299.                 if (oldSelectedZIndex != -1f) {
  300.                     marker.zIndex = oldSelectedZIndex
  301.                 }
  302.             }
  303.         }
  304.  
  305.         if (item.haveOrNeed == "h") {
  306.             clusterRender?.getMarker(item)?.let { marker ->
  307.                 marker.setIcon(
  308.                     HaveIcon.getIcon(
  309.                         mapView!!, item.price, item.country!!, HaveIcon.HaveIconState.ACTIVE
  310.                     )
  311.                 )
  312.                 oldSelectedZIndex = marker.zIndex
  313.                 selectedItem = item
  314.                 marker.zIndex = Float.MAX_VALUE
  315.             }
  316.  
  317.             selectedId = profileId
  318.             visitedIds.add(selectedId!!)
  319.         } else {
  320.             selectedItem = null
  321.             selectedId = null
  322.         }
  323.  
  324.         val reactContext = mapView?.context as ReactContext
  325.         reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
  326.             mapView!!.id, "pinClick", event
  327.         )
  328.  
  329.         return true
  330.     }
  331.  
  332.  
  333.     private fun renderPins() {
  334.         try {
  335.             renderExecutor.shutdownNow()
  336.             renderExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
  337.         } catch (e: Exception) {
  338.             e.printStackTrace()
  339.         }
  340.  
  341.         renderExecutor = Executors.newSingleThreadExecutor()
  342.  
  343.         renderExecutor.execute {
  344.             executor?.shutdownNow()
  345.             try {
  346.                 executor?.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
  347.             } catch (e: Exception) {
  348.                 e.printStackTrace()
  349.             }
  350.  
  351.             renderCount++
  352.             val thisRenderCount = renderCount
  353.             val thisPins = pins
  354.             val numCores = coreCount
  355.             activity = reactContext.currentActivity
  356.             executor = Executors.newFixedThreadPool(numCores) as ThreadPoolExecutor
  357.  
  358.             val numPins = thisPins?.size()
  359.             if (numPins != null) {
  360.                 if (numPins < 1) return@execute
  361.             }
  362.  
  363.             val pinsHashSet = Collections.synchronizedSet(HashSet<String>())
  364.             val items = clusterManager?.algorithm?.items
  365.             val displayedPinsHashSet = items?.let { HashSet<String>(it.size) }
  366.  
  367.             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  368.                 items?.parallelStream()?.forEach { item ->
  369.                     displayedPinsHashSet?.add(item.profileId)
  370.                 }
  371.             } else {
  372.                 val legacyExecutor = Executors.newFixedThreadPool(numCores)
  373.                 val itemList = ArrayList(items!!)
  374.                 val segmentSize = ceil(itemList.size / numCores.toDouble()).toInt()
  375.  
  376.                 for (i in 0 until numCores) {
  377.                     val start = i * segmentSize
  378.                     val end = minOf((i + 1) * segmentSize, itemList.size)
  379.                     val segment = itemList.subList(start, end)
  380.  
  381.                     legacyExecutor.submit {
  382.                         segment.forEach { item ->
  383.                             displayedPinsHashSet?.add(item.profileId)
  384.                         }
  385.                     }
  386.                 }
  387.  
  388.                 legacyExecutor.shutdown()
  389.                 try {
  390.                     legacyExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
  391.                 } catch (e: InterruptedException) {
  392.                     e.printStackTrace()
  393.                 }
  394.             }
  395.  
  396.             val clusterItems = Collections.synchronizedList(ArrayList<ProfileClusterItem>(numPins!!))
  397.  
  398.             for (t in 0 until numCores) {
  399.                 executor!!.execute execute2@{
  400.                     val minPin = (floor(numPins / numCores.toDouble()) * t).toInt()
  401.                     val maxPin = if (t == numCores - 1) {
  402.                         numPins
  403.                     } else {
  404.                         (floor(numPins / numCores.toDouble()) * (t + 1)).toInt() + 1
  405.                     }
  406.  
  407.                     for (i in minPin until maxPin) {
  408.                         if (renderCount != thisRenderCount) return@execute2
  409.  
  410.                         try {
  411.                             val pinArr = thisPins.getArray(i)
  412.                             val type = pinArr.getString(0)
  413.                             val profileId = pinArr.getString(1)
  414.  
  415.                             if (!displayedPinsHashSet!!.contains(profileId)) {
  416.                                 when (type) {
  417.                                     "h" -> {
  418.                                         val pinLoc = pinArr.getArray(2)
  419.                                         val point = LatLng(pinLoc.getDouble(0), pinLoc.getDouble(1))
  420.  
  421.                                         if (renderCount == thisRenderCount && activity != null) {
  422.                                             val country = pinArr.getString(4)
  423.                                             val price = pinArr.getInt(3)
  424.                                             val selectedPin = selectedId == profileId
  425.                                             val wasVisited = visitedIds.contains(profileId)
  426.  
  427.                                             val item = ProfileClusterItem(
  428.                                                 point, profileId, "h", price, country,
  429.                                                 HaveIcon.getIcon(
  430.                                                     mapView!!, price, country,
  431.                                                     if (selectedPin) HaveIcon.HaveIconState.ACTIVE
  432.                                                     else if (wasVisited) HaveIcon.HaveIconState.VISITED
  433.                                                     else HaveIcon.HaveIconState.UNVISITED
  434.                                                 )
  435.                                             )
  436.                                             clusterItems.add(item)
  437.                                         }
  438.                                     }
  439.  
  440.                                     "n" -> {
  441.                                         val pinLocs = pinArr.getArray(2)
  442.                                         for (j in 0 until pinLocs.size()) {
  443.                                             val pinLoc = pinLocs.getArray(j)
  444.                                             val point = LatLng(pinLoc.getDouble(0), pinLoc.getDouble(1))
  445.  
  446.                                             if (renderCount == thisRenderCount && activity != null) {
  447.                                                 val item = ProfileClusterItem(point, profileId, "n", NeedIcon.getIcon(
  448.                                                     mapView!!
  449.                                                 ))
  450.                                                 clusterItems.add(item)
  451.                                             }
  452.                                         }
  453.                                     }
  454.                                 }
  455.                             }
  456.  
  457.                             synchronized(pinsHashSet) {
  458.                                 pinsHashSet.add(profileId)
  459.                             }
  460.  
  461.                         } catch (e: Exception) {
  462.                             e.printStackTrace()
  463.                         }
  464.                     }
  465.                 }
  466.             }
  467.  
  468.             try {
  469.                 executor!!.shutdown()
  470.                 executor!!.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
  471.             } catch (e: InterruptedException) {
  472.                 e.printStackTrace()
  473.             }
  474.  
  475.             if (thisRenderCount != renderCount) return@execute
  476.  
  477.             synchronized(clusterItems) {
  478.                 clusterManager!!.addItems(clusterItems)
  479.                 val itemsList = clusterManager!!.algorithm.items
  480.                 val toRemove = ArrayList<ProfileClusterItem>(numPins)
  481.  
  482.                 for (item in itemsList) {
  483.                     if (!pinsHashSet.contains(item.profileId)) {
  484.                         toRemove.add(item)
  485.                     }
  486.                 }
  487.  
  488.                 clusterManager!!.removeItems(toRemove)
  489.             }
  490.  
  491.             handler.post {
  492.                 if (thisRenderCount == renderCount) {
  493.                     clusterManager?.cluster()
  494.                 }
  495.             }
  496.         }
  497.     }
  498. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement