Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.pkg.membermap
- import android.app.Activity
- import android.content.Context
- import android.os.Build
- import android.os.Handler
- import android.os.Looper
- import android.view.LayoutInflater
- import androidx.appcompat.content.res.AppCompatResources
- import com.google.android.gms.maps.MapsInitializer
- import com.google.maps.android.clustering.Cluster
- import com.google.maps.android.clustering.ClusterManager
- import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm
- import com.google.maps.android.clustering.view.DefaultClusterRenderer
- import com.facebook.react.bridge.Arguments
- import com.facebook.react.bridge.ReactContext
- import com.facebook.react.bridge.ReactMethod
- import com.facebook.react.bridge.ReadableArray
- import com.facebook.react.bridge.ReadableMap
- import com.facebook.react.bridge.WritableMap
- import com.facebook.react.uimanager.annotations.ReactProp
- import com.facebook.react.uimanager.events.RCTEventEmitter
- import com.google.android.gms.maps.MapView
- import com.google.android.gms.maps.CameraUpdateFactory
- import com.google.android.gms.maps.GoogleMap
- import com.facebook.react.uimanager.SimpleViewManager
- import com.facebook.react.uimanager.ThemedReactContext
- import com.facebook.react.bridge.ReactApplicationContext
- import com.google.android.gms.maps.OnMapReadyCallback
- import com.google.android.gms.maps.model.BitmapDescriptorFactory
- import com.google.android.gms.maps.model.LatLng
- import com.google.android.gms.maps.model.LatLngBounds
- import com.google.android.gms.maps.model.Marker
- import com.google.android.gms.maps.model.MarkerOptions
- import com.google.maps.android.ui.IconGenerator
- import com.pkg.R
- import java.util.ArrayList
- import java.util.Collections
- import java.util.HashSet
- import java.util.concurrent.ExecutorService
- import java.util.concurrent.Executors
- import java.util.concurrent.ThreadPoolExecutor
- import java.util.concurrent.TimeUnit
- import kotlin.math.ceil
- import kotlin.math.floor
- internal class MemberMap(private val reactContext: ReactApplicationContext) :
- SimpleViewManager<MapView>(),
- OnMapReadyCallback,
- GoogleMap.OnMapClickListener,
- GoogleMap.OnCameraIdleListener,
- GoogleMap.OnMapLoadedCallback,
- ClusterManager.OnClusterItemClickListener<ProfileClusterItem> {
- companion object {
- const val REACT_CLASS = "RCTMemberMap"
- const val ANIMATE_MAP_TO_LOCATION = "ANIMATE_MAP_TO_LOCATION"
- }
- private lateinit var context: ThemedReactContext
- private var mapView: MapView? = null
- private var boundsToMove: LatLngBounds? = null
- private var map: GoogleMap? = null
- private var initialRegion: ReadableMap? = null
- private var pins: ReadableArray? = null
- private var mapReady = false
- private var renderPinsOnLoad = false
- private var executor: ThreadPoolExecutor? = null
- private var renderExecutor: ExecutorService = Executors.newSingleThreadExecutor()
- private val handler = Handler(Looper.getMainLooper())
- private var renderCount = 0
- private val coreCount = ParallelProcessing.getNumberOfCores()
- private var activity: Activity? = null
- private var clusterManager: ClusterManager<ProfileClusterItem>? = null
- private var clusterRender: DefaultClusterRenderer<ProfileClusterItem>? = null
- private var selectedId: String? = null
- private var selectedItem: ProfileClusterItem? = null
- private var oldSelectedZIndex = Float.MIN_VALUE
- private val visitedIds = HashSet<String>()
- override fun getName(): String = REACT_CLASS
- override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Map<String, Map<String, String>>> {
- return mapOf(
- "pinClick" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onPinClickEvent")),
- "mapClick" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onMapClickEvent")),
- "cameraIdle" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onCameraIdleEvent"))
- )
- }
- override fun createViewInstance(context: ThemedReactContext): MapView {
- MapsInitializer.initialize(reactContext.applicationContext)
- mapView = MapView(context)
- this.context = context
- mapView?.onCreate(null)
- mapView?.getMapAsync(this)
- mapView?.onResume()
- return mapView!!
- }
- override fun onMapReady(googleMap: GoogleMap) {
- map = googleMap
- googleMap.clear()
- initialRegion?.let { region ->
- val lat = region.getDouble("latitude")
- val lng = region.getDouble("longitude")
- val latDelta = region.getDouble("latitudeDelta")
- val lngDelta = region.getDouble("longitudeDelta")
- val bounds = LatLngBounds(
- LatLng(lat - latDelta, lng - lngDelta),
- LatLng(lat + latDelta, lng + lngDelta)
- )
- if ((mapView?.height ?: 0) <= 0 || (mapView?.width ?: 0) <= 0) {
- map?.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(lat, lng), 10f))
- boundsToMove = bounds
- } else {
- map?.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0))
- boundsToMove = null
- }
- }
- googleMap.setOnMapLoadedCallback(this)
- googleMap.uiSettings.isCompassEnabled = false
- googleMap.setMinZoomPreference(5.5f)
- googleMap.setOnMapClickListener(this)
- googleMap.setOnCameraIdleListener(this)
- }
- @ReactProp(name = "initialMapRegion")
- fun setInitialMapRegion(view: MapView, geo: ReadableMap?) {
- initialRegion = geo
- }
- @ReactProp(name = "pins")
- fun setPins(view: MapView, pins: ReadableArray?) {
- this.pins = pins
- if (mapReady) renderPins() else renderPinsOnLoad = true
- }
- override fun receiveCommand(root: MapView, commandId: String, args: ReadableArray?) {
- if (commandId == ANIMATE_MAP_TO_LOCATION) {
- args?.getMap(0)?.let { animateMapToLocation(it) }
- }
- }
- @ReactMethod
- fun animateMapToLocation(geo: ReadableMap) {
- val loc = geo.getMap("viewport")
- val southwest = loc?.getMap("southwest")
- val northeast = loc?.getMap("northeast")
- boundsToMove = null
- val bounds = LatLngBounds(
- LatLng(southwest?.getDouble("lat") ?: 0.0, southwest?.getDouble("lng") ?: 0.0),
- LatLng(northeast?.getDouble("lat") ?: 0.0, northeast?.getDouble("lng") ?: 0.0)
- )
- map?.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0))
- }
- override fun onMapClick(latLng: LatLng) {
- val event = Arguments.createMap().apply {
- putDouble("latitude", latLng.latitude)
- putDouble("longitude", latLng.longitude)
- }
- val reactContext = mapView?.context as ReactContext
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
- mapView!!.id, "mapClick", event
- )
- selectedId = null
- selectedItem?.let { item ->
- clusterRender?.getMarker(item)?.let { marker ->
- marker.setIcon(
- HaveIcon.getIcon(
- mapView!!, item.price, item.country!!, HaveIcon.HaveIconState.VISITED
- )
- )
- if (oldSelectedZIndex != Float.MIN_VALUE) marker.zIndex = oldSelectedZIndex
- }
- oldSelectedZIndex = Float.MIN_VALUE
- }
- }
- override fun onCameraIdle() {
- val bounds = map?.projection?.visibleRegion?.latLngBounds
- val event = Arguments.createMap().apply {
- putMap("northeast", createLatLngMap(bounds?.northeast))
- putMap("southwest", createLatLngMap(bounds?.southwest))
- }
- val reactContext = mapView?.context as ReactContext
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
- mapView!!.id, "cameraIdle", event
- )
- clusterManager?.onCameraIdle()
- }
- private fun createLatLngMap(latLng: LatLng?): WritableMap {
- return Arguments.createMap().apply {
- putDouble("latitude", latLng?.latitude ?: 0.0)
- putDouble("longitude", latLng?.longitude ?: 0.0)
- }
- }
- override fun onMapLoaded() {
- boundsToMove?.let { bounds ->
- map?.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0))
- boundsToMove = null
- }
- clusterManager = ClusterManager(mapView?.context, map)
- val gen = IconGenerator(mapView?.context).apply {
- setBackground(mapView?.context?.let {
- AppCompatResources.getDrawable(it, R.drawable.map_cluster)
- })
- }
- val inflater = mapView?.context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
- val activityView = inflater.inflate(R.layout.cluster_view, mapView, false)
- gen.setContentView(activityView)
- clusterRender = object : DefaultClusterRenderer<ProfileClusterItem>(
- mapView?.context, map, clusterManager
- ) {
- override fun onBeforeClusterRendered(
- cluster: Cluster<ProfileClusterItem>,
- markerOptions: MarkerOptions
- ) {
- val clusterSize = cluster.size
- markerOptions.icon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(clusterSize.toString())))
- }
- override fun onClusterUpdated(
- cluster: Cluster<ProfileClusterItem>,
- marker: Marker
- ) {
- val clusterSize = cluster.size
- marker.setIcon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(clusterSize.toString())))
- }
- override fun onBeforeClusterItemRendered(
- item: ProfileClusterItem,
- markerOptions: MarkerOptions
- ) {
- markerOptions.apply {
- icon(item.getIcon())
- snippet(item.snippet)
- title(item.title)
- }
- }
- }
- clusterRender?.minClusterSize = 5
- val displayMetrics = mapView?.context?.resources?.displayMetrics
- clusterManager?.apply {
- setAlgorithm(
- NonHierarchicalViewBasedAlgorithm(
- displayMetrics?.widthPixels ?: 0,
- displayMetrics?.heightPixels ?: 0
- )
- )
- renderer = clusterRender
- setOnClusterItemClickListener(this@MemberMap)
- }
- mapReady = true
- if (renderPinsOnLoad) {
- renderPins()
- }
- }
- override fun onClusterItemClick(item: ProfileClusterItem?): Boolean {
- if (item == null) return true
- map?.animateCamera(CameraUpdateFactory.newLatLng(item.position), 300, null)
- val profileId = item.profileId
- val event = Arguments.createMap().apply {
- putString("profileId", profileId)
- }
- selectedItem?.takeIf { it != item }?.let { previousItem ->
- clusterRender?.getMarker(previousItem)?.let { marker ->
- marker.setIcon(
- HaveIcon.getIcon(
- mapView!!, previousItem.price, previousItem.country!!, HaveIcon.HaveIconState.VISITED
- )
- )
- if (oldSelectedZIndex != -1f) {
- marker.zIndex = oldSelectedZIndex
- }
- }
- }
- if (item.haveOrNeed == "h") {
- clusterRender?.getMarker(item)?.let { marker ->
- marker.setIcon(
- HaveIcon.getIcon(
- mapView!!, item.price, item.country!!, HaveIcon.HaveIconState.ACTIVE
- )
- )
- oldSelectedZIndex = marker.zIndex
- selectedItem = item
- marker.zIndex = Float.MAX_VALUE
- }
- selectedId = profileId
- visitedIds.add(selectedId!!)
- } else {
- selectedItem = null
- selectedId = null
- }
- val reactContext = mapView?.context as ReactContext
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
- mapView!!.id, "pinClick", event
- )
- return true
- }
- private fun renderPins() {
- try {
- renderExecutor.shutdownNow()
- renderExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- renderExecutor = Executors.newSingleThreadExecutor()
- renderExecutor.execute {
- executor?.shutdownNow()
- try {
- executor?.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- renderCount++
- val thisRenderCount = renderCount
- val thisPins = pins
- val numCores = coreCount
- activity = reactContext.currentActivity
- executor = Executors.newFixedThreadPool(numCores) as ThreadPoolExecutor
- val numPins = thisPins?.size()
- if (numPins != null) {
- if (numPins < 1) return@execute
- }
- val pinsHashSet = Collections.synchronizedSet(HashSet<String>())
- val items = clusterManager?.algorithm?.items
- val displayedPinsHashSet = items?.let { HashSet<String>(it.size) }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- items?.parallelStream()?.forEach { item ->
- displayedPinsHashSet?.add(item.profileId)
- }
- } else {
- val legacyExecutor = Executors.newFixedThreadPool(numCores)
- val itemList = ArrayList(items!!)
- val segmentSize = ceil(itemList.size / numCores.toDouble()).toInt()
- for (i in 0 until numCores) {
- val start = i * segmentSize
- val end = minOf((i + 1) * segmentSize, itemList.size)
- val segment = itemList.subList(start, end)
- legacyExecutor.submit {
- segment.forEach { item ->
- displayedPinsHashSet?.add(item.profileId)
- }
- }
- }
- legacyExecutor.shutdown()
- try {
- legacyExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
- } catch (e: InterruptedException) {
- e.printStackTrace()
- }
- }
- val clusterItems = Collections.synchronizedList(ArrayList<ProfileClusterItem>(numPins!!))
- for (t in 0 until numCores) {
- executor!!.execute execute2@{
- val minPin = (floor(numPins / numCores.toDouble()) * t).toInt()
- val maxPin = if (t == numCores - 1) {
- numPins
- } else {
- (floor(numPins / numCores.toDouble()) * (t + 1)).toInt() + 1
- }
- for (i in minPin until maxPin) {
- if (renderCount != thisRenderCount) return@execute2
- try {
- val pinArr = thisPins.getArray(i)
- val type = pinArr.getString(0)
- val profileId = pinArr.getString(1)
- if (!displayedPinsHashSet!!.contains(profileId)) {
- when (type) {
- "h" -> {
- val pinLoc = pinArr.getArray(2)
- val point = LatLng(pinLoc.getDouble(0), pinLoc.getDouble(1))
- if (renderCount == thisRenderCount && activity != null) {
- val country = pinArr.getString(4)
- val price = pinArr.getInt(3)
- val selectedPin = selectedId == profileId
- val wasVisited = visitedIds.contains(profileId)
- val item = ProfileClusterItem(
- point, profileId, "h", price, country,
- HaveIcon.getIcon(
- mapView!!, price, country,
- if (selectedPin) HaveIcon.HaveIconState.ACTIVE
- else if (wasVisited) HaveIcon.HaveIconState.VISITED
- else HaveIcon.HaveIconState.UNVISITED
- )
- )
- clusterItems.add(item)
- }
- }
- "n" -> {
- val pinLocs = pinArr.getArray(2)
- for (j in 0 until pinLocs.size()) {
- val pinLoc = pinLocs.getArray(j)
- val point = LatLng(pinLoc.getDouble(0), pinLoc.getDouble(1))
- if (renderCount == thisRenderCount && activity != null) {
- val item = ProfileClusterItem(point, profileId, "n", NeedIcon.getIcon(
- mapView!!
- ))
- clusterItems.add(item)
- }
- }
- }
- }
- }
- synchronized(pinsHashSet) {
- pinsHashSet.add(profileId)
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- }
- }
- try {
- executor!!.shutdown()
- executor!!.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
- } catch (e: InterruptedException) {
- e.printStackTrace()
- }
- if (thisRenderCount != renderCount) return@execute
- synchronized(clusterItems) {
- clusterManager!!.addItems(clusterItems)
- val itemsList = clusterManager!!.algorithm.items
- val toRemove = ArrayList<ProfileClusterItem>(numPins)
- for (item in itemsList) {
- if (!pinsHashSet.contains(item.profileId)) {
- toRemove.add(item)
- }
- }
- clusterManager!!.removeItems(toRemove)
- }
- handler.post {
- if (thisRenderCount == renderCount) {
- clusterManager?.cluster()
- }
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement